|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +pragma solidity ^0.8.19; |
| 3 | + |
| 4 | +import { IERC1155 } from "openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol"; |
| 5 | +import { IERC1155Receiver, IERC165 } from "openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol"; |
| 6 | + |
| 7 | +/** |
| 8 | + * An ERC-1155 contract that allows permissive minting. |
| 9 | + */ |
| 10 | +contract ERC1155Holder is IERC1155Receiver { |
| 11 | + |
| 12 | + /// @dev Emitted when a claim is added. |
| 13 | + event ClaimAdded(address claimant, address tokenAddress, uint256 tokenId, uint256 amount); |
| 14 | + /// @dev Emitted when a batch of claims is added. |
| 15 | + event ClaimAddedBatch(address claimant, address tokenAddress, uint256[] tokenIds, uint256[] amounts); |
| 16 | + |
| 17 | + /// @dev Emitted when a claim is claimed. |
| 18 | + event Claimed(address claimant, address tokenAddress, uint256 tokenId, uint256 amount); |
| 19 | + /// @dev Emitted when a batch of claims is claimed. |
| 20 | + event ClaimedBatch(address claimant, address tokenAddress, uint256[] tokenIds, uint256[] amounts); |
| 21 | + |
| 22 | + /// @dev Error thrown when the claimant is invalid. |
| 23 | + error InvalidClaimant(); |
| 24 | + |
| 25 | + /// @dev claimant -> tokenAddress -> tokenId -> amount |
| 26 | + mapping(address => mapping(address => mapping(uint256 => uint256))) public claims; |
| 27 | + |
| 28 | + /// @dev Claims a token. |
| 29 | + /// @param claimant The claimant. |
| 30 | + /// @param tokenAddress The token address. |
| 31 | + /// @param tokenId The token id. |
| 32 | + function claim(address claimant, address tokenAddress, uint256 tokenId) public { |
| 33 | + uint256 amount = claims[claimant][tokenAddress][tokenId]; |
| 34 | + delete claims[claimant][tokenAddress][tokenId]; |
| 35 | + emit Claimed(claimant, tokenAddress, tokenId, amount); |
| 36 | + IERC1155(tokenAddress).safeTransferFrom(address(this), claimant, tokenId, amount, ""); |
| 37 | + } |
| 38 | + |
| 39 | + /// @dev Claims a batch of tokens. |
| 40 | + /// @param claimant The claimant. |
| 41 | + /// @param tokenAddress The token address. |
| 42 | + /// @param tokenIds The token ids. |
| 43 | + function claimBatch(address claimant, address tokenAddress, uint256[] memory tokenIds) public { |
| 44 | + uint256[] memory amounts = new uint256[](tokenIds.length); |
| 45 | + for (uint256 i = 0; i < tokenIds.length; i++) { |
| 46 | + amounts[i] = claims[claimant][tokenAddress][tokenIds[i]]; |
| 47 | + delete claims[claimant][tokenAddress][tokenIds[i]]; |
| 48 | + } |
| 49 | + emit ClaimedBatch(claimant, tokenAddress, tokenIds, amounts); |
| 50 | + IERC1155(tokenAddress).safeBatchTransferFrom(address(this), claimant, tokenIds, amounts, ""); |
| 51 | + } |
| 52 | + |
| 53 | + /// @inheritdoc IERC1155Receiver |
| 54 | + /// @param claimData The encoded claimant. |
| 55 | + function onERC1155Received( |
| 56 | + address, |
| 57 | + address, |
| 58 | + uint256 tokenId, |
| 59 | + uint256 amount, |
| 60 | + bytes calldata claimData |
| 61 | + ) public virtual override returns (bytes4) { |
| 62 | + address claimant = _decodeClaimant(claimData); |
| 63 | + address tokenAddress = msg.sender; |
| 64 | + claims[claimant][tokenAddress][tokenId] += amount; |
| 65 | + emit ClaimAdded(claimant, tokenAddress, tokenId, amount); |
| 66 | + return this.onERC1155Received.selector; |
| 67 | + } |
| 68 | + |
| 69 | + /// @inheritdoc IERC1155Receiver |
| 70 | + /// @param claimData The encoded claimant. |
| 71 | + function onERC1155BatchReceived( |
| 72 | + address, |
| 73 | + address, |
| 74 | + uint256[] calldata tokenIds, |
| 75 | + uint256[] calldata amounts, |
| 76 | + bytes calldata claimData |
| 77 | + ) public virtual override returns (bytes4) { |
| 78 | + address claimant = _decodeClaimant(claimData); |
| 79 | + address tokenAddress = msg.sender; |
| 80 | + for (uint256 i = 0; i < tokenIds.length; i++) { |
| 81 | + claims[claimant][tokenAddress][tokenIds[i]] += amounts[i]; |
| 82 | + } |
| 83 | + emit ClaimAddedBatch(claimant, tokenAddress, tokenIds, amounts); |
| 84 | + return this.onERC1155BatchReceived.selector; |
| 85 | + } |
| 86 | + |
| 87 | + /// @dev Decodes the claimant from the claim data. |
| 88 | + function _decodeClaimant( |
| 89 | + bytes calldata claimData |
| 90 | + ) internal pure returns (address claimant) { |
| 91 | + if (claimData.length == 20) { |
| 92 | + // Packed address format |
| 93 | + assembly { |
| 94 | + calldatacopy(0, claimData.offset, 20) |
| 95 | + claimant := shr(96, mload(0)) |
| 96 | + } |
| 97 | + } else if (claimData.length == 32) { |
| 98 | + // ABI encoded address format |
| 99 | + (claimant) = abi.decode(claimData, (address)); |
| 100 | + } |
| 101 | + if (claimant == address(0)) { |
| 102 | + revert InvalidClaimant(); |
| 103 | + } |
| 104 | + return claimant; |
| 105 | + } |
| 106 | + |
| 107 | + /// @inheritdoc IERC165 |
| 108 | + function supportsInterface( |
| 109 | + bytes4 interfaceId |
| 110 | + ) public view virtual override(IERC165) returns (bool) { |
| 111 | + return interfaceId == type(IERC1155Receiver).interfaceId || interfaceId == type(IERC165).interfaceId; |
| 112 | + } |
| 113 | + |
| 114 | +} |
0 commit comments