Skip to content
Merged
Show file tree
Hide file tree
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
168 changes: 120 additions & 48 deletions contracts/airdrop/AirdropERC1155.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ contract AirdropERC1155 is
uint256 public payeeCount;
uint256 public processedCount;

uint256[] private indicesOfFailed;
uint256[] public indicesOfFailed;

mapping(uint256 => AirdropContent) private airdropContent;

CancelledPayments[] public cancelledPaymentIndices;

/*///////////////////////////////////////////////////////////////
Constructor + initializer logic
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -80,41 +82,82 @@ contract AirdropERC1155 is
//////////////////////////////////////////////////////////////*/

///@notice Lets contract-owner set up an airdrop of ERC721 NFTs to a list of addresses.
function addAirdropRecipients(AirdropContent[] calldata _contents) external onlyRole(DEFAULT_ADMIN_ROLE) {
function addRecipients(AirdropContent[] calldata _contents) external onlyRole(DEFAULT_ADMIN_ROLE) {
uint256 len = _contents.length;
require(len > 0, "No payees provided.");

uint256 currentCount = payeeCount;
payeeCount += len;

for (uint256 i = currentCount; i < len; i += 1) {
airdropContent[i] = _contents[i];
for (uint256 i = 0; i < len; ) {
airdropContent[i + currentCount] = _contents[i];

unchecked {
i += 1;
}
}

emit RecipientsAdded(_contents);
emit RecipientsAdded(currentCount, currentCount + len);
}

///@notice Lets contract-owner cancel any pending payments.
function cancelPendingPayments(uint256 numberOfPaymentsToCancel) external onlyRole(DEFAULT_ADMIN_ROLE) {
uint256 countOfProcessed = processedCount;

// increase processedCount by the specified count -- all pending payments in between will be treated as cancelled.
uint256 newProcessedCount = countOfProcessed + numberOfPaymentsToCancel;
require(newProcessedCount <= payeeCount, "Exceeds total payees.");
processedCount = newProcessedCount;

CancelledPayments memory range = CancelledPayments({
startIndex: countOfProcessed,
endIndex: newProcessedCount - 1
});

cancelledPaymentIndices.push(range);

emit PaymentsCancelledByAdmin(countOfProcessed, newProcessedCount - 1);
}

/// @notice Lets contract-owner send ERC721 NFTs to a list of addresses.
function airdrop(uint256 paymentsToProcess) external nonReentrant {
function processPayments(uint256 paymentsToProcess) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) {
uint256 totalPayees = payeeCount;
uint256 countOfProcessed = processedCount;

require(countOfProcessed + paymentsToProcess <= totalPayees, "invalid no. of payments");

processedCount += paymentsToProcess;

for (uint256 i = countOfProcessed; i < (countOfProcessed + paymentsToProcess); i += 1) {
for (uint256 i = countOfProcessed; i < (countOfProcessed + paymentsToProcess); ) {
AirdropContent memory content = airdropContent[i];

IERC1155(content.tokenAddress).safeTransferFrom(
content.tokenOwner,
content.recipient,
content.tokenId,
content.amount,
""
);

emit AirdropPayment(content.recipient, content);
bool failed;
try
IERC1155(content.tokenAddress).safeTransferFrom{ gas: 80_000 }(
content.tokenOwner,
content.recipient,
content.tokenId,
content.amount,
""
)
{} catch {
// revert if failure is due to unapproved tokens
require(
IERC1155(content.tokenAddress).balanceOf(content.tokenOwner, content.tokenId) >= content.amount &&
IERC1155(content.tokenAddress).isApprovedForAll(content.tokenOwner, address(this)),
"Not balance or approved"
);

// record and continue for all other failures, likely originating from recipient accounts
indicesOfFailed.push(i);
failed = true;
}

emit AirdropPayment(content.recipient, i, failed);

unchecked {
i += 1;
}
}
}

Expand All @@ -123,21 +166,38 @@ contract AirdropERC1155 is
* @dev The token-owner should approve target tokens to Airdrop contract,
* which acts as operator for the tokens.
*
* @param _tokenAddress Contract address of ERC1155 tokens to air-drop.
* @param _tokenOwner Address from which to transfer tokens.
* @param _contents List containing recipient, tokenId and amounts to airdrop.
*/
function airdrop(
address _tokenAddress,
address _tokenOwner,
AirdropContent[] calldata _contents
) external nonReentrant onlyOwner {
function airdrop(AirdropContent[] calldata _contents) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) {
uint256 len = _contents.length;

IERC1155 token = IERC1155(_tokenAddress);

for (uint256 i = 0; i < len; i++) {
token.safeTransferFrom(_tokenOwner, _contents[i].recipient, _contents[i].tokenId, _contents[i].amount, "");
for (uint256 i = 0; i < len; ) {
bool failed;
try
IERC1155(_contents[i].tokenAddress).safeTransferFrom(
_contents[i].tokenOwner,
_contents[i].recipient,
_contents[i].tokenId,
_contents[i].amount,
""
)
{} catch {
// revert if failure is due to unapproved tokens
require(
IERC1155(_contents[i].tokenAddress).balanceOf(_contents[i].tokenOwner, _contents[i].tokenId) >=
_contents[i].amount &&
IERC1155(_contents[i].tokenAddress).isApprovedForAll(_contents[i].tokenOwner, address(this)),
"Not balance or approved"
);

failed = true;
}

emit StatelessAirdrop(_contents[i].recipient, _contents[i], failed);

unchecked {
i += 1;
}
}
}

Expand All @@ -146,38 +206,45 @@ contract AirdropERC1155 is
//////////////////////////////////////////////////////////////*/

/// @notice Returns all airdrop payments set up -- pending, processed or failed.
function getAllAirdropPayments() external view returns (AirdropContent[] memory contents) {
uint256 count = payeeCount;
contents = new AirdropContent[](count);
function getAllAirdropPayments(uint256 startId, uint256 endId)
external
view
returns (AirdropContent[] memory contents)
{
require(startId <= endId && endId < payeeCount, "invalid range");

for (uint256 i = 0; i < count; i += 1) {
contents[i] = airdropContent[i];
contents = new AirdropContent[](endId - startId + 1);

for (uint256 i = startId; i <= endId; i += 1) {
contents[i - startId] = airdropContent[i];
}
}

/// @notice Returns all pending airdrop payments.
function getAllAirdropPaymentsPending() external view returns (AirdropContent[] memory contents) {
uint256 endCount = payeeCount;
uint256 startCount = processedCount;
contents = new AirdropContent[](endCount - startCount);
function getAllAirdropPaymentsPending(uint256 startId, uint256 endId)
external
view
returns (AirdropContent[] memory contents)
{
require(startId <= endId && endId < payeeCount, "invalid range");

uint256 processed = processedCount;
if (processed == payeeCount) {
return contents;
}

if (startId < processed) {
startId = processed;
}
contents = new AirdropContent[](endId - startId + 1);

uint256 idx;
for (uint256 i = startCount; i < endCount; i += 1) {
for (uint256 i = startId; i <= endId; i += 1) {
contents[idx] = airdropContent[i];
idx += 1;
}
}

/// @notice Returns all pending airdrop processed.
function getAllAirdropPaymentsProcessed() external view returns (AirdropContent[] memory contents) {
uint256 count = processedCount;
contents = new AirdropContent[](count);

for (uint256 i = 0; i < count; i += 1) {
contents[i] = airdropContent[i];
}
}

/// @notice Returns all pending airdrop failed.
function getAllAirdropPaymentsFailed() external view returns (AirdropContent[] memory contents) {
uint256 count = indicesOfFailed.length;
Expand All @@ -188,12 +255,17 @@ contract AirdropERC1155 is
}
}

/// @notice Returns all blocks of cancelled payments as an array of index range.
function getCancelledPaymentIndices() external view returns (CancelledPayments[] memory) {
return cancelledPaymentIndices;
}

/*///////////////////////////////////////////////////////////////
Miscellaneous
//////////////////////////////////////////////////////////////*/

/// @dev Returns whether owner can be set in the given execution context.
function _canSetOwner() internal view virtual override returns (bool) {
return msg.sender == owner();
return hasRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
}
Loading