@@ -44,10 +44,12 @@ contract AirdropERC1155 is
4444 uint256 public payeeCount;
4545 uint256 public processedCount;
4646
47- uint256 [] private indicesOfFailed;
47+ uint256 [] public indicesOfFailed;
4848
4949 mapping (uint256 => AirdropContent) private airdropContent;
5050
51+ CancelledPayments[] public cancelledPaymentIndices;
52+
5153 /*///////////////////////////////////////////////////////////////
5254 Constructor + initializer logic
5355 //////////////////////////////////////////////////////////////*/
@@ -80,41 +82,82 @@ contract AirdropERC1155 is
8082 //////////////////////////////////////////////////////////////*/
8183
8284 ///@notice Lets contract-owner set up an airdrop of ERC721 NFTs to a list of addresses.
83- function addAirdropRecipients (AirdropContent[] calldata _contents ) external onlyRole (DEFAULT_ADMIN_ROLE) {
85+ function addRecipients (AirdropContent[] calldata _contents ) external onlyRole (DEFAULT_ADMIN_ROLE) {
8486 uint256 len = _contents.length ;
8587 require (len > 0 , "No payees provided. " );
8688
8789 uint256 currentCount = payeeCount;
8890 payeeCount += len;
8991
90- for (uint256 i = currentCount; i < len; i += 1 ) {
91- airdropContent[i] = _contents[i];
92+ for (uint256 i = 0 ; i < len; ) {
93+ airdropContent[i + currentCount] = _contents[i];
94+
95+ unchecked {
96+ i += 1 ;
97+ }
9298 }
9399
94- emit RecipientsAdded (_contents);
100+ emit RecipientsAdded (currentCount, currentCount + len);
101+ }
102+
103+ ///@notice Lets contract-owner cancel any pending payments.
104+ function cancelPendingPayments (uint256 numberOfPaymentsToCancel ) external onlyRole (DEFAULT_ADMIN_ROLE) {
105+ uint256 countOfProcessed = processedCount;
106+
107+ // increase processedCount by the specified count -- all pending payments in between will be treated as cancelled.
108+ uint256 newProcessedCount = countOfProcessed + numberOfPaymentsToCancel;
109+ require (newProcessedCount <= payeeCount, "Exceeds total payees. " );
110+ processedCount = newProcessedCount;
111+
112+ CancelledPayments memory range = CancelledPayments ({
113+ startIndex: countOfProcessed,
114+ endIndex: newProcessedCount - 1
115+ });
116+
117+ cancelledPaymentIndices.push (range);
118+
119+ emit PaymentsCancelledByAdmin (countOfProcessed, newProcessedCount - 1 );
95120 }
96121
97122 /// @notice Lets contract-owner send ERC721 NFTs to a list of addresses.
98- function airdrop (uint256 paymentsToProcess ) external nonReentrant {
123+ function processPayments (uint256 paymentsToProcess ) external nonReentrant onlyRole (DEFAULT_ADMIN_ROLE) {
99124 uint256 totalPayees = payeeCount;
100125 uint256 countOfProcessed = processedCount;
101126
102127 require (countOfProcessed + paymentsToProcess <= totalPayees, "invalid no. of payments " );
103128
104129 processedCount += paymentsToProcess;
105130
106- for (uint256 i = countOfProcessed; i < (countOfProcessed + paymentsToProcess); i += 1 ) {
131+ for (uint256 i = countOfProcessed; i < (countOfProcessed + paymentsToProcess); ) {
107132 AirdropContent memory content = airdropContent[i];
108133
109- IERC1155 (content.tokenAddress).safeTransferFrom (
110- content.tokenOwner,
111- content.recipient,
112- content.tokenId,
113- content.amount,
114- ""
115- );
116-
117- emit AirdropPayment (content.recipient, content);
134+ bool failed;
135+ try
136+ IERC1155 (content.tokenAddress).safeTransferFrom { gas: 80_000 }(
137+ content.tokenOwner,
138+ content.recipient,
139+ content.tokenId,
140+ content.amount,
141+ ""
142+ )
143+ {} catch {
144+ // revert if failure is due to unapproved tokens
145+ require (
146+ IERC1155 (content.tokenAddress).balanceOf (content.tokenOwner, content.tokenId) >= content.amount &&
147+ IERC1155 (content.tokenAddress).isApprovedForAll (content.tokenOwner, address (this )),
148+ "Not balance or approved "
149+ );
150+
151+ // record and continue for all other failures, likely originating from recipient accounts
152+ indicesOfFailed.push (i);
153+ failed = true ;
154+ }
155+
156+ emit AirdropPayment (content.recipient, i, failed);
157+
158+ unchecked {
159+ i += 1 ;
160+ }
118161 }
119162 }
120163
@@ -123,21 +166,38 @@ contract AirdropERC1155 is
123166 * @dev The token-owner should approve target tokens to Airdrop contract,
124167 * which acts as operator for the tokens.
125168 *
126- * @param _tokenAddress Contract address of ERC1155 tokens to air-drop.
127- * @param _tokenOwner Address from which to transfer tokens.
128169 * @param _contents List containing recipient, tokenId and amounts to airdrop.
129170 */
130- function airdrop (
131- address _tokenAddress ,
132- address _tokenOwner ,
133- AirdropContent[] calldata _contents
134- ) external nonReentrant onlyOwner {
171+ function airdrop (AirdropContent[] calldata _contents ) external nonReentrant onlyRole (DEFAULT_ADMIN_ROLE) {
135172 uint256 len = _contents.length ;
136173
137- IERC1155 token = IERC1155 (_tokenAddress);
138-
139- for (uint256 i = 0 ; i < len; i++ ) {
140- token.safeTransferFrom (_tokenOwner, _contents[i].recipient, _contents[i].tokenId, _contents[i].amount, "" );
174+ for (uint256 i = 0 ; i < len; ) {
175+ bool failed;
176+ try
177+ IERC1155 (_contents[i].tokenAddress).safeTransferFrom (
178+ _contents[i].tokenOwner,
179+ _contents[i].recipient,
180+ _contents[i].tokenId,
181+ _contents[i].amount,
182+ ""
183+ )
184+ {} catch {
185+ // revert if failure is due to unapproved tokens
186+ require (
187+ IERC1155 (_contents[i].tokenAddress).balanceOf (_contents[i].tokenOwner, _contents[i].tokenId) >=
188+ _contents[i].amount &&
189+ IERC1155 (_contents[i].tokenAddress).isApprovedForAll (_contents[i].tokenOwner, address (this )),
190+ "Not balance or approved "
191+ );
192+
193+ failed = true ;
194+ }
195+
196+ emit StatelessAirdrop (_contents[i].recipient, _contents[i], failed);
197+
198+ unchecked {
199+ i += 1 ;
200+ }
141201 }
142202 }
143203
@@ -146,38 +206,45 @@ contract AirdropERC1155 is
146206 //////////////////////////////////////////////////////////////*/
147207
148208 /// @notice Returns all airdrop payments set up -- pending, processed or failed.
149- function getAllAirdropPayments () external view returns (AirdropContent[] memory contents ) {
150- uint256 count = payeeCount;
151- contents = new AirdropContent [](count);
209+ function getAllAirdropPayments (uint256 startId , uint256 endId )
210+ external
211+ view
212+ returns (AirdropContent[] memory contents )
213+ {
214+ require (startId <= endId && endId < payeeCount, "invalid range " );
152215
153- for (uint256 i = 0 ; i < count; i += 1 ) {
154- contents[i] = airdropContent[i];
216+ contents = new AirdropContent [](endId - startId + 1 );
217+
218+ for (uint256 i = startId; i <= endId; i += 1 ) {
219+ contents[i - startId] = airdropContent[i];
155220 }
156221 }
157222
158223 /// @notice Returns all pending airdrop payments.
159- function getAllAirdropPaymentsPending () external view returns (AirdropContent[] memory contents ) {
160- uint256 endCount = payeeCount;
161- uint256 startCount = processedCount;
162- contents = new AirdropContent [](endCount - startCount);
224+ function getAllAirdropPaymentsPending (uint256 startId , uint256 endId )
225+ external
226+ view
227+ returns (AirdropContent[] memory contents )
228+ {
229+ require (startId <= endId && endId < payeeCount, "invalid range " );
230+
231+ uint256 processed = processedCount;
232+ if (processed == payeeCount) {
233+ return contents;
234+ }
235+
236+ if (startId < processed) {
237+ startId = processed;
238+ }
239+ contents = new AirdropContent [](endId - startId + 1 );
163240
164241 uint256 idx;
165- for (uint256 i = startCount ; i < endCount ; i += 1 ) {
242+ for (uint256 i = startId ; i <= endId ; i += 1 ) {
166243 contents[idx] = airdropContent[i];
167244 idx += 1 ;
168245 }
169246 }
170247
171- /// @notice Returns all pending airdrop processed.
172- function getAllAirdropPaymentsProcessed () external view returns (AirdropContent[] memory contents ) {
173- uint256 count = processedCount;
174- contents = new AirdropContent [](count);
175-
176- for (uint256 i = 0 ; i < count; i += 1 ) {
177- contents[i] = airdropContent[i];
178- }
179- }
180-
181248 /// @notice Returns all pending airdrop failed.
182249 function getAllAirdropPaymentsFailed () external view returns (AirdropContent[] memory contents ) {
183250 uint256 count = indicesOfFailed.length ;
@@ -188,12 +255,17 @@ contract AirdropERC1155 is
188255 }
189256 }
190257
258+ /// @notice Returns all blocks of cancelled payments as an array of index range.
259+ function getCancelledPaymentIndices () external view returns (CancelledPayments[] memory ) {
260+ return cancelledPaymentIndices;
261+ }
262+
191263 /*///////////////////////////////////////////////////////////////
192264 Miscellaneous
193265 //////////////////////////////////////////////////////////////*/
194266
195267 /// @dev Returns whether owner can be set in the given execution context.
196268 function _canSetOwner () internal view virtual override returns (bool ) {
197- return msg .sender == owner ( );
269+ return hasRole (DEFAULT_ADMIN_ROLE, msg .sender );
198270 }
199271}
0 commit comments