Skip to content

Commit

Permalink
Disallow multiple claims per userId (#1)
Browse files Browse the repository at this point in the history
* Add userId check to claim and burn

* Update tests

* Update docs

* Deploy to Mumbai
  • Loading branch information
TomiOhl authored Jul 4, 2023
1 parent 6c4a2f4 commit 8f2e876
Show file tree
Hide file tree
Showing 6 changed files with 494 additions and 42 deletions.
312 changes: 312 additions & 0 deletions .openzeppelin/polygon-mumbai.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,318 @@
}
}
}
},
"725b519bbbff1e4a7e4410f8395fb446470536783d5ee706e3298b921ad035c8": {
"address": "0x172c455e7B644701E37abb4c30849cF8e2e1FFE5",
"txHash": "0xd80429bf371670b42f31410e15d8e61f009f78519543b2f09308a52c21fdc52f",
"layout": {
"solcVersion": "0.8.19",
"storage": [
{
"label": "_initialized",
"offset": 0,
"slot": "0",
"type": "t_uint8",
"contract": "Initializable",
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63",
"retypedFrom": "bool"
},
{
"label": "_initializing",
"offset": 1,
"slot": "0",
"type": "t_bool",
"contract": "Initializable",
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68"
},
{
"label": "__gap",
"offset": 0,
"slot": "1",
"type": "t_array(t_uint256)50_storage",
"contract": "ERC1967UpgradeUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169"
},
{
"label": "__gap",
"offset": 0,
"slot": "51",
"type": "t_array(t_uint256)50_storage",
"contract": "ContextUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36"
},
{
"label": "_owner",
"offset": 0,
"slot": "101",
"type": "t_address",
"contract": "OwnableUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22"
},
{
"label": "__gap",
"offset": 0,
"slot": "102",
"type": "t_array(t_uint256)49_storage",
"contract": "OwnableUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94"
},
{
"label": "__gap",
"offset": 0,
"slot": "151",
"type": "t_array(t_uint256)50_storage",
"contract": "UUPSUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111"
},
{
"label": "__gap",
"offset": 0,
"slot": "201",
"type": "t_array(t_uint256)50_storage",
"contract": "ERC165Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41"
},
{
"label": "_name",
"offset": 0,
"slot": "251",
"type": "t_string_storage",
"contract": "ERC721Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:25"
},
{
"label": "_symbol",
"offset": 0,
"slot": "252",
"type": "t_string_storage",
"contract": "ERC721Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:28"
},
{
"label": "_owners",
"offset": 0,
"slot": "253",
"type": "t_mapping(t_uint256,t_address)",
"contract": "ERC721Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:31"
},
{
"label": "_balances",
"offset": 0,
"slot": "254",
"type": "t_mapping(t_address,t_uint256)",
"contract": "ERC721Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:34"
},
{
"label": "_tokenApprovals",
"offset": 0,
"slot": "255",
"type": "t_mapping(t_uint256,t_address)",
"contract": "ERC721Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:37"
},
{
"label": "_operatorApprovals",
"offset": 0,
"slot": "256",
"type": "t_mapping(t_address,t_mapping(t_address,t_bool))",
"contract": "ERC721Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:40"
},
{
"label": "__gap",
"offset": 0,
"slot": "257",
"type": "t_array(t_uint256)44_storage",
"contract": "ERC721Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:477"
},
{
"label": "_ownedTokens",
"offset": 0,
"slot": "301",
"type": "t_mapping(t_address,t_mapping(t_uint256,t_uint256))",
"contract": "ERC721EnumerableUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol:22"
},
{
"label": "_ownedTokensIndex",
"offset": 0,
"slot": "302",
"type": "t_mapping(t_uint256,t_uint256)",
"contract": "ERC721EnumerableUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol:25"
},
{
"label": "_allTokens",
"offset": 0,
"slot": "303",
"type": "t_array(t_uint256)dyn_storage",
"contract": "ERC721EnumerableUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol:28"
},
{
"label": "_allTokensIndex",
"offset": 0,
"slot": "304",
"type": "t_mapping(t_uint256,t_uint256)",
"contract": "ERC721EnumerableUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol:31"
},
{
"label": "__gap",
"offset": 0,
"slot": "305",
"type": "t_array(t_uint256)46_storage",
"contract": "ERC721EnumerableUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol:171"
},
{
"label": "__gap",
"offset": 0,
"slot": "351",
"type": "t_array(t_uint256)50_storage",
"contract": "SoulboundERC721",
"src": "contracts/token/SoulboundERC721.sol:17"
},
{
"label": "treasury",
"offset": 0,
"slot": "401",
"type": "t_address_payable",
"contract": "TreasuryManager",
"src": "contracts/utils/TreasuryManager.sol:10"
},
{
"label": "fee",
"offset": 0,
"slot": "402",
"type": "t_mapping(t_address,t_uint256)",
"contract": "TreasuryManager",
"src": "contracts/utils/TreasuryManager.sol:12"
},
{
"label": "__gap",
"offset": 0,
"slot": "403",
"type": "t_array(t_uint256)48_storage",
"contract": "TreasuryManager",
"src": "contracts/utils/TreasuryManager.sol:15"
},
{
"label": "validSigner",
"offset": 0,
"slot": "451",
"type": "t_address",
"contract": "GuildRewardNFT",
"src": "contracts/GuildRewardNFT.sol:26"
},
{
"label": "cid",
"offset": 0,
"slot": "452",
"type": "t_string_storage",
"contract": "GuildRewardNFT",
"src": "contracts/GuildRewardNFT.sol:29"
},
{
"label": "claimedTokens",
"offset": 0,
"slot": "453",
"type": "t_mapping(t_uint256,t_uint256)",
"contract": "GuildRewardNFT",
"src": "contracts/GuildRewardNFT.sol:31"
},
{
"label": "__gap",
"offset": 0,
"slot": "454",
"type": "t_array(t_uint256)47_storage",
"contract": "GuildRewardNFT",
"src": "contracts/GuildRewardNFT.sol:34"
}
],
"types": {
"t_address": {
"label": "address",
"numberOfBytes": "20"
},
"t_address_payable": {
"label": "address payable",
"numberOfBytes": "20"
},
"t_array(t_uint256)44_storage": {
"label": "uint256[44]",
"numberOfBytes": "1408"
},
"t_array(t_uint256)46_storage": {
"label": "uint256[46]",
"numberOfBytes": "1472"
},
"t_array(t_uint256)47_storage": {
"label": "uint256[47]",
"numberOfBytes": "1504"
},
"t_array(t_uint256)48_storage": {
"label": "uint256[48]",
"numberOfBytes": "1536"
},
"t_array(t_uint256)49_storage": {
"label": "uint256[49]",
"numberOfBytes": "1568"
},
"t_array(t_uint256)50_storage": {
"label": "uint256[50]",
"numberOfBytes": "1600"
},
"t_array(t_uint256)dyn_storage": {
"label": "uint256[]",
"numberOfBytes": "32"
},
"t_bool": {
"label": "bool",
"numberOfBytes": "1"
},
"t_mapping(t_address,t_bool)": {
"label": "mapping(address => bool)",
"numberOfBytes": "32"
},
"t_mapping(t_address,t_mapping(t_address,t_bool))": {
"label": "mapping(address => mapping(address => bool))",
"numberOfBytes": "32"
},
"t_mapping(t_address,t_mapping(t_uint256,t_uint256))": {
"label": "mapping(address => mapping(uint256 => uint256))",
"numberOfBytes": "32"
},
"t_mapping(t_address,t_uint256)": {
"label": "mapping(address => uint256)",
"numberOfBytes": "32"
},
"t_mapping(t_uint256,t_address)": {
"label": "mapping(uint256 => address)",
"numberOfBytes": "32"
},
"t_mapping(t_uint256,t_uint256)": {
"label": "mapping(uint256 => uint256)",
"numberOfBytes": "32"
},
"t_string_storage": {
"label": "string",
"numberOfBytes": "32"
},
"t_uint256": {
"label": "uint256",
"numberOfBytes": "32"
},
"t_uint8": {
"label": "uint8",
"numberOfBytes": "1"
}
}
}
}
}
}
27 changes: 20 additions & 7 deletions contracts/GuildRewardNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ contract GuildRewardNFT is
/// @notice The cid for tokenURI.
string internal cid;

