diff --git a/Makefile b/Makefile index f92d7bb..9e61395 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ update:; forge update build:; forge fmt && forge build -test :; forge test +test :; forge fmt && forge build && forge test snapshot :; forge snapshot diff --git a/script/Config.sol b/script/Config.sol index 72916b4..18a1f07 100644 --- a/script/Config.sol +++ b/script/Config.sol @@ -7,6 +7,7 @@ contract Config { address[] donationRecipient; address[] feeReceiver; address[] admins; + uint256 minFee; } NetworkConfig private activeNetworkConfig; @@ -34,7 +35,7 @@ contract Config { feeReceivers[0] = address(1); admins[0] = address(1); - return getConfig(acceptedTokens, donationRecipients, feeReceivers, admins); + return getConfig(acceptedTokens, donationRecipients, feeReceivers, admins, 5e16); } // ================== Gnosis Config ================== @@ -50,7 +51,7 @@ contract Config { feeReceivers[0] = address(1); admins[0] = address(1); - return getConfig(acceptedTokens, donationRecipients, feeReceivers, admins); + return getConfig(acceptedTokens, donationRecipients, feeReceivers, admins, 5e16); } // ================== Goerli Config ================== @@ -66,7 +67,7 @@ contract Config { feeReceivers[0] = address(1); admins[0] = address(1); - return getConfig(acceptedTokens, donationRecipients, feeReceivers, admins); + return getConfig(acceptedTokens, donationRecipients, feeReceivers, admins, 5e16); } // ================== Helper ================== @@ -79,7 +80,8 @@ contract Config { address[] memory _acceptedToken, address[] memory _donationRecipient, address[] memory _feeReceiver, - address[] memory _admins + address[] memory _admins, + uint256 _minFee ) internal pure returns (NetworkConfig memory) { uint256 tLength = _acceptedToken.length; uint256 dLength = _donationRecipient.length; @@ -90,7 +92,8 @@ contract Config { acceptedToken: new address[](tLength), donationRecipient: new address[](dLength), feeReceiver: new address[](fLength), - admins: new address[](aLength) + admins: new address[](aLength), + minFee: _minFee }); for (uint256 i = 0; i < tLength; i++) { @@ -127,4 +130,8 @@ contract Config { function getAdmins() public view returns (address[] memory) { return activeNetworkConfig.admins; } + + function getMinFee() public view returns (uint256) { + return activeNetworkConfig.minFee; + } } diff --git a/script/DonationHandler.s.sol b/script/DonationHandler.s.sol index 39c1afe..cddda73 100644 --- a/script/DonationHandler.s.sol +++ b/script/DonationHandler.s.sol @@ -23,7 +23,11 @@ contract DeployDonationHandler is Script, Config { ); DonationHandler(address(proxy)).initialize( - config.getAcceptedTokens(), config.getDonationRecipients(), config.getFeeReceivers(), config.getAdmins() + config.getAcceptedTokens(), + config.getDonationRecipients(), + config.getFeeReceivers(), + config.getAdmins(), + config.getMinFee() ); vm.stopBroadcast(); diff --git a/src/DonationHandler.sol b/src/DonationHandler.sol index 919e83e..90bba59 100644 --- a/src/DonationHandler.sol +++ b/src/DonationHandler.sol @@ -6,19 +6,18 @@ import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import {ReentrancyGuardUpgradeable as ReentrancyGuard} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; -import {MulticallUpgradeable as Multicall} from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol"; import "./DonationHandlerRoles.sol"; +import "./gitcoin/IVotingStrategy.sol"; /// @title DonationHandler /// @author @Kurt for Giveth /// @notice This contract is used to handle donations /// This contract is build to use with proxies. /// -/// The user can donate whitelisted token to whitelisted recipients by calling the donate function. -/// A donation fee can be set by the user. The fee is taken from the donation amount. -/// The donation fee is a percentage of the donation amount where 1e18 is 100%, 1e17 10%, etc.. -/// The donation fee can be set by the user and is limited by the minFee and maxFee. -/// The min fee is set by default to 0 and can be changed by the protocol admins. +/// The user can donate whitelisted token to whitelisted recipients by calling the vote function. +/// +/// The fee is deducted from the users donation, assigned to the contracts address and can be withdrawn by a fee receiver. +/// The min fee is set during initialization and can be changed by the protocol admins. /// The max fee is set by default to 1e18 and can't be changed. /// /// The user can withdraw the donation of a single token by calling the withdraw function. @@ -32,7 +31,7 @@ import "./DonationHandlerRoles.sol"; /// /// The donation balance of one token can be checked by calling the balanceOf function. /// The donation balance of multiple token can be checked by calling the balancesOf function. -contract DonationHandler is DonationHandlerRoles, ReentrancyGuard, Multicall { +contract DonationHandler is IVotingStrategy, DonationHandlerRoles, ReentrancyGuard { using SafeERC20 for IERC20; /// @notice 1e18 represents 100%, 1e16 represents 1% @@ -41,6 +40,9 @@ contract DonationHandler is DonationHandlerRoles, ReentrancyGuard, Multicall { /// @notice Minimum donation fee. 0 by default uint256 public minFee; + bool public isTokenWhitelistActive; + bool public isRecipientWhitelistActive; + /// @notice mapping: user => token => amount mapping(address => mapping(address => uint256)) public balances; @@ -49,45 +51,74 @@ contract DonationHandler is DonationHandlerRoles, ReentrancyGuard, Multicall { /// @param _donationReceiver Array of donation receivers /// @param _feeReceiver Array of fee receivers /// @param _admins Array of admins + /// @param _minFee Minimum donation fee function initialize( address[] calldata _acceptedToken, address[] calldata _donationReceiver, address[] calldata _feeReceiver, - address[] calldata _admins + address[] calldata _admins, + uint256 _minFee ) public initializer { __DonationHandlerRoles_init(_acceptedToken, _donationReceiver, _feeReceiver, _admins); __ReentrancyGuard_init(); - __Multicall_init(); + + if (_acceptedToken.length > 0) { + isTokenWhitelistActive = true; + } + if (_donationReceiver.length > 0) { + isRecipientWhitelistActive = true; + } + + if (_minFee > 0) { + _setMinFee(_minFee); + } } - /// @notice Donate tokens to a recipient. The fee is deducted from the donation amount. - /// @param _token Address of the token to donate - /// @param _recipient Address of the recipient - /// @param _amount Amount of tokens to donate - /// @param _fee Fee to be paid to the fee receiver (protocol) - function donate(address _token, address _recipient, uint256 _amount, uint256 _fee) external payable nonReentrant { - if (_fee > HUNDRED) revert FeeTooHigh(); - if (_fee < minFee) revert FeeTooLow(); - if (_amount == 0) revert InvalidAmount(); + /// @notice Donate(vote) to a whitelisted recipient. + /// @param encodedVotes Array of donations + /// @param voterAddress Address of the voter + function vote(bytes[] calldata encodedVotes, address voterAddress) + external + payable + override + nonReentrant + isRoundContract + { + /// @dev iterate over multiple donations and transfer funds + uint256 length = encodedVotes.length; + uint256 msgValue = 0; - _validateDonation(_token, _recipient); + for (uint256 i = 0; i < length;) { + (address _token, uint256 _amount, address _grantAddress) = + abi.decode(encodedVotes[i], (address, uint256, address)); - if (_token != NATIVE) { - _transfer(_token, _amount); - } else { - if (msg.value != _amount) revert InvalidAmount(); - } + if (isTokenWhitelistActive) _checkToken(_token); + if (isRecipientWhitelistActive) { + _checkDonationRecipient(_grantAddress); + } - if (_fee == 0) { - _registerDonation(_token, _recipient, _amount); - } else if (_fee == HUNDRED) { - _registerFee(_token, _amount); - } else { - uint256 feeAmount = (_amount * _fee) / HUNDRED; - uint256 donationAmount = _amount - feeAmount; + if (_token != NATIVE) { + _transfer(voterAddress, _token, _amount); + } else { + msgValue += _amount; + } + + if (minFee > 0) { + uint256 fee = (_amount * minFee) / HUNDRED; - _registerDonation(_token, _recipient, donationAmount); - _registerFee(_token, feeAmount); + _registerDonation(_token, _grantAddress, _amount - fee); + _registerFee(_token, fee); + } else { + _registerDonation(_token, _grantAddress, _amount); + } + + unchecked { + i++; + } + } + + if (msgValue > msg.value) { + revert InvalidAmount(); } } @@ -108,19 +139,11 @@ contract DonationHandler is DonationHandlerRoles, ReentrancyGuard, Multicall { emit DonationRegistered(_token, msg.sender, _recipient, _amount); } - /// @notice Internal function. Validates a donation by checking if token and donation recipient are whitelisted. - /// @param _token Address of the token - /// @param _recipient Address of the recipient - function _validateDonation(address _token, address _recipient) internal view { - _checkToken(_token); - _checkDonationRecipient(_recipient); - } - /// @notice Internal function. Transfers tokens from the sender to the contract. /// @param _token Address of the token /// @param _amount Amount of tokens - function _transfer(address _token, uint256 _amount) internal { - IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); + function _transfer(address _from, address _token, uint256 _amount) internal { + IERC20(_token).safeTransferFrom(_from, address(this), _amount); } /// @notice Withdraw tokens from the contract to msg.sender. @@ -243,11 +266,33 @@ contract DonationHandler is DonationHandlerRoles, ReentrancyGuard, Multicall { /// @param _minFee Minimum donation fee function setMinFee(uint256 _minFee) external { _checkAdmin(msg.sender); + _setMinFee(_minFee); + } + + /// @notice Internal function. Set minimum donation fee. Emits MinFeeSet event. + /// @param _minFee Minimum donation fee + function _setMinFee(uint256 _minFee) internal { if (_minFee > HUNDRED) revert FeeTooHigh(); minFee = _minFee; emit MinFeeSet(_minFee); } + /// @notice Enable/Disable token whitelist. Can only be called by an Admin. Emits IsTokenWhitelistActiveSet event. + /// @param _isTokenWhitelistActive Enable/Disable token whitelist + function setIsTokenWhitelistActive(bool _isTokenWhitelistActive) external { + _checkAdmin(msg.sender); + isTokenWhitelistActive = _isTokenWhitelistActive; + emit IsTokenWhitelistActiveSet(_isTokenWhitelistActive); + } + + /// @notice Enable/Disable recipient whitelist. Can only be called by an Admin. Emits IsRecipientWhitelistActiveSet event. + /// @param _isRecipientWhitelistActive Enable/Disable recipient whitelist + function setIsRecipientWhitelistActive(bool _isRecipientWhitelistActive) external { + _checkAdmin(msg.sender); + isRecipientWhitelistActive = _isRecipientWhitelistActive; + emit IsRecipientWhitelistActiveSet(_isRecipientWhitelistActive); + } + /// @notice Throws if passed fee is above 100%. error FeeTooHigh(); @@ -286,4 +331,12 @@ contract DonationHandler is DonationHandlerRoles, ReentrancyGuard, Multicall { /// @notice Emitted when the minimum fee is set /// @param minFee The minimum fee event MinFeeSet(uint256 minFee); + + /// @notice Emitted when the token whitelist is set to active or inactive + /// @param isTokenWhitelistActive The token whitelist status + event IsTokenWhitelistActiveSet(bool isTokenWhitelistActive); + + /// @notice Emitted when the recipient whitelist is set to active or inactive + /// @param isRecipientWhitelistActive The recipient whitelist status + event IsRecipientWhitelistActiveSet(bool isRecipientWhitelistActive); } diff --git a/src/DonationHandlerRoles.sol b/src/DonationHandlerRoles.sol index a0d26c5..58712af 100644 --- a/src/DonationHandlerRoles.sol +++ b/src/DonationHandlerRoles.sol @@ -13,8 +13,8 @@ contract DonationHandlerRoles is AccessControl { bytes32 public constant FEE_RECEIVER = keccak256("FEE_RECEIVER"); bytes32 public constant ADMIN = keccak256("ADMIN"); - /// @notice special address which represents the native network currency - address public constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + /// @notice special address which represents the native network currency. address(0) is used by gitcoin. + address public constant NATIVE = address(0); //0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @notice Initializes the contract settings by adding all addresses to their roles. /// @param _acceptedToken The list of accepted tokens. @@ -29,6 +29,7 @@ contract DonationHandlerRoles is AccessControl { ) internal onlyInitializing { __AccessControl_init(); _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); + _setupRole(DONATION_RECIPIENT, address(this)); // this contract is a donation recipient (for fees) by default _setRoleAdmin(ACCEPTED_TOKEN, ADMIN); _setRoleAdmin(DONATION_RECIPIENT, ADMIN); diff --git a/src/gitcoin/IVotingStrategy.sol b/src/gitcoin/IVotingStrategy.sol new file mode 100644 index 0000000..e40a0e2 --- /dev/null +++ b/src/gitcoin/IVotingStrategy.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.17; + +/** + * @notice Defines the abstract contract for voting algorithms on grants + * within a round. Any new voting algorithm would be expected to + * extend this abstract contract. + * Every IVotingStrategy contract would be unique to RoundImplementation + * and would be deployed before creating a round + */ +abstract contract IVotingStrategy { + // --- Data --- + + /// @notice Round address + address public roundAddress; + + // --- Modifier --- + + /// @notice modifier to check if sender is round contract. + modifier isRoundContract() { + require(roundAddress != address(0), "error: voting contract not linked to a round"); + require(msg.sender == roundAddress, "error: can be invoked only by round contract"); + _; + } + + // --- Core methods --- + + /** + * @notice Invoked by RoundImplementation on creation to + * set the round for which the voting contracts is to be used + * + */ + function init() external { + require(roundAddress == address(0), "init: roundAddress already set"); + roundAddress = msg.sender; + } + + /** + * @notice Invoked by RoundImplementation to allow voter to case + * vote for grants during a round. + * + * @dev + * - allows contributor to do cast multiple votes which could be weighted. + * - should be invoked by RoundImplementation contract + * - ideally IVotingStrategy implementation should emit events after a vote is cast + * - this would be triggered when a voter casts their vote via grant explorer + * + * @param _encodedVotes encoded votes + * @param _voterAddress voter address + */ + function vote(bytes[] calldata _encodedVotes, address _voterAddress) external payable virtual; +} diff --git a/test/DonationHandler.t.sol b/test/DonationHandler.t.sol index 244e91c..ce7b0cf 100644 --- a/test/DonationHandler.t.sol +++ b/test/DonationHandler.t.sol @@ -12,28 +12,31 @@ contract DonationHandlerTest is SharedInitialization { // Donate - function testFail_donateWithoutApproval() public { - donationHandler.donate(address(allowedToken), address(1), 100, 0); + function _encode(address token, uint256 amount, address recipient) internal pure returns (bytes memory) { + return abi.encode(token, amount, recipient); } - function test_donateWithoutFee() public { - allowedToken.approve(address(donationHandler), 100); - donationHandler.donate(address(allowedToken), address(1), 100, 0); - assertEq(donationHandler.balanceOf(address(allowedToken), address(1)), 100); + function testFail_donateWithoutApproval() public { + bytes[] memory donation = new bytes[](1); + donation[0] = _encode(address(allowedToken), 100, address(1)); + donationHandler.vote(donation, deployer); } function _donate() internal { - allowedToken.approve(address(donationHandler), 100); - donationHandler.donate(address(allowedToken), address(1), 100, 1e17); // 10% fee + bytes[] memory donation = new bytes[](2); + donation[0] = _encode(address(allowedToken), 100, address(1)); + donation[1] = _encode(address(allowedToken2), 100, address(1)); + + allowedToken.approve(address(donationHandler), 100); allowedToken2.approve(address(donationHandler), 100); - donationHandler.donate(address(allowedToken2), address(1), 100, 1e17); // 10% fee + + donationHandler.vote(donation, deployer); } function test_donate() public { _donate(); assertEq(donationHandler.balanceOf(address(allowedToken), address(1)), 90); - assertEq(donationHandler.balanceOf(address(allowedToken), address(donationHandler)), 10); uint256[] memory balances = donationHandler.balancesOf(acceptedToken, address(1)); assertEq(balances.length, 2); @@ -41,38 +44,43 @@ contract DonationHandlerTest is SharedInitialization { assertEq(balances[1], 90); } - function test_donateHundred() public { - allowedToken.approve(address(donationHandler), 100); - donationHandler.donate(address(allowedToken), address(1), 100, 1e18); - assertEq(donationHandler.balanceOf(address(allowedToken), address(1)), 0); - assertEq(donationHandler.balanceOf(address(allowedToken), address(donationHandler)), 100); - } - - function testFail_donateTooHigh() public { - allowedToken.approve(address(donationHandler), 100); - donationHandler.donate(address(allowedToken), address(1), 100, 1.1e18); // 110% fee - } - - function testFail_donateTooLow() public { - donationHandler.setMinFee(1e17); // min fee: 10% - allowedToken.approve(address(donationHandler), 100); - donationHandler.donate(address(allowedToken), address(1), 100, 1e16); // 1% fee - } - function testFail_donateToWrongRecipient() public { allowedToken.approve(address(donationHandler), 100); - donationHandler.donate(address(allowedToken), address(2), 100, 0); + bytes[] memory donation = new bytes[](1); + donation[0] = _encode(address(allowedToken), 100, address(2)); + donationHandler.vote(donation, deployer); } function testFail_donateWithWrongToken() public { notAllowedToken.approve(address(donationHandler), 100); - donationHandler.donate(address(notAllowedToken), address(1), 100, 0); + bytes[] memory donation = new bytes[](1); + donation[0] = _encode(address(notAllowedToken), 100, address(1)); + donationHandler.vote(donation, deployer); } function test_donateEth() public { - donationHandler.donate{value: 100}(NATIVE, address(1), 100, 1e17); // 10% fee + bytes[] memory donation = new bytes[](1); + donation[0] = _encode(NATIVE, 100, address(1)); + donationHandler.vote{value: 100}(donation, deployer); assertEq(donationHandler.balanceOf(NATIVE, address(1)), 90); - assertEq(donationHandler.balanceOf(NATIVE, address(donationHandler)), 10); + } + + function test_donateMany() public { + bytes[] memory donation = new bytes[](2); + + donation[0] = _encode(address(allowedToken), 200, address(1)); + donation[1] = _encode(address(allowedToken2), 200, address(1)); + + allowedToken.approve(address(donationHandler), 200); + allowedToken2.approve(address(donationHandler), 200); + + donationHandler.vote(donation, deployer); + + assertEq(donationHandler.balanceOf(address(allowedToken), address(1)), 180); + assertEq(donationHandler.balanceOf(address(allowedToken), address(donationHandler)), 20); + + assertEq(donationHandler.balanceOf(address(allowedToken2), address(1)), 180); + assertEq(donationHandler.balanceOf(address(allowedToken2), address(donationHandler)), 20); } // withdraw @@ -80,9 +88,9 @@ contract DonationHandlerTest is SharedInitialization { function test_withdraw() public { _donate(); vm.prank(address(1)); - donationHandler.withdraw(address(allowedToken), 80); - assertEq(donationHandler.balanceOf(address(allowedToken), address(1)), 10); - assertEq(allowedToken.balanceOf(address(1)), 80); + donationHandler.withdraw(address(allowedToken), 90); + assertEq(donationHandler.balanceOf(address(allowedToken), address(1)), 0); + assertEq(allowedToken.balanceOf(address(1)), 90); } function test_withdrawMany() public { @@ -103,13 +111,13 @@ contract DonationHandlerTest is SharedInitialization { function testFail_withdrawWrongToken() public { _donate(); vm.prank(address(1)); - donationHandler.withdraw(address(notAllowedToken), 80); + donationHandler.withdraw(address(notAllowedToken), 90); } function testFail_withdrawWrongRecipient() public { _donate(); vm.prank(address(2)); - donationHandler.withdraw(address(allowedToken), 80); + donationHandler.withdraw(address(allowedToken), 90); } function testFail_withdrawFeeNotAdmin() public { @@ -135,7 +143,11 @@ contract DonationHandlerTest is SharedInitialization { } function test_WithdrawEth() public { - donationHandler.donate{value: 100}(NATIVE, address(1), 100, 1e17); // 10% fee + bytes[] memory donation = new bytes[](1); + donation[0] = _encode(NATIVE, 100, address(1)); + + donationHandler.vote{value: 100}(donation, deployer); + assertEq(donationHandler.balanceOf(NATIVE, address(1)), 90); assertEq(donationHandler.balanceOf(NATIVE, address(donationHandler)), 10); diff --git a/test/DonationHandlerMulticall.t.sol b/test/DonationHandlerMulticall.t.sol deleted file mode 100644 index d66d9a0..0000000 --- a/test/DonationHandlerMulticall.t.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "./shared/SharedInitialization.sol"; - -contract DonationHandlerMulticallTest is SharedInitialization { - // acceptedToken[0] = address(allowedToken); - // acceptedToken[1] = address(allowedToken2); - // donationRecipient[0] = address(1); - // feeReceiver[0] = address(2); - // admins[0] = address(3); - - // Donate - - function testFail_donateWithoutApproval() public { - bytes[] memory data = new bytes[](1); - data[0] = abi.encodeWithSelector(donationHandler.donate.selector, address(allowedToken), address(1), 100, 0); - donationHandler.multicall(data); - } - - function testFail_donateWithoutApproval2() public { - allowedToken.approve(address(donationHandler), 100); - - bytes[] memory data = new bytes[](2); - - data[0] = abi.encodeWithSelector(donationHandler.donate.selector, address(allowedToken), address(1), 100, 1e17); - data[1] = abi.encodeWithSelector(donationHandler.donate.selector, address(allowedToken2), address(1), 100, 1e17); - - donationHandler.multicall(data); - } - - function test_donate() public { - allowedToken.approve(address(donationHandler), 100); - allowedToken2.approve(address(donationHandler), 200); - - bytes[] memory data = new bytes[](2); - - data[0] = abi.encodeWithSelector(donationHandler.donate.selector, address(allowedToken), address(1), 100, 1e17); - data[1] = abi.encodeWithSelector(donationHandler.donate.selector, address(allowedToken2), address(1), 200, 9e17); - - vm.expectEmit(true, true, true, true, address(donationHandler)); - emit DonationRegistered(address(allowedToken), address(this), address(1), 90); - - vm.expectEmit(true, true, true, true, address(donationHandler)); - emit FeeRegistered(address(allowedToken), address(this), 10); - - vm.expectEmit(true, true, true, true, address(donationHandler)); - emit DonationRegistered(address(allowedToken2), address(this), address(1), 20); - - vm.expectEmit(true, true, true, true, address(donationHandler)); - emit FeeRegistered(address(allowedToken2), address(this), 180); - - donationHandler.multicall(data); - assertEq(donationHandler.balanceOf(address(allowedToken), address(1)), 90); - assertEq(donationHandler.balanceOf(address(allowedToken), address(donationHandler)), 10); - - assertEq(donationHandler.balanceOf(address(allowedToken2), address(1)), 20); - assertEq(donationHandler.balanceOf(address(allowedToken2), address(donationHandler)), 180); - - uint256[] memory balances = donationHandler.balancesOf(acceptedToken, address(1)); - assertEq(balances.length, 2); - assertEq(balances[0], 90); - assertEq(balances[1], 20); - } - - function test_donateWithoutFee() public { - allowedToken.approve(address(donationHandler), 100); - allowedToken2.approve(address(donationHandler), 100); - - bytes[] memory data = new bytes[](2); - - data[0] = abi.encodeWithSelector(donationHandler.donate.selector, address(allowedToken), address(1), 100, 0); - data[1] = abi.encodeWithSelector(donationHandler.donate.selector, address(allowedToken2), address(1), 100, 0); - - vm.expectEmit(true, true, true, true, address(donationHandler)); - emit DonationRegistered(address(allowedToken), address(this), address(1), 100); - - vm.expectEmit(true, true, true, true, address(donationHandler)); - emit DonationRegistered(address(allowedToken2), address(this), address(1), 100); - - donationHandler.multicall(data); - } -} diff --git a/test/shared/SharedInitialization.sol b/test/shared/SharedInitialization.sol index 0726c46..89cb633 100644 --- a/test/shared/SharedInitialization.sol +++ b/test/shared/SharedInitialization.sol @@ -7,7 +7,7 @@ import "../mocks/MockERC20.sol"; contract SharedInitialization is Test { address deployer = 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84; - address public constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address public constant NATIVE = address(0); DonationHandler public donationHandler; MockERC20 public allowedToken; @@ -39,7 +39,8 @@ contract SharedInitialization is Test { } function _initializeDonationHandler() internal { - donationHandler.initialize(acceptedToken, donationRecipient, feeReceiver, admins); + donationHandler.init(); + donationHandler.initialize(acceptedToken, donationRecipient, feeReceiver, admins, 1e17); } // Events