Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contract v1 #75

Merged
merged 3 commits into from
Mar 14, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 192 additions & 21 deletions packages/hardhat/contracts/BGGrants.sol
Original file line number Diff line number Diff line change
@@ -1,28 +1,67 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "@openzeppelin/contracts/access/Ownable.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

// Dummy contract for initial iteration
// From https://github.com/BuidlGuidl/Eth-Splitter/blob/master/packages/hardhat/contracts/Splitter.sol
contract BGGrants is Ownable, ReentrancyGuard {
error INVALID_RECIPIENT();
interface ENSContract {
function setName(string memory newName) external;
}

/**
* @title BGGrants
* @notice A smart contract to split ETH or ERC20 tokens between multiple recipients.
* @dev This is intended for research and development purposes only. Use this contract at your
* own risk and discretion.
* @dev Based on https://split.buidlguidl.com/
*/

contract BGGrants is ReentrancyGuard, AccessControl {
using SafeERC20 for IERC20;
// Mainnet ENS contract
ENSContract public immutable ensContract = ENSContract(0xa58E81fe9b61B5c3fE2AFD33CF304c454AbFc7Cb);

bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

// Events
event EthSplit(address indexed sender, uint256 totalAmount, address payable[] recipients, uint256[] amounts);
event EthSplitEqual(address indexed sender, uint256 totalAmount, address payable[] recipients);
event Erc20Split(address indexed sender, address payable[] recipients, uint256[] amounts, IERC20 token);
event Erc20SplitEqual(address indexed sender, uint256 totalAmount, address payable[] recipients, IERC20 token);

//*********************************************************************//
// --------------------------- custom errors ------------------------- //
//*********************************************************************//
error INVALID_INPUT();
error INSUFFICIENT_RECIPIENT_COUNT();
error TRANSFER_FAILED();
error INVALID_RECIPIENT();
error INSUFFICIENT_SPLIT_AMOUNT();
error INVALID_INPUT();
error TRANSFER_FAILED();
error NOT_ADMIN();

event EthSplit(address indexed sender, uint256 totalAmount, address payable[] recipients, uint256[] amounts);
modifier onlyAdmin() {
if (!hasRole(ADMIN_ROLE, msg.sender)) revert NOT_ADMIN();
_;
}

/**
* @notice The constructor sets the owner of the contract
*/
constructor(address _owner) {
super.transferOwnership(_owner);
_setupRole(DEFAULT_ADMIN_ROLE, _owner);
_setupRole(ADMIN_ROLE, _owner);
}

/**
* @notice Splits the ETH amongst the given recipients, according to the specified amounts
* @param recipients The recipients of the ETH
* @param amounts The amounts each recipient shall receive
*/
function splitETH(
address payable[] calldata recipients,
uint256[] calldata amounts
) external payable nonReentrant {
) external payable nonReentrant onlyAdmin {
uint256 remainingAmount = _splitETH(recipients, amounts, msg.value);
emit EthSplit(msg.sender, msg.value, recipients, amounts);

Expand All @@ -32,6 +71,93 @@ contract BGGrants is Ownable, ReentrancyGuard {
}
}

/**
* @notice Splits the ETH equally amongst the given recipients
* @dev The contract adds any leftover dust to the amount to be received by the first recipient in the input array.
* @param recipients The recipients of the ETH
*/
function splitEqualETH(
address payable[] calldata recipients
) external payable nonReentrant onlyAdmin {
uint256 totalAmount = msg.value;
uint256 rLength = recipients.length;
uint256 equalAmount = totalAmount / rLength;
uint256 remainingAmount = totalAmount % rLength;

if (rLength > 25) revert INSUFFICIENT_RECIPIENT_COUNT();

for (uint256 i = 0; i < rLength; ) {
if (recipients[i] == address(0)) revert INVALID_RECIPIENT();
uint256 amountToSend = equalAmount;
if (i == 0) {
amountToSend = amountToSend + remainingAmount;
}
(bool success, ) = recipients[i].call{value: amountToSend, gas: 20000}("");
if (!success) revert TRANSFER_FAILED();
unchecked {
++i;
}
}

emit EthSplitEqual(msg.sender, msg.value, recipients);
}

/**
* @notice Splits the ERC20 tokens amongst the given recipients, according to the specified amounts
* @param token The token to be shared amongst the recipients
* @param recipients The recipients of the ERC20 tokens
* @param amounts The amounts each recipient shall receive
*/
function splitERC20(
IERC20 token,
address payable[] calldata recipients,
uint256[] calldata amounts
) external nonReentrant onlyAdmin {
_transferTokensFromSenderToRecipients(token, recipients, amounts);
emit Erc20Split(msg.sender, recipients, amounts, token);
}

/**
* @notice Splits the ERC20 tokens equally amongst the given recipients
* @param token The token to be shared amongst the recipients
* @param recipients The recipients of the ERC20 tokens
* @param totalAmount The total amount to be shared
*/
function splitEqualERC20(
IERC20 token,
address payable[] calldata recipients,
uint256 totalAmount
) external nonReentrant onlyAdmin {
uint256 rLength = recipients.length;

if (rLength > 25) revert INSUFFICIENT_RECIPIENT_COUNT();

uint256 equalAmount = totalAmount / rLength;

uint256 remainingAmount = totalAmount % rLength;
for (uint256 i = 0; i < rLength; ) {
if (recipients[i] == address(0)) revert INVALID_RECIPIENT();

uint256 amountToSend = equalAmount;
if (i == 0) {
amountToSend = amountToSend + remainingAmount;
}
SafeERC20.safeTransferFrom(token, msg.sender, recipients[i], amountToSend);
unchecked {
++i;
}
}

emit Erc20SplitEqual(msg.sender, totalAmount, recipients, token);
}

/**
* @notice Internal function to split the ETH amongst the given recipients, according to the specified amounts
* @param recipients The recipients of the ETH
* @param amounts The amounts each recipient shall receive
* @param totalAvailable The total available ETH to be split
* @return remainingAmount The remaining ETH dust
*/
function _splitETH(
address payable[] calldata recipients,
uint256[] calldata amounts,
Expand Down Expand Up @@ -60,16 +186,61 @@ contract BGGrants is Ownable, ReentrancyGuard {
}

/**
* Function that allows the owner to withdraw all the Ether in the contract
* The function can only be called by the owner of the contract as defined by the isOwner modifier
*/
function withdraw() public onlyOwner {
(bool success, ) = owner().call{ value: address(this).balance }("");
require(success, "Failed to send Ether");
* @notice Internal function to transfer ERC20 tokens from the sender to the recipients
* @param erc20Token The ERC20 token to be shared
* @param recipients The noble recipients of the tokens
* @param amounts The amounts each recipient shall receive
*/
function _transferTokensFromSenderToRecipients(
IERC20 erc20Token,
address payable[] calldata recipients,
uint256[] calldata amounts
) internal {
uint256 length = recipients.length;

if (length != amounts.length) revert INVALID_INPUT();
if (length > 25) revert INSUFFICIENT_RECIPIENT_COUNT();

for (uint256 i = 0; i < length; ) {
if (recipients[i] == address(0)) revert INVALID_RECIPIENT();
if (amounts[i] == 0) revert INSUFFICIENT_SPLIT_AMOUNT();

SafeERC20.safeTransferFrom(erc20Token, msg.sender, recipients[i], amounts[i]);
unchecked {
++i;
}
}
}

function addAdmin(address _admin) external onlyAdmin {
grantRole(ADMIN_ROLE, _admin);
}

function removeAdmin(address _admin) external onlyAdmin {
revokeRole(ADMIN_ROLE, _admin);
}

/**
* Function that allows the contract to receive ETH
*/
* @notice Withdraws the remaining ETH or ERC20 tokens to the owner's address
* @param token The address of the ERC20 token, or 0 for ETH
*/
function withdraw(IERC20 token) external onlyAdmin {
if (address(token) == address(0)) {
(bool success, ) = msg.sender.call{value: address(this).balance, gas: 20000}("");
if (!success) revert TRANSFER_FAILED();
} else {
token.transfer(msg.sender, token.balanceOf(address(this)));
}
}

/**
* Set the reverse ENS name for the contract
* @param _newName The new ENS name for the contract
* @dev only meant to be call on mainnet
*/
function setName(string memory _newName) onlyAdmin public {
ensContract.setName(_newName);
}

receive() external payable {}
}
Loading