mapping(uint256 userId => uint256 claimed) internal claimedTokens;

/// @notice Empty space reserved for future updates.
uint256[48] private __gap;
uint256[47] private __gap;

/// @notice Sets metadata and the associated addresses.
/// @param name The name of the token.
Expand All @@ -52,15 +54,17 @@ contract GuildRewardNFT is
__TreasuryManager_init(treasury);
}

function claim(address payToken, address receiver, bytes calldata signature) external payable {
if (balanceOf(receiver) > 0) revert AlreadyClaimed();
if (!isValidSignature(receiver, signature)) revert IncorrectSignature();
function claim(address payToken, address receiver, uint256 userId, bytes calldata signature) external payable {
if (balanceOf(receiver) > 0 || claimedTokens[userId] > 0) revert AlreadyClaimed();
if (!isValidSignature(receiver, userId, signature)) revert IncorrectSignature();

uint256 tokenId = totalSupply();

uint256 fee = fee[payToken];
if (fee == 0) revert IncorrectPayToken(payToken);

claimedTokens[userId]++;

// Fee collection
// When there is no msg.value, try transferring ERC20
// When there is msg.value, ensure it's the correct amount
Expand All @@ -73,8 +77,12 @@ contract GuildRewardNFT is
emit Claimed(receiver, tokenId);
}

function burn(uint256 tokenId) external {
function burn(uint256 tokenId, uint256 userId, bytes calldata signature) external {
if (msg.sender != ownerOf(tokenId)) revert IncorrectSender();
if (!isValidSignature(msg.sender, userId, signature)) revert IncorrectSignature();

claimedTokens[userId]--;

_burn(tokenId);
}

Expand All @@ -92,6 +100,10 @@ contract GuildRewardNFT is
return balanceOf(account) > 0;
}

function hasTheUserIdClaimed(uint256 userId) external view returns (bool claimed) {
return claimedTokens[userId] > 0;
}

function tokenURI(uint256 tokenId) public view override returns (string memory) {
if (!_exists(tokenId)) revert NonExistentToken(tokenId);

Expand All @@ -102,9 +114,10 @@ contract GuildRewardNFT is
function _authorizeUpgrade(address) internal override onlyOwner {}

/// @notice Checks the validity of the signature for the given params.
function isValidSignature(address receiver, bytes calldata signature) internal view returns (bool) {
function isValidSignature(address receiver, uint256 userId, bytes calldata signature) internal view returns (bool) {
if (signature.length != 65) revert IncorrectSignature();
bytes32 message = keccak256(abi.encode(receiver, block.chainid, address(this))).toEthSignedMessageHash();
bytes32 message = keccak256(abi.encode(receiver, userId, block.chainid, address(this)))
.toEthSignedMessageHash();
return message.recover(signature) == validSigner;
}
}
Loading

0 comments on commit 8f2e876

Please sign in to comment.