Skip to content

Commit a6c89a5

Browse files
authored
Stateless airdrops (#432)
* stateless airdrops * update struct for airdrop-erc20 * update struct for airdrop-erc721 * update struct for airdrop-erc1155 * gasreport * emit when failed * docs * v3.6.1-4 * fix * gasreport
1 parent 11f6726 commit a6c89a5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+4980
-3148
lines changed

contracts/airdrop/AirdropERC1155.sol

+18-170
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,10 @@ import "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol";
2323
import "../interfaces/airdrop/IAirdropERC1155.sol";
2424

2525
// ========== Features ==========
26-
import "../extension/Ownable.sol";
2726
import "../extension/PermissionsEnumerable.sol";
2827

2928
contract AirdropERC1155 is
3029
Initializable,
31-
Ownable,
3230
PermissionsEnumerable,
3331
ReentrancyGuardUpgradeable,
3432
MulticallUpgradeable,
@@ -41,15 +39,6 @@ contract AirdropERC1155 is
4139
bytes32 private constant MODULE_TYPE = bytes32("AirdropERC1155");
4240
uint256 private constant VERSION = 1;
4341

44-
uint256 public payeeCount;
45-
uint256 public processedCount;
46-
47-
uint256[] public indicesOfFailed;
48-
49-
mapping(uint256 => AirdropContent) private airdropContent;
50-
51-
CancelledPayments[] public cancelledPaymentIndices;
52-
5342
/*///////////////////////////////////////////////////////////////
5443
Constructor + initializer logic
5544
//////////////////////////////////////////////////////////////*/
@@ -59,7 +48,6 @@ contract AirdropERC1155 is
5948
/// @dev Initiliazes the contract, like a constructor.
6049
function initialize(address _defaultAdmin) external initializer {
6150
_setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
62-
_setupOwner(_defaultAdmin);
6351
__ReentrancyGuard_init();
6452
}
6553

@@ -81,101 +69,26 @@ contract AirdropERC1155 is
8169
Airdrop logic
8270
//////////////////////////////////////////////////////////////*/
8371

84-
///@notice Lets contract-owner set up an airdrop of ERC721 NFTs to a list of addresses.
85-
function addRecipients(AirdropContent[] calldata _contents) external onlyRole(DEFAULT_ADMIN_ROLE) {
86-
uint256 len = _contents.length;
87-
require(len > 0, "No payees provided.");
88-
89-
uint256 currentCount = payeeCount;
90-
payeeCount += len;
91-
92-
for (uint256 i = 0; i < len; ) {
93-
airdropContent[i + currentCount] = _contents[i];
94-
95-
unchecked {
96-
i += 1;
97-
}
98-
}
99-
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);
120-
}
121-
122-
/// @notice Lets contract-owner send ERC721 NFTs to a list of addresses.
123-
function processPayments(uint256 paymentsToProcess) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) {
124-
uint256 totalPayees = payeeCount;
125-
uint256 countOfProcessed = processedCount;
126-
127-
require(countOfProcessed + paymentsToProcess <= totalPayees, "invalid no. of payments");
128-
129-
processedCount += paymentsToProcess;
130-
131-
for (uint256 i = countOfProcessed; i < (countOfProcessed + paymentsToProcess); ) {
132-
AirdropContent memory content = airdropContent[i];
133-
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-
}
161-
}
162-
}
163-
16472
/**
16573
* @notice Lets contract-owner send ERC1155 tokens to a list of addresses.
16674
* @dev The token-owner should approve target tokens to Airdrop contract,
16775
* which acts as operator for the tokens.
16876
*
77+
* @param _tokenAddress The contract address of the tokens to transfer.
78+
* @param _tokenOwner The owner of the the tokens to transfer.
16979
* @param _contents List containing recipient, tokenId and amounts to airdrop.
17080
*/
171-
function airdrop(AirdropContent[] calldata _contents) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) {
81+
function airdrop(
82+
address _tokenAddress,
83+
address _tokenOwner,
84+
AirdropContent[] calldata _contents
85+
) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) {
17286
uint256 len = _contents.length;
17387

17488
for (uint256 i = 0; i < len; ) {
175-
bool failed;
17689
try
177-
IERC1155(_contents[i].tokenAddress).safeTransferFrom(
178-
_contents[i].tokenOwner,
90+
IERC1155(_tokenAddress).safeTransferFrom{ gas: 80_000 }(
91+
_tokenOwner,
17992
_contents[i].recipient,
18093
_contents[i].tokenId,
18194
_contents[i].amount,
@@ -184,88 +97,23 @@ contract AirdropERC1155 is
18497
{} catch {
18598
// revert if failure is due to unapproved tokens
18699
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)),
100+
IERC1155(_tokenAddress).balanceOf(_tokenOwner, _contents[i].tokenId) >= _contents[i].amount &&
101+
IERC1155(_tokenAddress).isApprovedForAll(_tokenOwner, address(this)),
190102
"Not balance or approved"
191103
);
192104

193-
failed = true;
105+
emit AirdropFailed(
106+
_tokenAddress,
107+
_tokenOwner,
108+
_contents[i].recipient,
109+
_contents[i].tokenId,
110+
_contents[i].amount
111+
);
194112
}
195113

196-
emit StatelessAirdrop(_contents[i].recipient, _contents[i], failed);
197-
198114
unchecked {
199115
i += 1;
200116
}
201117
}
202118
}
203-
204-
/*///////////////////////////////////////////////////////////////
205-
Airdrop view logic
206-
//////////////////////////////////////////////////////////////*/
207-
208-
/// @notice Returns all airdrop payments set up -- pending, processed or failed.
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");
215-
216-
contents = new AirdropContent[](endId - startId + 1);
217-
218-
for (uint256 i = startId; i <= endId; i += 1) {
219-
contents[i - startId] = airdropContent[i];
220-
}
221-
}
222-
223-
/// @notice Returns all pending airdrop payments.
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);
240-
241-
uint256 idx;
242-
for (uint256 i = startId; i <= endId; i += 1) {
243-
contents[idx] = airdropContent[i];
244-
idx += 1;
245-
}
246-
}
247-
248-
/// @notice Returns all pending airdrop failed.
249-
function getAllAirdropPaymentsFailed() external view returns (AirdropContent[] memory contents) {
250-
uint256 count = indicesOfFailed.length;
251-
contents = new AirdropContent[](count);
252-
253-
for (uint256 i = 0; i < count; i += 1) {
254-
contents[i] = airdropContent[indicesOfFailed[i]];
255-
}
256-
}
257-
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-
263-
/*///////////////////////////////////////////////////////////////
264-
Miscellaneous
265-
//////////////////////////////////////////////////////////////*/
266-
267-
/// @dev Returns whether owner can be set in the given execution context.
268-
function _canSetOwner() internal view virtual override returns (bool) {
269-
return hasRole(DEFAULT_ADMIN_ROLE, msg.sender);
270-
}
271119
}

0 commit comments

Comments
 (0)