Skip to content

Commit

Permalink
Grants4FirstBatch (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
karankurbur authored Aug 27, 2024
1 parent 6da3a45 commit 107a056
Show file tree
Hide file tree
Showing 4 changed files with 364 additions and 0 deletions.
26 changes: 26 additions & 0 deletions src/Grants4FirstBatch/Context.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}

function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}

function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
181 changes: 181 additions & 0 deletions src/Grants4FirstBatch/Grants4FirstBatch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import { Ownable2Step } from "./Ownable2Step.sol";
import { Ownable } from "./Ownable.sol";

contract Grants4FirstBatch is Ownable2Step {
////////////////////////////////////////////////////////////////
// ERRORS //
////////////////////////////////////////////////////////////////

/// @notice Error that is thrown if the caller is not allowed to call the function
error OnlyAllowedCaller();

/// @notice Error that is thrown if the input arrays have different lengths
error LengthMismatch();

////////////////////////////////////////////////////////////////
// EVENTS //
////////////////////////////////////////////////////////////////

/// @notice Event emitted when a caller is added
event CallerAdded(address indexed caller);

/// @notice Event emitted when a caller is removed
event CallerRemoved(address indexed caller);

/// @notice Event emitted when the AllowanceModule is set
event AllowanceModuleSet(address indexed allowanceModule);

/// @notice Event emitted when the WLD token is set
event WldTokenSet(address indexed wldToken);

/// @notice Event emitted when the Holder is set
event HolderSet(address indexed holder);

/// @notice Event emitted when the RecurringGrantDrop is set
event RecurringGrantDropSet(address indexed recurringGrantDrop);

/// @notice Event emitted when a batch has been processed
event BatchProcessed(uint256 batchSize, uint256 successfulExecutions);

////////////////////////////////////////////////////////////////
/// CONFIG STORAGE ///
////////////////////////////////////////////////////////////////

/// @notice address of the Safe Allowance Module
AllowanceModule public ALLOWANCE_MODULE;

/// @notice Worldcoin token address
address public WLD_TOKEN;

/// @notice BVI Safe that grants allowances to this contract
GnosisSafe public HOLDER;

/// @notice Recurring Grant Drop contract
IRecurringGrantDrop public RECURRING_GRANT_DROP;

/// @notice addresses that can call the batch function
mapping(address => bool) public allowedCallers;

////////////////////////////////////////////////////////////////
/// CONSTRUCTOR ///
////////////////////////////////////////////////////////////////

constructor(
address _allowanceModuleAddress,
address _wldToken,
address _holder,
address _recurringGrantDrop
)
Ownable(msg.sender)
{
ALLOWANCE_MODULE = AllowanceModule(_allowanceModuleAddress);
WLD_TOKEN = _wldToken;
HOLDER = GnosisSafe(_holder);
RECURRING_GRANT_DROP = IRecurringGrantDrop(_recurringGrantDrop);
}

////////////////////////////////////////////////////////////////
/// FUNCTIONS ///
////////////////////////////////////////////////////////////////

/// @notice Batch setNullifierHash + transfer WLD tokens to multiple addresses
/// @param _nullifierHashes array of nullifier hashes
/// @param _recipients array of recipient addresses
/// @param _amounts array of amounts to transfer
function batch(
uint256[] calldata _nullifierHashes,
address[] calldata _recipients,
uint256[] calldata _amounts
)
external
{
if (!(_nullifierHashes.length == _recipients.length && _recipients.length == _amounts.length)) {
revert LengthMismatch();
}

if (!allowedCallers[msg.sender]) {
revert OnlyAllowedCaller();
}

uint256 batchSize = _nullifierHashes.length;
uint256 successfulExecutions = 0;
for (uint256 i = 0; i < _recipients.length; i++) {
try RECURRING_GRANT_DROP.setNullifierHash(_nullifierHashes[i]) {
AllowanceModule(ALLOWANCE_MODULE).executeAllowanceTransfer(
HOLDER, WLD_TOKEN, payable(_recipients[i]), uint96(_amounts[i])
);
successfulExecutions++;
} catch (bytes memory) { }
}

emit BatchProcessed(batchSize, successfulExecutions);
}

////////////////////////////////////////////////////////////////
/// CONFIG FUNCTIONS ///
////////////////////////////////////////////////////////////////

function setAllowanceModule(address _allowanceModuleAddress) external onlyOwner {
ALLOWANCE_MODULE = AllowanceModule(_allowanceModuleAddress);
emit AllowanceModuleSet(_allowanceModuleAddress);
}

function setWldToken(address _wldToken) external onlyOwner {
WLD_TOKEN = _wldToken;
emit WldTokenSet(_wldToken);
}

function setHolder(address _holder) external onlyOwner {
HOLDER = GnosisSafe(_holder);
emit HolderSet(_holder);
}

function setRecurringGrantDrop(IRecurringGrantDrop _recurringGrantDrop) external onlyOwner {
RECURRING_GRANT_DROP = _recurringGrantDrop;
emit RecurringGrantDropSet(address(_recurringGrantDrop));
}

function addCaller(address _caller) external onlyOwner {
allowedCallers[_caller] = true;
emit CallerAdded(_caller);
}

function removeCaller(address _caller) external onlyOwner {
delete allowedCallers[_caller];
emit CallerRemoved(_caller);
}
}

interface GnosisSafe {
/// @dev Allows a Module to execute a Safe transaction without any further confirmations.
/// @param to Destination address of module transaction.
/// @param value Ether value of module transaction.
/// @param data Data payload of module transaction.
/// @param operation Operation type of module transaction.
function execTransactionFromModule(
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation
)
external
returns (bool success);
}

contract Enum {
enum Operation {
Call,
DelegateCall
}
}

interface IRecurringGrantDrop {
function setNullifierHash(uint256 nullifierHash) external;
}

interface AllowanceModule {
function executeAllowanceTransfer(GnosisSafe safe, address token, address payable to, uint96 amount) external;
}
98 changes: 98 additions & 0 deletions src/Grants4FirstBatch/Ownable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;
import { Context } from "./Context.sol";

/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;

/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);

/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);

event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}

/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}

/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}

/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}

/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}

/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}

/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
59 changes: 59 additions & 0 deletions src/Grants4FirstBatch/Ownable2Step.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

import { Ownable } from "./Ownable.sol";

/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is specified at deployment time in the constructor for `Ownable`. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;

event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}

/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is
* one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}

/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}

/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
if (pendingOwner() != sender) {
revert OwnableUnauthorizedAccount(sender);
}
_transferOwnership(sender);
}
}

0 comments on commit 107a056

Please sign in to comment.