Skip to content

ab1.1 #16072

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed

ab1.1 #16072

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
129 changes: 129 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,132 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import { ERC1155Holder } from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { BitMaps } from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { AccessControlDefaultAdminRules } from
"@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol";
import { PixelDungeonsItems } from "./PixelDungeonsItems.sol";

contract PixelDungeonsRewards is AccessControlDefaultAdminRules, ReentrancyGuard, ERC721Holder, ERC1155Holder {
using BitMaps for BitMaps.BitMap;
using SafeERC20 for IERC20;

bytes32 public constant SENDER_ROLE = keccak256("SENDER_ROLE");
bytes32 public constant WITHDRAW_ROLE = keccak256("WITHDRAW_ROLE");

enum Category {
ETHER,
ITEM,
ERC20,
ERC721,
ERC1155
}

struct Item {
Category category;
address receiver;
uint256 amount;
uint256 tokenId; // Used for Item, ERC721, ERC1155 rewards
address tokenAddress; // Used for ERC20, ERC721, ERC1155 rewards
}

struct Reward {
uint256 topic; // game, tournament, referral, etc.
uint256 id;
Item[] items;
}

address payable public recipient;
PixelDungeonsItems public immutable items;
mapping(uint256 => BitMaps.BitMap) private sentRewards;

event RewardSent(uint256 indexed topic, uint256 indexed id);

error InvalidRecipient(address recipient);

constructor(PixelDungeonsItems _items, address _recipient) AccessControlDefaultAdminRules(2 days, msg.sender) {
items = _items;
recipient = payable(_recipient);
}

function hasSentReward(uint256 _topic, uint256 _id) public view returns (bool) {
return sentRewards[_topic].get(_id);
}

function setRecipient(address payable _recipient) public onlyRole(DEFAULT_ADMIN_ROLE) {
if (_recipient == address(0)) {
revert InvalidRecipient(_recipient);
}

recipient = _recipient;
}

function sendRewards(Reward[] calldata _rewards) external virtual onlyRole(SENDER_ROLE) nonReentrant {
for (uint256 i = 0; i < _rewards.length; i++) {
Reward calldata reward = _rewards[i];

if (!hasSentReward(reward.topic, reward.id)) {
for (uint256 j = 0; j < reward.items.length; j++) {
Item calldata item = reward.items[j];

if (item.category == Category.ETHER) {
Address.sendValue(payable(item.receiver), item.amount);
} else if (item.category == Category.ITEM) {
items.mint(item.receiver, item.tokenId, item.amount, "");
} else if (item.category == Category.ERC20) {
IERC20(item.tokenAddress).safeTransfer(item.receiver, item.amount);
} else if (item.category == Category.ERC721) {
IERC721(item.tokenAddress).safeTransferFrom(address(this), item.receiver, item.tokenId);
} else if (item.category == Category.ERC1155) {
IERC1155(item.tokenAddress).safeTransferFrom(address(this), item.receiver, item.tokenId, item.amount, "");
}
}

_markRewardAsSent(reward.topic, reward.id);
}
}
}

function _markRewardAsSent(uint256 _topic, uint256 _id) internal {
sentRewards[_topic].set(_id);
emit RewardSent(_topic, _id);
}

function withdrawEther(uint256 _amount) external onlyRole(WITHDRAW_ROLE) {
Address.sendValue(recipient, _amount);
}

function withdrawERC20(IERC20 _token, uint256 _amount) external onlyRole(WITHDRAW_ROLE) {
_token.safeTransfer(recipient, _amount);
}

function withdrawERC721(IERC721 _token, uint256 _tokenId) external onlyRole(WITHDRAW_ROLE) {
_token.safeTransferFrom(address(this), recipient, _tokenId);
}

function withdrawERC1155(IERC1155 _token, uint256 _id, uint256 _amount) external onlyRole(WITHDRAW_ROLE) {
_token.safeTransferFrom(address(this), recipient, _id, _amount, "");
}

function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC1155Holder, AccessControlDefaultAdminRules)
returns (bool)
{
return super.supportsInterface(interfaceId);
}

