-
Notifications
You must be signed in to change notification settings - Fork 1
Feat: Add rewards like a semi-diamond proxy with a central storage #277
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,244 @@ | ||||||||||||
| // SPDX-License-Identifier: MIT | ||||||||||||
| pragma solidity ^0.8.28; | ||||||||||||
|
|
||||||||||||
| // @author Summon.xyz Team - https://summon.xyz | ||||||||||||
| // @contributors: [ @ogarciarevett, @karacurt] | ||||||||||||
|
|
||||||||||||
| import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; | ||||||||||||
| import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; | ||||||||||||
| import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; | ||||||||||||
| import { LibItems } from "../../libraries/LibItems.sol"; | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * @title RewardsState | ||||||||||||
| * @notice Centralized state storage for the Rewards system | ||||||||||||
| * @dev This contract holds all state that is shared between Rewards and Treasury contracts. | ||||||||||||
| * Only authorized contracts (with STATE_MANAGER_ROLE) can modify state. | ||||||||||||
| * This contract is upgradeable using the UUPS pattern. | ||||||||||||
| */ | ||||||||||||
| contract RewardsState is Initializable, AccessControlUpgradeable, UUPSUpgradeable { | ||||||||||||
|
|
||||||||||||
| /*////////////////////////////////////////////////////////////// | ||||||||||||
| ERRORS | ||||||||||||
| //////////////////////////////////////////////////////////////*/ | ||||||||||||
| error AddressIsZero(); | ||||||||||||
| error TokenAlreadyWhitelisted(); | ||||||||||||
| error TokenNotWhitelisted(); | ||||||||||||
|
|
||||||||||||
| /*////////////////////////////////////////////////////////////// | ||||||||||||
| CONSTANTS | ||||||||||||
| //////////////////////////////////////////////////////////////*/ | ||||||||||||
| bytes32 public constant STATE_MANAGER_ROLE = keccak256("STATE_MANAGER_ROLE"); | ||||||||||||
| bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); | ||||||||||||
|
|
||||||||||||
| /*////////////////////////////////////////////////////////////// | ||||||||||||
| STATE VARIABLES | ||||||||||||
| //////////////////////////////////////////////////////////////*/ | ||||||||||||
| // Treasury whitelist | ||||||||||||
| mapping(address => bool) public whitelistedTokens; | ||||||||||||
| address[] private whitelistedTokenList; | ||||||||||||
| mapping(address => LibItems.RewardType) public tokenTypes; | ||||||||||||
|
|
||||||||||||
| // ERC20 Reservations | ||||||||||||
| mapping(address => uint256) public reservedAmounts; | ||||||||||||
|
|
||||||||||||
| // ERC721 Reservations | ||||||||||||
| mapping(address => mapping(uint256 => bool)) public isErc721Reserved; | ||||||||||||
| mapping(address => uint256) public erc721TotalReserved; | ||||||||||||
|
|
||||||||||||
| // ERC1155 Reservations | ||||||||||||
| mapping(address => mapping(uint256 => uint256)) public erc1155ReservedAmounts; | ||||||||||||
| mapping(address => uint256) public erc1155TotalReserved; | ||||||||||||
|
|
||||||||||||
| // Reward Token Management | ||||||||||||
| uint256[] public itemIds; | ||||||||||||
| mapping(uint256 => bool) public tokenExists; | ||||||||||||
| mapping(uint256 => LibItems.RewardToken) public tokenRewards; | ||||||||||||
| mapping(uint256 => bool) public isTokenMintPaused; | ||||||||||||
| mapping(uint256 => bool) public isClaimRewardPaused; | ||||||||||||
| mapping(uint256 => mapping(uint256 => uint256)) public erc721RewardCurrentIndex; // rewardTokenId => rewardIndex => erc721RewardCurrentIndex | ||||||||||||
| mapping(uint256 => uint256) public currentRewardSupply; | ||||||||||||
|
|
||||||||||||
| // Per-user nonce tracking | ||||||||||||
| mapping(address => mapping(uint256 => bool)) public userNonces; | ||||||||||||
|
|
||||||||||||
| uint256[50] private __gap; | ||||||||||||
|
|
||||||||||||
| /*////////////////////////////////////////////////////////////// | ||||||||||||
| EVENTS | ||||||||||||
| //////////////////////////////////////////////////////////////*/ | ||||||||||||
| event TokenWhitelisted(address indexed token, LibItems.RewardType tokenType); | ||||||||||||
| event TokenRemovedFromWhitelist(address indexed token); | ||||||||||||
| event RewardTokenAdded(uint256 indexed tokenId); | ||||||||||||
| event RewardTokenUpdated(uint256 indexed tokenId); | ||||||||||||
| event TokenMintPausedUpdated(uint256 indexed tokenId, bool isPaused); | ||||||||||||
| event ClaimRewardPausedUpdated(uint256 indexed tokenId, bool isPaused); | ||||||||||||
| event UserNonceUsed(address indexed user, uint256 indexed nonce); | ||||||||||||
|
|
||||||||||||
| /*////////////////////////////////////////////////////////////// | ||||||||||||
| INITIALIZER | ||||||||||||
| //////////////////////////////////////////////////////////////*/ | ||||||||||||
| /// @custom:oz-upgrades-unsafe-allow constructor | ||||||||||||
| constructor() { | ||||||||||||
| _disableInitializers(); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| function initialize(address _admin) external initializer { | ||||||||||||
| if (_admin == address(0)) revert AddressIsZero(); | ||||||||||||
|
|
||||||||||||
| __AccessControl_init(); | ||||||||||||
|
|
||||||||||||
| _grantRole(DEFAULT_ADMIN_ROLE, _admin); | ||||||||||||
| _grantRole(UPGRADER_ROLE, _admin); | ||||||||||||
|
||||||||||||
| _grantRole(UPGRADER_ROLE, _admin); | |
| _grantRole(UPGRADER_ROLE, _admin); | |
| _grantRole(STATE_MANAGER_ROLE, _admin); |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The decreaseERC20Reserved function should revert if the amount to decrease exceeds the current reserved amount, rather than silently capping the decrease. This could mask accounting errors and lead to incorrect reserve tracking. Consider adding a require statement or reverting when reservedAmounts[_token] < _amount.
| if (reservedAmounts[_token] >= _amount) { | |
| reservedAmounts[_token] -= _amount; | |
| } | |
| require(reservedAmounts[_token] >= _amount, "RewardsState: insufficient reserved"); | |
| reservedAmounts[_token] -= _amount; |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The incrementERC721RewardIndex function only increments the index by 1, but in Rewards.sol line 790, a loop distributes reward.rewardAmount NFTs (lines 790-805). The index should be incremented by reward.rewardAmount, not by 1. This will cause the index to be out of sync with the actual number of distributed NFTs, leading to attempts to redistribute the same NFTs on subsequent claims.
| function incrementERC721RewardIndex(uint256 _rewardTokenId, uint256 _rewardIndex) external onlyRole(STATE_MANAGER_ROLE) { | |
| erc721RewardCurrentIndex[_rewardTokenId][_rewardIndex]++; | |
| function incrementERC721RewardIndex(uint256 _rewardTokenId, uint256 _rewardIndex, uint256 _amount) external onlyRole(STATE_MANAGER_ROLE) { | |
| erc721RewardCurrentIndex[_rewardTokenId][_rewardIndex] += _amount; |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no test files for the new RewardsState.sol and Treasury.sol contracts. The existing test files (rewardsSoulbound.test.ts, rewardsNftTreasury.test.ts) test the old monolithic Rewards contract architecture and will fail with the new separated architecture. Tests need to be added or updated to cover the new contracts and their interactions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The RewardsState and Treasury contracts use pragma solidity ^0.8.28, while LibItems.sol uses ^0.8.24. This version mismatch could cause compilation issues or unexpected behavior. All contracts in the same project should use compatible Solidity versions. Consider updating LibItems.sol to ^0.8.28 or ensure the version range is explicitly compatible.