receive() external payable { }
}
# The Solidity Contract-Oriented Programming Language

[![Matrix Chat](https://img.shields.io/badge/Matrix%20-chat-brightgreen?style=plastic&logo=matrix)](https://matrix.to/#/#ethereum_solidity:gitter.im)
Expand Down
123 changes: 123 additions & 0 deletions pixel dungeon
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import { AccessControlDefaultAdminRulesUpgradeable } from
"@openzeppelin/contracts-upgradeable/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol";
import { ERC1155SupplyUpgradeable } from
"@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155SupplyUpgradeable.sol";
import { ERC1155Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract PixelDungeonsItems is
Initializable,
ERC1155Upgradeable,
AccessControlDefaultAdminRulesUpgradeable,
ERC1155SupplyUpgradeable,
UUPSUpgradeable
{
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");

string public constant name = "Pixel Dungeons Items";
string public constant symbol = "PDI";

mapping(uint256 => bool) public soulboundTokens;
mapping(uint256 => uint256) public minimumPrices;

event SoulboundUpdated(uint256 id, bool soulbound);
event MinimumPriceUpdated(uint256 id, uint256 price);

error InvalidSoulboundTransfer(uint256 id);

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

function initialize() public initializer {
__ERC1155_init("https://api.pixeldungeons.xyz/items/{id}");
__AccessControlDefaultAdminRules_init(2 days, msg.sender);
__ERC1155Supply_init();
__UUPSUpgradeable_init();
}

function setMinimumPrice(uint256 _id, uint256 _price) external onlyRole(DEFAULT_ADMIN_ROLE) {
minimumPrices[_id] = _price;
emit MinimumPriceUpdated(_id, _price);
}

function setSoulbound(uint256 _id, bool _soulbound) external onlyRole(DEFAULT_ADMIN_ROLE) {
soulboundTokens[_id] = _soulbound;
emit SoulboundUpdated(_id, _soulbound);
}

function setTokenURI(string memory _newUri) external onlyRole(DEFAULT_ADMIN_ROLE) {
_setURI(_newUri);
}

function mint(address account, uint256 id, uint256 amount, bytes memory data) external onlyRole(MINTER_ROLE) {
_mint(account, id, amount, data);
}

function mintBatch(address[] memory to, uint256[][] memory ids, uint256[][] memory amounts, bytes memory data)
external
onlyRole(MINTER_ROLE)
{
for (uint256 i = 0; i < to.length; i++) {
_mintBatch(to[i], ids[i], amounts[i], data);
}
}

function burn(address account, uint256 id, uint256 amount) external onlyRole(BURNER_ROLE) {
_burn(account, id, amount);
}

function burnBatch(address[] memory accounts, uint256[][] memory ids, uint256[][] memory amounts)
external
onlyRole(BURNER_ROLE)
{
for (uint256 i = 0; i < accounts.length; i++) {
_burnBatch(accounts[i], ids[i], amounts[i]);
}
}

// Disable soul-bound token transfers
function _updateWithAcceptanceCheck(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) internal virtual override {
if (from != address(0) && to != address(0)) {
// Only check for transfers, not minting or burning
for (uint256 i = 0; i < ids.length; i++) {
if (soulboundTokens[ids[i]]) {
revert InvalidSoulboundTransfer(ids[i]);
}
}
}
super._updateWithAcceptanceCheck(from, to, ids, values, data);
}

function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) { }

// The following functions are overrides required by Solidity.
function _update(address from, address to, uint256[] memory ids, uint256[] memory values)
internal
override(ERC1155Upgradeable, ERC1155SupplyUpgradeable)
{
super._update(from, to, ids, values);
}

function supportsInterface(bytes4 interfaceId)
public
view
override(ERC1155Upgradeable, AccessControlDefaultAdminRulesUpgradeable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
129 changes: 129 additions & 0 deletions reaward
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import { ERC1155Holder } from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { BitMaps } from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { AccessControlDefaultAdminRules } from
"@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol";
import { PixelDungeonsItems } from "./PixelDungeonsItems.sol";

contract PixelDungeonsRewards is AccessControlDefaultAdminRules, ReentrancyGuard, ERC721Holder, ERC1155Holder {
using BitMaps for BitMaps.BitMap;
using SafeERC20 for IERC20;

bytes32 public constant SENDER_ROLE = keccak256("SENDER_ROLE");
bytes32 public constant WITHDRAW_ROLE = keccak256("WITHDRAW_ROLE");

enum Category {
ETHER,
ITEM,
ERC20,
ERC721,
ERC1155
}

struct Item {
Category category;
address receiver;
uint256 amount;
uint256 tokenId; // Used for Item, ERC721, ERC1155 rewards
address tokenAddress; // Used for ERC20, ERC721, ERC1155 rewards
}

struct Reward {
uint256 topic; // game, tournament, referral, etc.
uint256 id;
Item[] items;
}

address payable public recipient;
PixelDungeonsItems public immutable items;
mapping(uint256 => BitMaps.BitMap) private sentRewards;

event RewardSent(uint256 indexed topic, uint256 indexed id);

error InvalidRecipient(address recipient);

constructor(PixelDungeonsItems _items, address _recipient) AccessControlDefaultAdminRules(2 days, msg.sender) {
items = _items;
recipient = payable(_recipient);
}

function hasSentReward(uint256 _topic, uint256 _id) public view returns (bool) {
return sentRewards[_topic].get(_id);
}

function setRecipient(address payable _recipient) public onlyRole(DEFAULT_ADMIN_ROLE) {
if (_recipient == address(0)) {
revert InvalidRecipient(_recipient);
}

recipient = _recipient;
}

function sendRewards(Reward[] calldata _rewards) external virtual onlyRole(SENDER_ROLE) nonReentrant {
for (uint256 i = 0; i < _rewards.length; i++) {
Reward calldata reward = _rewards[i];

if (!hasSentReward(reward.topic, reward.id)) {
for (uint256 j = 0; j < reward.items.length; j++) {
Item calldata item = reward.items[j];

if (item.category == Category.ETHER) {
Address.sendValue(payable(item.receiver), item.amount);
} else if (item.category == Category.ITEM) {
items.mint(item.receiver, item.tokenId, item.amount, "");
} else if (item.category == Category.ERC20) {
IERC20(item.tokenAddress).safeTransfer(item.receiver, item.amount);
} else if (item.category == Category.ERC721) {
IERC721(item.tokenAddress).safeTransferFrom(address(this), item.receiver, item.tokenId);
} else if (item.category == Category.ERC1155) {
IERC1155(item.tokenAddress).safeTransferFrom(address(this), item.receiver, item.tokenId, item.amount, "");
}
}

_markRewardAsSent(reward.topic, reward.id);
}
}
}

function _markRewardAsSent(uint256 _topic, uint256 _id) internal {
sentRewards[_topic].set(_id);
emit RewardSent(_topic, _id);
}

function withdrawEther(uint256 _amount) external onlyRole(WITHDRAW_ROLE) {
Address.sendValue(recipient, _amount);
}

function withdrawERC20(IERC20 _token, uint256 _amount) external onlyRole(WITHDRAW_ROLE) {
_token.safeTransfer(recipient, _amount);
}

function withdrawERC721(IERC721 _token, uint256 _tokenId) external onlyRole(WITHDRAW_ROLE) {
_token.safeTransferFrom(address(this), recipient, _tokenId);
}

function withdrawERC1155(IERC1155 _token, uint256 _id, uint256 _amount) external onlyRole(WITHDRAW_ROLE) {
_token.safeTransferFrom(address(this), recipient, _id, _amount, "");
}

function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC1155Holder, AccessControlDefaultAdminRules)
returns (bool)
{
return super.supportsInterface(interfaceId);
}

receive() external payable { }
}