From 9b09d8a9653206997c8693d1ea10149da1829236 Mon Sep 17 00:00:00 2001 From: Max Vasin Limsukhawat Date: Mon, 29 Apr 2024 23:42:00 -0600 Subject: [PATCH 1/6] reuse achievo soulbound contract and whitelist sig --- contracts/games/GameSummary.sol | 675 ++++++++++++++++++-------------- 1 file changed, 389 insertions(+), 286 deletions(-) diff --git a/contracts/games/GameSummary.sol b/contracts/games/GameSummary.sol index a28aac72..3c228581 100644 --- a/contracts/games/GameSummary.sol +++ b/contracts/games/GameSummary.sol @@ -1,6 +1,11 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; +/** + * Author: Max vasinl124(https://github.com/vasinl124) + * Co-Authors: Omar ogarciarevett(https://github.com/ogarciarevett) + */ + // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM // MWXd,. .cONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM // Wx' .cKMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM @@ -16,315 +21,304 @@ pragma solidity ^0.8.17; // MMNx'.dWMMK;.:0WMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM // MMMM0cdNMM0cdNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM -/** - * Authors: Omar Garcia - * GitHub: https://github.com/ogarciarevett - */ - -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import "@openzeppelin/contracts/access/AccessControl.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/security/Pausable.sol"; -import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; +import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import { ERC1155Burnable } from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; +import { ERC1155Supply } from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; +import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; +import { Pausable } from "@openzeppelin/contracts/security/Pausable.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +import { Achievo1155Soulbound } from "../ercs/extensions/Achievo1155Soulbound.sol"; +import { ERCWhitelistSignature } from "../ercs/ERCWhitelistSignature.sol"; +import { LibItems } from "../libraries/LibItems.sol"; + +error AddressIsZero(); +error InvalidInput(); + +contract GameSummary is + ERC1155Burnable, + ERC1155Supply, + Achievo1155Soulbound, + ERCWhitelistSignature, + AccessControl, + Pausable, + ReentrancyGuard, + Initializable +{ + event BaseURIChanged(string indexed uri); + event ContractURIChanged(string indexed uri); + event CompoundURIChanged(string indexed uri); + event CompoundURIEnabledChanged(bool enabled); -// This contract contains only the phase 1 of the GameSummaries contract -contract GameSummary is ERC1155, AccessControl, Pausable, ReentrancyGuard { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 public constant GAME_CREATOR_ROLE = keccak256("GAME_CREATOR_ROLE"); + bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); + bytes32 public constant DEV_CONFIG_ROLE = keccak256("DEV_CONFIG_ROLE"); - event GameSummaryUpdated(address indexed indexer, uint256 indexed tokenId); - event GameSummaryMinted(address indexed player, uint256 indexed gameId, uint256 totalAchievements); - event PlayerGameSummaryMinted(address indexed player, uint256 indexed gameId, uint256 achievements); - event PlayerGameSummaryUpdated(address indexed player, uint256 indexed gameId, uint256 achievements); - event SignerAdded(address signer); - event SignerRemoved(address signer); - event GameSummaryMintedPaused(bool paused); + string public contractURI; + string private baseURI; + string public compoundURI; - string public baseUri; + bool public compoundURIEnabled; - struct GameSummary { - uint256 tokenId; - uint256 storeId; - uint256 gameId; - string name; - string image; - string externalURI; - uint256 totalAchievements; - } + string public name; + string public symbol; + using Strings for uint256; - struct PlayerGameData { - uint256 tokenId; - uint256 achievementsMinted; - bool soulbounded; - } + uint256 public MAX_PER_MINT; - // tokenId(storeId+0+gameId) => common game data - mapping(uint256 => GameSummary) public commonGameSummaries; + mapping(uint256 => bool) private tokenExists; + mapping(uint256 => string) public tokenUris; // tokenId => tokenUri + mapping(uint256 => bool) public isTokenMintPaused; // tokenId => bool - default is false - // player address => tokenId(storeId+0+gameId) => player game data - mapping(address => mapping(uint256 => PlayerGameData)) public playerGameData; + uint256[] public itemIds; - mapping(address => bool) public whitelistSigners; + mapping(address => mapping(uint256 => bool)) private tokenIdProcessed; - // bytes(signature) => used - mapping(bytes => bool) public usedSignatures; + event Minted(address indexed to, uint256[] tokenIds, uint256 amount, bool soulbound); + event MintedId(address indexed to, uint256 indexed tokenId, uint256 amount, bool soulbound); + event TokenAdded(uint256 indexed tokenId); - modifier onlyOnceSignature(bytes memory signature) { - require(usedSignatures[signature] != true, "Signature and nonce already used"); + modifier maxPerMintCheck(uint256 amount) { + if (amount > MAX_PER_MINT) { + revert("ExceedMaxMint"); + } _; } - constructor(string memory _uri) ERC1155(_uri) { - baseUri = _uri; - _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + constructor(address devWallet) ERC1155("") { + if (devWallet == address(0)) { + revert AddressIsZero(); + } + _grantRole(DEFAULT_ADMIN_ROLE, devWallet); + } + + function initialize( + string memory _name, + string memory _symbol, + string memory _initBaseURI, + string memory _contractURI, + uint256 _maxPerMint, + bool _isPaused, + address devWallet + ) external initializer onlyRole(DEFAULT_ADMIN_ROLE) { + if (devWallet == address(0)) { + revert AddressIsZero(); + } + + _grantRole(DEFAULT_ADMIN_ROLE, devWallet); + _grantRole(DEV_CONFIG_ROLE, devWallet); + _grantRole(MINTER_ROLE, devWallet); + _grantRole(MANAGER_ROLE, devWallet); + _addWhitelistSigner(devWallet); + + name = _name; + symbol = _symbol; + baseURI = _initBaseURI; + contractURI = _contractURI; + MAX_PER_MINT = _maxPerMint; + + if (_isPaused) _pause(); + } + + function getAllItems() public view returns (LibItems.TokenReturn[] memory) { + uint256 totalTokens = itemIds.length; + LibItems.TokenReturn[] memory tokenReturns = new LibItems.TokenReturn[](totalTokens); + + uint index; + for (uint i = 0; i < totalTokens; i++) { + uint256 tokenId = itemIds[i]; + uint256 amount = balanceOf(_msgSender(), tokenId); + + if (amount > 0) { + LibItems.TokenReturn memory tokenReturn = LibItems.TokenReturn({ + tokenId: tokenId, + tokenUri: uri(tokenId), + amount: amount + }); + tokenReturns[index] = tokenReturn; + index++; + } + } + + // truncate the array + LibItems.TokenReturn[] memory returnsTruncated = new LibItems.TokenReturn[](index); + for (uint i = 0; i < index; i++) { + returnsTruncated[i] = tokenReturns[i]; + } + + return returnsTruncated; } - function pause() public onlyRole(DEFAULT_ADMIN_ROLE) { - _pause(); + function getAllItemsAdmin( + address _owner + ) public view onlyRole(MINTER_ROLE) returns (LibItems.TokenReturn[] memory) { + uint256 totalTokens = itemIds.length; + LibItems.TokenReturn[] memory tokenReturns = new LibItems.TokenReturn[](totalTokens); + + uint index; + for (uint i = 0; i < totalTokens; i++) { + uint256 tokenId = itemIds[i]; + uint256 amount = balanceOf(_owner, tokenId); + + LibItems.TokenReturn memory tokenReturn = LibItems.TokenReturn({ + tokenId: tokenId, + tokenUri: uri(tokenId), + amount: amount + }); + tokenReturns[index] = tokenReturn; + index++; + } + + // truncate the array + LibItems.TokenReturn[] memory returnsTruncated = new LibItems.TokenReturn[](index); + for (uint i = 0; i < index; i++) { + returnsTruncated[i] = tokenReturns[i]; + } + + return returnsTruncated; } - function unpause() public onlyRole(DEFAULT_ADMIN_ROLE) { - _unpause(); + function isTokenExist(uint256 _tokenId) public view returns (bool) { + if (!tokenExists[_tokenId]) { + revert("TokenNotExist"); + } + return true; } - function getTokenId(uint256 storeId, uint256 gameId) public pure returns (uint256) { - uint256 tokenId = uint256(keccak256(abi.encode(storeId, gameId))); - return tokenId; + function _verifyContractChainIdAndDecode(bytes calldata data) private view returns (uint256[] memory) { + uint256 currentChainId = getChainID(); + (address contractAddress, uint256 chainId, uint256[] memory tokenIds) = _decodeData(data); + + if (chainId != currentChainId || contractAddress != address(this)) { + revert InvalidInput(); + } + return tokenIds; } - function setBaseUri(string memory _uri) public onlyRole(DEFAULT_ADMIN_ROLE) { - baseUri = _uri; + function decodeData( + bytes calldata _data + ) public view onlyRole(DEV_CONFIG_ROLE) returns (address, uint256, uint256[] memory) { + return _decodeData(_data); } - function setSigner(address _signer) public onlyRole(DEFAULT_ADMIN_ROLE) { - whitelistSigners[_signer] = true; - emit SignerAdded(_signer); + function _decodeData(bytes calldata _data) private view returns (address, uint256, uint256[] memory) { + (address contractAddress, uint256 chainId, uint256[] memory _tokenIds) = abi.decode( + _data, + (address, uint256, uint256[]) + ); + return (contractAddress, chainId, _tokenIds); } - function removeSigner(address signer) public onlyRole(DEFAULT_ADMIN_ROLE) { - whitelistSigners[signer] = false; - emit SignerRemoved(signer); + function pause() external onlyRole(MANAGER_ROLE) { + _pause(); } - function recoverAddress(uint256 nonce, bytes memory signature) private view returns (address) { - bytes32 message = keccak256(abi.encodePacked(msg.sender, nonce)); - bytes32 hash = ECDSA.toEthSignedMessageHash(message); - address signer = ECDSA.recover(hash, signature); - return signer; + function unpause() external onlyRole(MANAGER_ROLE) { + _unpause(); } - function verifySignature(uint256 nonce, bytes memory signature) private returns (bool) { - address signer = recoverAddress(nonce, signature); - if (whitelistSigners[signer]) { - usedSignatures[signature] = true; - return true; - } else { - return false; + function addNewToken(LibItems.TokenCreate calldata _token) public onlyRole(DEV_CONFIG_ROLE) { + if (bytes(_token.tokenUri).length > 0) { + tokenUris[_token.tokenId] = _token.tokenUri; } - } - function getGameSummary(uint256 tokenId) public view returns (GameSummary memory) { - return commonGameSummaries[tokenId]; + tokenExists[_token.tokenId] = true; + + itemIds.push(_token.tokenId); + emit TokenAdded(_token.tokenId); } - function getGameSummaries(uint256[] calldata tokenIds) public view returns (GameSummary[] memory) { - GameSummary[] memory summaries = new GameSummary[](tokenIds.length); - for (uint i = 0; i < tokenIds.length; i++) { - summaries[i] = commonGameSummaries[tokenIds[i]]; + function addNewTokens(LibItems.TokenCreate[] calldata _tokens) external onlyRole(DEV_CONFIG_ROLE) { + for (uint256 i = 0; i < _tokens.length; i++) { + addNewToken(_tokens[i]); } - return summaries; } - function getPlayerGameData(address player, uint256 tokenId) public view returns (PlayerGameData memory) { - return playerGameData[player][tokenId]; + function updateTokenUri(uint256 _tokenId, string calldata _tokenUri) public onlyRole(DEV_CONFIG_ROLE) { + tokenUris[_tokenId] = _tokenUri; } - function getPlayerGamesData( - address player, - uint256[] calldata tokenIds - ) public view returns (PlayerGameData[] memory) { - PlayerGameData[] memory playerGamesData = new PlayerGameData[](tokenIds.length); - for (uint i = 0; i < tokenIds.length; i++) { - playerGamesData[i] = playerGameData[player][tokenIds[i]]; + function batchUpdateTokenUri( + uint256[] calldata _tokenIds, + string[] calldata _tokenUris + ) public onlyRole(DEV_CONFIG_ROLE) { + if (_tokenIds.length != _tokenUris.length) { + revert("InvalidInput"); + } + for (uint256 i = 0; i < _tokenIds.length; i++) { + updateTokenUri(_tokenIds[i], _tokenUris[i]); } - return playerGamesData; } - function updateCommonGameSummary( - uint256 tokenId, - string memory newName, - string memory newImageURI, - string memory newExternalURI, - uint256 newTotalAchievements - ) public onlyRole(GAME_CREATOR_ROLE) whenNotPaused { - require(tokenId > 0, "TokenId must be greater than 0"); - require(commonGameSummaries[tokenId].storeId != 0, "Token doesn't exists"); - GameSummary storage gameData = commonGameSummaries[tokenId]; - gameData.name = newName; - gameData.image = newImageURI; - gameData.externalURI = newExternalURI; - gameData.totalAchievements = newTotalAchievements; - emit GameSummaryUpdated(msg.sender, tokenId); - } - - function addPlayerAchievements(address player, uint256 tokenId, uint256 newAchievements) private { - require(tokenId > 0, "TokenId must be greater than 0"); - require(playerGameData[player][tokenId].tokenId != 0, "Token doesn't exists"); - PlayerGameData storage playerData = playerGameData[player][tokenId]; - if (playerData.achievementsMinted + newAchievements > commonGameSummaries[tokenId].totalAchievements) { - revert("total achievements exceeded"); - } - playerData.achievementsMinted += newAchievements; - emit PlayerGameSummaryUpdated(player, tokenId, playerData.achievementsMinted); + function updateTokenMintPaused(uint256 _tokenId, bool _isTokenMintPaused) public onlyRole(MANAGER_ROLE) { + isTokenMintPaused[_tokenId] = _isTokenMintPaused; } - function adminUpdatePlayerAchievements( - address player, - uint256 tokenId, - uint256 newAchievements - ) public onlyRole(DEFAULT_ADMIN_ROLE) whenNotPaused { - addPlayerAchievements(player, tokenId, newAchievements); + function _mintBatch(address to, uint256[] memory _tokenIds, uint256 amount, bool soulbound) private { + for (uint256 i = 0; i < _tokenIds.length; i++) { + uint256 _id = _tokenIds[i]; + isTokenExist(_id); + if (isTokenMintPaused[_id]) { + revert("TokenMintPaused"); + } + + if (soulbound) { + _soulbound(to, _id, amount); + } + + _mint(to, _id, amount, ""); + } + emit Minted(to, _tokenIds, amount, soulbound); } - function updatePlayerAchievementsWithSignature( - uint256 tokenId, - uint256 newAchievements, + function mint( + bytes calldata data, + uint256 amount, + bool soulbound, uint256 nonce, - bytes memory signature - ) public nonReentrant onlyOnceSignature(signature) whenNotPaused { - require(verifySignature(nonce, signature), "Invalid signature"); - addPlayerAchievements(msg.sender, tokenId, newAchievements); - } - - function createCommonGameSummary( - uint256 storeId, - uint256 gameId, - string memory name, - string memory onChainURI, - string memory externalURI, - uint256 totalAchievements - ) public onlyRole(GAME_CREATOR_ROLE) { - require(gameId > 0, "GameId must be greater than 0"); - require(storeId > 0, "StoreId must be greater than 0"); - uint256 tokenId = getTokenId(storeId, gameId); - require(commonGameSummaries[tokenId].tokenId != tokenId, "CommonGameSummary already exists"); - commonGameSummaries[tokenId] = GameSummary( - tokenId, - storeId, - gameId, - name, - onChainURI, - externalURI, - totalAchievements - ); - emit GameSummaryMinted(msg.sender, tokenId, totalAchievements); + bytes calldata signature + ) external nonReentrant signatureCheck(_msgSender(), nonce, data, signature) maxPerMintCheck(amount) whenNotPaused { + uint256[] memory _tokenIds = _verifyContractChainIdAndDecode(data); + _mintBatch(_msgSender(), _tokenIds, amount, soulbound); } - function mintGameSummary( - address player, - uint256 gameId, - uint256 achievementsLength, - uint256 storeId, - bool soulbound - ) private { - require(storeId > 0, "StoreId must be greater than 0"); - require(gameId > 0, "GameId must be greater than 0"); - uint256 tokenId = getTokenId(storeId, gameId); - require(commonGameSummaries[tokenId].tokenId == tokenId, "This game is not allowed yet"); - require(playerGameData[player][tokenId].tokenId == 0, "Token already exists"); - _mint(player, tokenId, 1, ""); - playerGameData[player][tokenId] = PlayerGameData(tokenId, achievementsLength, soulbound); - emit PlayerGameSummaryMinted(player, tokenId, achievementsLength); - } - - function adminMintGameSummary( + function adminMint(address to, bytes calldata data, bool soulbound) external onlyRole(MINTER_ROLE) whenNotPaused { + uint256[] memory _tokenIds = _verifyContractChainIdAndDecode(data); + _mintBatch(to, _tokenIds, 1, soulbound); + } + + function adminMintId( address to, - uint256 gameId, - uint256 achievementsLength, - uint256 storeId, + uint256 id, + uint256 amount, bool soulbound - ) public onlyRole(MINTER_ROLE) whenNotPaused { - mintGameSummary(to, gameId, achievementsLength, storeId, soulbound); - } + ) external onlyRole(MINTER_ROLE) whenNotPaused { + isTokenExist(id); - function mintGameSummaryWithSignature( - uint256 gameId, - uint256 achievementsLength, - uint256 storeId, - uint256 nonce, - bytes memory signature - ) public nonReentrant onlyOnceSignature(signature) whenNotPaused { - require(verifySignature(nonce, signature), "Invalid signature"); - mintGameSummary(msg.sender, gameId, achievementsLength, storeId, true); - } - - function adminBatchPlayerUpdateAchievements( - address[] memory players, - uint256[] calldata tokenIds, - uint256[] calldata newAchievements - ) public onlyRole(DEFAULT_ADMIN_ROLE) whenNotPaused { - require(players.length == tokenIds.length, "The players and tokenIds arrays must have the same length"); - require( - players.length == newAchievements.length, - "The players and newAchievements arrays must have the same length" - ); - for (uint i = 0; i < players.length; i++) { - addPlayerAchievements(players[i], tokenIds[i], newAchievements[i]); + if (isTokenMintPaused[id]) { + revert("TokenMintPaused"); } - } - function batchPlayerUpdateAchievementsWithSignature( - uint256[] calldata tokenIds, - uint256[] calldata newAchievements, - uint256 nonce, - bytes memory signature - ) public nonReentrant onlyOnceSignature(signature) whenNotPaused { - require(verifySignature(nonce, signature), "Invalid signature"); - require( - tokenIds.length == newAchievements.length, - "The players and newAchievements arrays must have the same length" - ); - for (uint i = 0; i < tokenIds.length; i++) { - addPlayerAchievements(msg.sender, tokenIds[i], newAchievements[i]); + if (soulbound) { + _soulbound(to, id, amount); } - } - function adminBatchMintGameSummary( - address[] calldata players, - uint256[] calldata gameIds, - uint256[] calldata achievementsLength, - uint256[] calldata storeIds, - bool[] calldata soulbounds - ) public onlyRole(MINTER_ROLE) whenNotPaused { - require(players.length == gameIds.length, "The players and gameIds arrays must have the same length"); - require(players.length == storeIds.length, "The players and storeIds arrays must have the same length"); - require( - players.length == achievementsLength.length, - "The players and newAchievements arrays must have the same length" - ); - for (uint i = 0; i < players.length; i++) { - mintGameSummary(players[i], gameIds[i], achievementsLength[i], storeIds[i], soulbounds[i]); - } + _mint(to, id, amount, ""); + emit MintedId(to, id, amount, soulbound); } - function batchMintGameSummaryWithSignature( - uint256[] calldata gameIds, - uint256[] calldata newAchievements, - uint256[] calldata storeIds, - uint256 nonce, - bytes memory signature - ) public whenNotPaused onlyOnceSignature(signature) nonReentrant { - require(verifySignature(nonce, signature), "Invalid signature"); - require(gameIds.length == storeIds.length, "The gameIds and storeIds arrays must have the same length"); - require( - gameIds.length == newAchievements.length, - "The gameIds and newAchievements arrays must have the same length" - ); - - for (uint i = 0; i < gameIds.length; i++) { - mintGameSummary(msg.sender, gameIds[i], newAchievements[i], storeIds[i], true); - } + function _beforeTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual override(ERC1155, ERC1155Supply) { + super._beforeTokenTransfer(operator, from, to, ids, amounts, data); } function safeTransferFrom( @@ -333,16 +327,7 @@ contract GameSummary is ERC1155, AccessControl, Pausable, ReentrancyGuard { uint256 _id, uint256 _amount, bytes memory _data - ) public virtual override { - require(playerGameData[_from][_id].tokenId != 0, "Token doesn't exists"); - require(!playerGameData[_from][_id].soulbounded, "You can't transfer this token"); - PlayerGameData storage playerData = playerGameData[_from][_id]; - uint256 transferachievements = playerData.achievementsMinted; - playerGameData[_from][_id] = PlayerGameData(0, 0, false); - if (playerGameData[_to][_id].tokenId != 0) { - revert("Token already exists, not possible to send it"); - } - playerGameData[_to][_id] = PlayerGameData(_id, transferachievements, false); + ) public virtual override soulboundCheckAndSync(_from, _to, _id, _amount, balanceOf(_from, _id)) { super.safeTransferFrom(_from, _to, _id, _amount, _data); } @@ -352,43 +337,161 @@ contract GameSummary is ERC1155, AccessControl, Pausable, ReentrancyGuard { uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data - ) public virtual override { - for (uint i = 0; i < _ids.length; i++) { - require(playerGameData[_from][_ids[i]].tokenId != 0, "Token doesn't exists"); - require(!playerGameData[_from][_ids[i]].soulbounded, "You can't transfer this token"); + ) + public + virtual + override + soulboundCheckAndSyncBatch(_from, _to, _ids, _amounts, balanceOfBatchOneAccount(_from, _ids)) + { + for (uint256 i = 0; i < _ids.length; i++) { + uint256 id = _ids[i]; + + if (tokenIdProcessed[_from][id]) { + revert("ERC1155: duplicate ID"); + } + + tokenIdProcessed[_from][id] = true; } + super.safeBatchTransferFrom(_from, _to, _ids, _amounts, _data); - for (uint i = 0; i < _ids.length; i++) { - PlayerGameData storage playerData = playerGameData[_from][_ids[i]]; - uint256 transferachievements = playerData.achievementsMinted; - playerGameData[_from][_ids[i]] = PlayerGameData(0, 0, false); - playerGameData[_to][_ids[i]] = PlayerGameData(_ids[i], transferachievements, false); + // Reset processed status after the transfer is completed + for (uint256 i = 0; i < _ids.length; i++) { + uint256 id = _ids[i]; + tokenIdProcessed[_from][id] = false; } } - function uri(uint256 _tokenId) public view override returns (string memory) { - return string(abi.encodePacked(baseUri, Strings.toString(_tokenId), ".json")); - } + function balanceOfBatchOneAccount( + address account, + uint256[] memory ids + ) public view virtual returns (uint256[] memory) { + uint256[] memory batchBalances = new uint256[](ids.length); + + for (uint256 i = 0; i < ids.length; ++i) { + batchBalances[i] = balanceOf(account, ids[i]); + } - function burn(uint256 tokenId) public nonReentrant { - require(playerGameData[msg.sender][tokenId].tokenId != 0, "Token doesn't exists"); - _burn(msg.sender, tokenId, 1); - playerGameData[msg.sender][tokenId] = PlayerGameData(0, 0, false); + return batchBalances; } - function burnBatch(uint256[] memory tokenIds) public nonReentrant { - uint256[] memory amounts = new uint256[](tokenIds.length); - for (uint i = 0; i < tokenIds.length; i++) { - require(playerGameData[msg.sender][tokenIds[i]].tokenId != 0, "Token doesn't exist"); - require(balanceOf(msg.sender, tokenIds[i]) > 0, "You don't have a token to burn"); - amounts[i] = 1; - playerGameData[msg.sender][tokenIds[i]] = PlayerGameData(0, 0, false); + function burn( + address to, + uint256 tokenId, + uint256 amount + ) + public + virtual + override + nonReentrant + soulboundCheckAndSync(to, address(0), tokenId, amount, balanceOf(to, tokenId)) + { + ERC1155Burnable.burn(to, tokenId, amount); + } + + function burnBatch( + address to, + uint256[] memory tokenIds, + uint256[] memory amounts + ) + public + virtual + override + nonReentrant + soulboundCheckAndSyncBatch(to, address(0), tokenIds, amounts, balanceOfBatchOneAccount(to, tokenIds)) + { + for (uint256 i = 0; i < tokenIds.length; i++) { + uint256 id = tokenIds[i]; + + if (tokenIdProcessed[to][id]) { + revert("ERC1155: duplicate ID"); + } + + tokenIdProcessed[to][id] = true; + } + + ERC1155Burnable.burnBatch(to, tokenIds, amounts); + + // Reset processed status after the transfer is completed + for (uint256 i = 0; i < tokenIds.length; i++) { + uint256 id = tokenIds[i]; + tokenIdProcessed[to][id] = false; } - _burnBatch(msg.sender, tokenIds, amounts); } function supportsInterface(bytes4 interfaceId) public view override(ERC1155, AccessControl) returns (bool) { return super.supportsInterface(interfaceId); } + + function uri(uint256 tokenId) public view override returns (string memory) { + isTokenExist(tokenId); + if (compoundURIEnabled) { + // "{compoundURI}/0x1234567890123456789012345678901234567890/{tokenId}"; + return + string( + abi.encodePacked( + compoundURI, + "/", + Strings.toHexString(uint160(address(this)), 20), + "/", + Strings.toString(tokenId) + ) + ); + } + + return string(abi.encodePacked(baseURI, "/", tokenId.toString())); + } + + function setBaseUri(string memory _uri) public onlyRole(DEV_CONFIG_ROLE) { + baseURI = _uri; + emit BaseURIChanged(baseURI); + } + + function setCompoundURIEnabled(bool _compoundURIEnabled) public onlyRole(DEV_CONFIG_ROLE) { + if (_compoundURIEnabled == compoundURIEnabled) { + revert InvalidInput(); + } + + compoundURIEnabled = _compoundURIEnabled; + emit CompoundURIEnabledChanged(_compoundURIEnabled); + } + + function setCompoundURI(string memory _compoundURI) public onlyRole(DEV_CONFIG_ROLE) { + compoundURI = _compoundURI; + emit CompoundURIChanged(_compoundURI); + } + + function setContractURI(string memory _contractURI) public onlyRole(DEV_CONFIG_ROLE) { + contractURI = _contractURI; + emit ContractURIChanged(_contractURI); + } + + function updateWhitelistAddress(address _address, bool _isWhitelisted) external onlyRole(DEV_CONFIG_ROLE) { + _updateWhitelistAddress(_address, _isWhitelisted); + } + + function adminVerifySignature( + address to, + uint256 nonce, + bytes calldata data, + bytes calldata signature + ) public onlyRole(DEV_CONFIG_ROLE) returns (bool) { + return _verifySignature(to, nonce, data, signature); + } + + function addWhitelistSigner(address _signer) external onlyRole(DEV_CONFIG_ROLE) { + _addWhitelistSigner(_signer); + } + + function removeWhitelistSigner(address signer) external onlyRole(DEV_CONFIG_ROLE) { + _removeWhitelistSigner(signer); + } + + function getChainID() public view returns (uint256) { + uint256 id; + assembly { + id := chainid() + } + return id; + } } From c4ed3fd58a93e8a7b986f517e8e53d30c52c9f9c Mon Sep 17 00:00:00 2001 From: Max Vasin Limsukhawat Date: Tue, 30 Apr 2024 18:19:29 -0600 Subject: [PATCH 2/6] chore: Change author to achievo team --- contracts/airdrops/FreeMint.sol | 3 +-- contracts/bridges/ERC20Bridge.sol | 3 +-- contracts/ercs/ERCWhitelistSignature.sol | 3 +-- contracts/ercs/extensions/Achievo1155Soulbound.sol | 3 +-- contracts/ercs/extensions/Achievo721Soulbound.sol | 3 +-- contracts/games/AvatarBound.sol | 3 +-- contracts/games/GameSummary.sol | 5 ++--- contracts/games/LevelsBound.sol | 3 +-- contracts/governance/Whitelist.sol | 3 +-- contracts/interfaces/IMarketplace.sol | 6 ++---- contracts/mocks/MockErc20.sol | 3 +-- contracts/soulbounds/AdminERC1155Soulbound.sol | 3 +-- contracts/soulbounds/ERC1155RoyaltiesSoulbound.sol | 3 +-- contracts/soulbounds/ERC1155Soulbound.sol | 7 ++++--- contracts/soulbounds/ERC721Soulbound.sol | 3 +-- contracts/soulbounds/LootDrop.sol | 3 +-- contracts/upgradeables/bridges/ERC20BridgeV1.sol | 3 +-- .../ercs/ERCWhitelistSignatureUpgradeable.sol | 3 +-- .../ercs/extensions/Achievo1155SoulboundUpgradeable.sol | 3 +-- .../ercs/extensions/Achievo721SoulboundUpgradeable.sol | 3 +-- contracts/upgradeables/games/AvatarBoundV1.sol | 3 +-- contracts/upgradeables/games/LevelsBoundV1.sol | 3 +-- contracts/upgradeables/games/LevelsBoundV2.sol | 3 +-- .../upgradeables/soulbounds/ERC1155RewardSoulboundV1.sol | 9 ++------- .../soulbounds/ERC1155RoyaltiesSoulboundV1.sol | 4 +--- .../soulbounds/ERC1155RoyaltiesSoulboundV2.sol | 3 +-- contracts/upgradeables/soulbounds/ERC1155SoulboundV1.sol | 3 +-- contracts/upgradeables/soulbounds/ERC721SoulboundV1.sol | 4 +--- 28 files changed, 34 insertions(+), 67 deletions(-) diff --git a/contracts/airdrops/FreeMint.sol b/contracts/airdrops/FreeMint.sol index 837b686e..8b8cc642 100644 --- a/contracts/airdrops/FreeMint.sol +++ b/contracts/airdrops/FreeMint.sol @@ -18,8 +18,7 @@ pragma solidity ^0.8.17; // MMMM0cdNMM0cdNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM /** - * Authors: Omar Garcia - * GitHub: https://github.com/ogarciarevett + * Author: Achievo Team - (https://achievo.xyz/) */ import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; diff --git a/contracts/bridges/ERC20Bridge.sol b/contracts/bridges/ERC20Bridge.sol index d11f5058..d04b96ab 100644 --- a/contracts/bridges/ERC20Bridge.sol +++ b/contracts/bridges/ERC20Bridge.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/ercs/ERCWhitelistSignature.sol b/contracts/ercs/ERCWhitelistSignature.sol index 5381c3c9..39febfa1 100644 --- a/contracts/ercs/ERCWhitelistSignature.sol +++ b/contracts/ercs/ERCWhitelistSignature.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/ercs/extensions/Achievo1155Soulbound.sol b/contracts/ercs/extensions/Achievo1155Soulbound.sol index e18ea901..09d20e4a 100644 --- a/contracts/ercs/extensions/Achievo1155Soulbound.sol +++ b/contracts/ercs/extensions/Achievo1155Soulbound.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /* - * Author: Omar ogarciarevett(https://github.com/ogarciarevett) - * Co-Authors: Max vasinl124(https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/ercs/extensions/Achievo721Soulbound.sol b/contracts/ercs/extensions/Achievo721Soulbound.sol index a9ae6d5d..645f5ea0 100644 --- a/contracts/ercs/extensions/Achievo721Soulbound.sol +++ b/contracts/ercs/extensions/Achievo721Soulbound.sol @@ -17,8 +17,7 @@ pragma solidity ^0.8.17; // MMMM0cdNMM0cdNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM /* - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ contract Achievo721Soulbound { diff --git a/contracts/games/AvatarBound.sol b/contracts/games/AvatarBound.sol index 4678f801..d9487a50 100644 --- a/contracts/games/AvatarBound.sol +++ b/contracts/games/AvatarBound.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/games/GameSummary.sol b/contracts/games/GameSummary.sol index 3c228581..d5adbca9 100644 --- a/contracts/games/GameSummary.sol +++ b/contracts/games/GameSummary.sol @@ -1,9 +1,8 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; /** - * Author: Max vasinl124(https://github.com/vasinl124) - * Co-Authors: Omar ogarciarevett(https://github.com/ogarciarevett) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/games/LevelsBound.sol b/contracts/games/LevelsBound.sol index 9cb16305..caca01c4 100644 --- a/contracts/games/LevelsBound.sol +++ b/contracts/games/LevelsBound.sol @@ -17,8 +17,7 @@ pragma solidity ^0.8.17; // MMMM0cdNMM0cdNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM /** - * Authors: Omar Garcia - * GitHub: https://github.com/ogarciarevett + * Author: Achievo Team - (https://achievo.xyz/) */ import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; diff --git a/contracts/governance/Whitelist.sol b/contracts/governance/Whitelist.sol index 3a80ae98..5186d789 100644 --- a/contracts/governance/Whitelist.sol +++ b/contracts/governance/Whitelist.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/interfaces/IMarketplace.sol b/contracts/interfaces/IMarketplace.sol index fa0e668f..05b91c90 100644 --- a/contracts/interfaces/IMarketplace.sol +++ b/contracts/interfaces/IMarketplace.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM @@ -21,7 +20,6 @@ pragma solidity ^0.8.17; // MMNx'.dWMMK;.:0WMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM // MMMM0cdNMM0cdNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM - /** * @author Omar * @@ -528,4 +526,4 @@ interface IOffers { /// @notice Returns all valid offers. An offer is valid if the offeror owns and has approved Marketplace to transfer the offer amount of currency. function getAllValidOffers(uint256 _startId, uint256 _endId) external view returns (Offer[] memory offers); -} \ No newline at end of file +} diff --git a/contracts/mocks/MockErc20.sol b/contracts/mocks/MockErc20.sol index d6a302bd..ca713c52 100644 --- a/contracts/mocks/MockErc20.sol +++ b/contracts/mocks/MockErc20.sol @@ -3,8 +3,7 @@ pragma solidity ^0.8.17; /** - * Authors: Omar Garcia - * GitHub: https://github.com/ogarciarevett + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/soulbounds/AdminERC1155Soulbound.sol b/contracts/soulbounds/AdminERC1155Soulbound.sol index ec99b71d..b51a9d3b 100644 --- a/contracts/soulbounds/AdminERC1155Soulbound.sol +++ b/contracts/soulbounds/AdminERC1155Soulbound.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Max vasinl124(https://github.com/vasinl124) - * Co-Authors: Omar ogarciarevett(https://github.com/ogarciarevett) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/soulbounds/ERC1155RoyaltiesSoulbound.sol b/contracts/soulbounds/ERC1155RoyaltiesSoulbound.sol index 66f3a000..69977da8 100644 --- a/contracts/soulbounds/ERC1155RoyaltiesSoulbound.sol +++ b/contracts/soulbounds/ERC1155RoyaltiesSoulbound.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Max vasinl124(https://github.com/vasinl124) - * Co-Authors: Omar ogarciarevett(https://github.com/ogarciarevett) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/soulbounds/ERC1155Soulbound.sol b/contracts/soulbounds/ERC1155Soulbound.sol index 1fac2e2b..a6d34e97 100644 --- a/contracts/soulbounds/ERC1155Soulbound.sol +++ b/contracts/soulbounds/ERC1155Soulbound.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Max vasinl124(https://github.com/vasinl124) - * Co-Authors: Omar ogarciarevett(https://github.com/ogarciarevett) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM @@ -409,7 +408,9 @@ contract ERC1155Soulbound is } } - function supportsInterface(bytes4 interfaceId) public view override(ERC2981, ERC1155, AccessControl) returns (bool) { + function supportsInterface( + bytes4 interfaceId + ) public view override(ERC2981, ERC1155, AccessControl) returns (bool) { return super.supportsInterface(interfaceId); } diff --git a/contracts/soulbounds/ERC721Soulbound.sol b/contracts/soulbounds/ERC721Soulbound.sol index 3a3e5799..e60b120b 100644 --- a/contracts/soulbounds/ERC721Soulbound.sol +++ b/contracts/soulbounds/ERC721Soulbound.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/soulbounds/LootDrop.sol b/contracts/soulbounds/LootDrop.sol index 629a7a3b..a6992af5 100644 --- a/contracts/soulbounds/LootDrop.sol +++ b/contracts/soulbounds/LootDrop.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/upgradeables/bridges/ERC20BridgeV1.sol b/contracts/upgradeables/bridges/ERC20BridgeV1.sol index 1636bebf..f820ec2c 100644 --- a/contracts/upgradeables/bridges/ERC20BridgeV1.sol +++ b/contracts/upgradeables/bridges/ERC20BridgeV1.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/upgradeables/ercs/ERCWhitelistSignatureUpgradeable.sol b/contracts/upgradeables/ercs/ERCWhitelistSignatureUpgradeable.sol index c27e83f5..b3b8e726 100644 --- a/contracts/upgradeables/ercs/ERCWhitelistSignatureUpgradeable.sol +++ b/contracts/upgradeables/ercs/ERCWhitelistSignatureUpgradeable.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/upgradeables/ercs/extensions/Achievo1155SoulboundUpgradeable.sol b/contracts/upgradeables/ercs/extensions/Achievo1155SoulboundUpgradeable.sol index fd00161e..a09809ee 100644 --- a/contracts/upgradeables/ercs/extensions/Achievo1155SoulboundUpgradeable.sol +++ b/contracts/upgradeables/ercs/extensions/Achievo1155SoulboundUpgradeable.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/upgradeables/ercs/extensions/Achievo721SoulboundUpgradeable.sol b/contracts/upgradeables/ercs/extensions/Achievo721SoulboundUpgradeable.sol index 402ab56c..19478549 100644 --- a/contracts/upgradeables/ercs/extensions/Achievo721SoulboundUpgradeable.sol +++ b/contracts/upgradeables/ercs/extensions/Achievo721SoulboundUpgradeable.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/upgradeables/games/AvatarBoundV1.sol b/contracts/upgradeables/games/AvatarBoundV1.sol index 7a6c9e1d..92917eb5 100644 --- a/contracts/upgradeables/games/AvatarBoundV1.sol +++ b/contracts/upgradeables/games/AvatarBoundV1.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/upgradeables/games/LevelsBoundV1.sol b/contracts/upgradeables/games/LevelsBoundV1.sol index 0a4111e3..4871f246 100644 --- a/contracts/upgradeables/games/LevelsBoundV1.sol +++ b/contracts/upgradeables/games/LevelsBoundV1.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/upgradeables/games/LevelsBoundV2.sol b/contracts/upgradeables/games/LevelsBoundV2.sol index ca7e8fa2..a0a23ff3 100644 --- a/contracts/upgradeables/games/LevelsBoundV2.sol +++ b/contracts/upgradeables/games/LevelsBoundV2.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/upgradeables/soulbounds/ERC1155RewardSoulboundV1.sol b/contracts/upgradeables/soulbounds/ERC1155RewardSoulboundV1.sol index 82d15c69..795bad2c 100644 --- a/contracts/upgradeables/soulbounds/ERC1155RewardSoulboundV1.sol +++ b/contracts/upgradeables/soulbounds/ERC1155RewardSoulboundV1.sol @@ -2,13 +2,11 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ //TODO: This contract is deprecated USE THE LootDrop.sol - // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM // MWXd,. .cONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM // Wx' .cKMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM @@ -200,10 +198,7 @@ contract ERC1155RewardSoulboundV1 is _burn(to, defaultRewardId, 1); } - function adminClaimERC20RewardById( - address to, - uint256[] calldata _tokenIds - ) public onlyRole(MANAGER_ROLE) { + function adminClaimERC20RewardById(address to, uint256[] calldata _tokenIds) public onlyRole(MANAGER_ROLE) { require(to != address(0), "InvalidToAddress"); for (uint256 i = 0; i < _tokenIds.length; i++) { diff --git a/contracts/upgradeables/soulbounds/ERC1155RoyaltiesSoulboundV1.sol b/contracts/upgradeables/soulbounds/ERC1155RoyaltiesSoulboundV1.sol index bf88693d..57605be8 100644 --- a/contracts/upgradeables/soulbounds/ERC1155RoyaltiesSoulboundV1.sol +++ b/contracts/upgradeables/soulbounds/ERC1155RoyaltiesSoulboundV1.sol @@ -2,13 +2,11 @@ pragma solidity ^0.8.17; /** - * Author: Max vasinl124(https://github.com/vasinl124) - * Co-Authors: Omar ogarciarevett(https://github.com/ogarciarevett) + * Author: Achievo Team - (https://achievo.xyz/) */ //TODO: This contract is deprecated USE THE ERC1155SoulboundV1.sol - // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM // MWXd,. .cONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM // Wx' .cKMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/upgradeables/soulbounds/ERC1155RoyaltiesSoulboundV2.sol b/contracts/upgradeables/soulbounds/ERC1155RoyaltiesSoulboundV2.sol index 94c88b5a..1c613298 100644 --- a/contracts/upgradeables/soulbounds/ERC1155RoyaltiesSoulboundV2.sol +++ b/contracts/upgradeables/soulbounds/ERC1155RoyaltiesSoulboundV2.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Max vasinl124(https://github.com/vasinl124) - * Co-Authors: Omar ogarciarevett(https://github.com/ogarciarevett) + * Author: Achievo Team - (https://achievo.xyz/) */ //TODO: This contract is deprecated USE THE ERC1155SoulboundV1.sol diff --git a/contracts/upgradeables/soulbounds/ERC1155SoulboundV1.sol b/contracts/upgradeables/soulbounds/ERC1155SoulboundV1.sol index 11338861..6901f4cc 100644 --- a/contracts/upgradeables/soulbounds/ERC1155SoulboundV1.sol +++ b/contracts/upgradeables/soulbounds/ERC1155SoulboundV1.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Max vasinl124(https://github.com/vasinl124) - * Co-Authors: Omar ogarciarevett(https://github.com/ogarciarevett) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM diff --git a/contracts/upgradeables/soulbounds/ERC721SoulboundV1.sol b/contracts/upgradeables/soulbounds/ERC721SoulboundV1.sol index af2e1ba8..9ed349d1 100644 --- a/contracts/upgradeables/soulbounds/ERC721SoulboundV1.sol +++ b/contracts/upgradeables/soulbounds/ERC721SoulboundV1.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.17; /** - * Author: Omar (https://github.com/ogarciarevett) - * Co-Authors: Max (https://github.com/vasinl124) + * Author: Achievo Team - (https://achievo.xyz/) */ // MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM @@ -38,7 +37,6 @@ import { } from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; import { Achievo721SoulboundUpgradeable } from "../ercs/extensions/Achievo721SoulboundUpgradeable.sol"; - contract ERC721SoulboundV1 is Initializable, ERC721URIStorageUpgradeable, From ae8ff4114ca6cf8009de17863aa65da6311315df Mon Sep 17 00:00:00 2001 From: Max Vasin Limsukhawat Date: Wed, 1 May 2024 14:12:21 -0600 Subject: [PATCH 3/6] add tests and deploy nonce for GameSummary --- constants/constructor-args.ts | 18 +- .../deployments/deployments-mantle-sepolia.ts | 25 - constants/deployments/index.ts | 8 +- .../deployments-mantle-mainnet.ts | 15 +- .../deployments-mantle-sepolia.ts | 12 +- contracts/games/GameSummary.sol | 10 +- tasks/deploy-create2.ts | 6 +- test/AvatarBound.t.sol | 344 -------- test/AvatarBoundV1.t.sol | 370 --------- test/games/GameSummary.t.sol | 782 ++++++++++++++++++ test/hardhatTests/gameSummary1155.test.ts | 770 ----------------- 11 files changed, 831 insertions(+), 1529 deletions(-) delete mode 100644 constants/deployments/deployments-mantle-sepolia.ts delete mode 100644 test/AvatarBound.t.sol delete mode 100644 test/AvatarBoundV1.t.sol create mode 100644 test/games/GameSummary.t.sol delete mode 100644 test/hardhatTests/gameSummary1155.test.ts diff --git a/constants/constructor-args.ts b/constants/constructor-args.ts index e0f8ede3..84d7bca4 100644 --- a/constants/constructor-args.ts +++ b/constants/constructor-args.ts @@ -258,10 +258,24 @@ export const ERC20ChainlinkPaymasterArgs = { export const GameSummaryArgs = { MAINNET: { - _uri: 'FILL_ME', + _name: 'GameSummary', + _symbol: 'GS', + _defaultTokenURI: 'FILL_ME', + _contractURI: 'FILL_ME', + _compoundURI: 'https://api.achievo.xyz/v1/uri/avatar', + _maxPerMint: 1, + _isPaused: false, + _devWallet: 'DEPLOYER_WALLET', }, TESTNET: { - _uri: 'FILL_ME', + _name: 'GameSummary', + _symbol: 'GS', + _defaultTokenURI: 'FILL_ME', + _contractURI: 'FILL_ME', + _compoundURI: 'https://staging-api.achievo.xyz/v1/uri/avatar', + _maxPerMint: 1, + _isPaused: false, + _devWallet: 'DEPLOYER_WALLET', }, }; diff --git a/constants/deployments/deployments-mantle-sepolia.ts b/constants/deployments/deployments-mantle-sepolia.ts deleted file mode 100644 index 8dd31633..00000000 --- a/constants/deployments/deployments-mantle-sepolia.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { GameSummaryArgs } from '@constants/constructor-args'; -import { CONTRACT_FILE_NAME, CONTRACT_NAME, CONTRACT_TYPE } from '@constants/contract'; -import { TENANT } from '@constants/tenant'; - -import { DeploymentContract } from '../../types/deployment-type'; -import { NETWORK_TYPE, NetworkName } from '../network'; - -const chain = NetworkName.MantleSepolia; -const networkType = NETWORK_TYPE.TESTNET; - -export const MANTLE_SEPOLIA_CONTRACTS: DeploymentContract[] = [ - { - contractFileName: CONTRACT_FILE_NAME.GameSummary, - type: CONTRACT_TYPE.GameSummary, - name: CONTRACT_NAME.GameSummary, - chain, - networkType, - tenants: [TENANT.HyperPlay], - verify: true, - upgradable: false, - dependencies: [], - functionCalls: [], - args: GameSummaryArgs.TESTNET, - }, -]; diff --git a/constants/deployments/index.ts b/constants/deployments/index.ts index bf3e63e5..24f105f7 100644 --- a/constants/deployments/index.ts +++ b/constants/deployments/index.ts @@ -1,15 +1,14 @@ import { ARBITRUM_ONE_CONTRACTS } from '@constants/deployments/deployments-arbitrum-one'; import { ARBITRUM_SEPOLIA_CONTRACTS } from '@constants/deployments/deployments-arbitrum-sepolia'; +import { BASE_SEPOLIA_CONTRACTS } from '@constants/deployments/deployments-base-sepolia'; +import { G7_SEPOLIA_ARB_CONTRACTS } from '@constants/deployments/deployments-g7-sepolia-arb'; +import { G7_SEPOLIA_BASE_CONTRACTS } from '@constants/deployments/deployments-g7-sepolia-base'; -import { MANTLE_SEPOLIA_CONTRACTS } from './deployments-mantle-sepolia'; import { POLYGON_MAINNET_CONTRACTS } from './deployments-polygon-mainnet'; import { POLYGON_MUMBAI_CONTRACTS } from './deployments-polygon-mumbai'; import { SEPOLIA_CONTRACTS } from './deployments-sepolia'; import { ZKSYNC_MAINNET_CONTRACTS } from './deployments-zksync-mainnet'; import { ZKSYNC_SEPOLIA_CONTRACTS } from './deployments-zksync-sepolia'; -import { BASE_SEPOLIA_CONTRACTS } from '@constants/deployments/deployments-base-sepolia'; -import { G7_SEPOLIA_BASE_CONTRACTS } from '@constants/deployments/deployments-g7-sepolia-base'; -import { G7_SEPOLIA_ARB_CONTRACTS } from '@constants/deployments/deployments-g7-sepolia-arb'; export const CONTRACTS = [ ...G7_SEPOLIA_ARB_CONTRACTS, @@ -22,7 +21,6 @@ export const CONTRACTS = [ ...ZKSYNC_MAINNET_CONTRACTS, ...ZKSYNC_SEPOLIA_CONTRACTS, ...SEPOLIA_CONTRACTS, - ...MANTLE_SEPOLIA_CONTRACTS, ]; export const ACHIEVO_TMP_DIR = '.achievo'; diff --git a/constants/nonce-deployments/deployments-mantle-mainnet.ts b/constants/nonce-deployments/deployments-mantle-mainnet.ts index 7293adc7..5505820e 100644 --- a/constants/nonce-deployments/deployments-mantle-mainnet.ts +++ b/constants/nonce-deployments/deployments-mantle-mainnet.ts @@ -1,4 +1,4 @@ -import { HelloWorldArgs, LootDropArgs, RewardTokenArgs } from '@constants/constructor-args'; +import { GameSummaryArgs, LootDropArgs, RewardTokenArgs } from '@constants/constructor-args'; import { CONTRACT_FILE_NAME, CONTRACT_NAME, CONTRACT_TYPE } from '@constants/contract'; import { TENANT } from '@constants/tenant'; @@ -35,4 +35,17 @@ export const MANTLE_MAINNET_CONTRACTS: DeploymentContract[] = [ functionCalls: [], args: RewardTokenArgs.MAINNET, }, + { + contractFileName: CONTRACT_FILE_NAME.GameSummary, + type: CONTRACT_TYPE.GameSummary, + name: CONTRACT_NAME.GameSummary, + chain, + networkType, + tenants: [TENANT.HyperPlay], + verify: true, + upgradable: false, + dependencies: [], + functionCalls: [], + args: GameSummaryArgs.MAINNET, + }, ]; diff --git a/constants/nonce-deployments/deployments-mantle-sepolia.ts b/constants/nonce-deployments/deployments-mantle-sepolia.ts index 58931d9c..c0a82aaf 100644 --- a/constants/nonce-deployments/deployments-mantle-sepolia.ts +++ b/constants/nonce-deployments/deployments-mantle-sepolia.ts @@ -1,4 +1,4 @@ -import { HelloWorldArgs, LootDropArgs, RewardTokenArgs } from '@constants/constructor-args'; +import { LootDropArgs, RewardTokenArgs, GameSummaryArgs } from '@constants/constructor-args'; import { CONTRACT_FILE_NAME, CONTRACT_NAME, CONTRACT_TYPE } from '@constants/contract'; import { TENANT } from '@constants/tenant'; @@ -36,16 +36,16 @@ export const MANTLE_SEPOLIA_CONTRACTS: DeploymentContract[] = [ args: RewardTokenArgs.TESTNET, }, { - contractFileName: CONTRACT_FILE_NAME.HelloWorld, - type: CONTRACT_TYPE.HelloWorld, - name: CONTRACT_NAME.HelloWorld, + contractFileName: CONTRACT_FILE_NAME.GameSummary, + type: CONTRACT_TYPE.GameSummary, + name: CONTRACT_NAME.GameSummary, chain, networkType, - tenants: [TENANT.Game7], + tenants: [TENANT.HyperPlay], verify: true, upgradable: false, dependencies: [], functionCalls: [], - args: HelloWorldArgs.TESTNET, + args: GameSummaryArgs.TESTNET, }, ]; diff --git a/contracts/games/GameSummary.sol b/contracts/games/GameSummary.sol index d5adbca9..da4c779f 100644 --- a/contracts/games/GameSummary.sol +++ b/contracts/games/GameSummary.sol @@ -99,6 +99,7 @@ contract GameSummary is string memory _symbol, string memory _initBaseURI, string memory _contractURI, + string memory _compoundURI, uint256 _maxPerMint, bool _isPaused, address devWallet @@ -119,6 +120,9 @@ contract GameSummary is contractURI = _contractURI; MAX_PER_MINT = _maxPerMint; + compoundURIEnabled = true; + compoundURI = _compoundURI; + if (_isPaused) _pause(); } @@ -438,7 +442,11 @@ contract GameSummary is ); } - return string(abi.encodePacked(baseURI, "/", tokenId.toString())); + if (bytes(tokenUris[tokenId]).length > 0) { + return tokenUris[tokenId]; + } else { + return string(abi.encodePacked(baseURI, "/", tokenId.toString())); + } } function setBaseUri(string memory _uri) public onlyRole(DEV_CONFIG_ROLE) { diff --git a/tasks/deploy-create2.ts b/tasks/deploy-create2.ts index ea729ae2..30d8eb5d 100644 --- a/tasks/deploy-create2.ts +++ b/tasks/deploy-create2.ts @@ -28,9 +28,8 @@ const deployOne = async ( ): Promise => { const encryptedPrivateKey = await encryptPrivateKey(DETERMINISTIC_DEPLOYER_PRIVATE_KEY); - const abiPath = getABIFilePath(true, contractFileName); - const isZkSync = networkName.toLowerCase().includes('zksync'); + const abiPath = getABIFilePath(isZkSync, contractFileName); const networkNameKey = Object.keys(NetworkName)[Object.values(NetworkName).indexOf(networkName as NetworkName)]; const chainId = ChainId[networkNameKey as keyof typeof ChainId]; @@ -51,13 +50,10 @@ const deployOne = async ( Object.keys(NetworkName)[Object.values(NetworkName).indexOf(ethNetworkName as NetworkName)]; const ethChainId = ChainId[ethNetworkNameKey as keyof typeof ChainId]; const ethRpcUrl = rpcUrls[ethChainId]; - const provider = new zkProvider(rpcUrl); const ethProvider = hre.ethers.getDefaultProvider(ethRpcUrl); - deployerWallet = new zkWallet(DETERMINISTIC_DEPLOYER_PRIVATE_KEY, provider, ethProvider); managerWallet = new zkWallet(PRIVATE_KEY, provider, ethProvider); - const factory = new zkContractFactory(contractAbi, bytecode, deployerWallet); achievoContract = await factory.deploy(managerWallet.address); } else { diff --git a/test/AvatarBound.t.sol b/test/AvatarBound.t.sol deleted file mode 100644 index e2ff2383..00000000 --- a/test/AvatarBound.t.sol +++ /dev/null @@ -1,344 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import { Test, console } from "forge-std/Test.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -import { AvatarBound } from "../contracts/games/AvatarBound.sol"; -import { ERC1155RoyaltiesSoulbound } from "../contracts/soulbounds/ERC1155RoyaltiesSoulbound.sol"; -import { MockERC721Receiver } from "../contracts/mocks/MockERC721Receiver.sol"; -import { MockERC1155Receiver } from "../contracts/mocks/MockERC1155Receiver.sol"; -import { FreeMint } from "../contracts/airdrops/FreeMint.sol"; -import { LibItems } from "../contracts/libraries/LibItems.sol"; - -contract AvatarBoundTest is Test { - using Strings for uint256; - AvatarBound public avatarBound; - MockERC721Receiver public mockERC721Receiver; - MockERC1155Receiver public mockERC1155Receiver; - ERC1155RoyaltiesSoulbound public itemBound; - FreeMint public capsuleNft; - - uint256 public defaultBaseSkinId = 1; - uint256 public defaultCapsuleNftId = 0; - uint256 public specialItemId = 777888; - uint256 public defaultItemId = 100001; - - struct Wallet { - address addr; - uint256 privateKey; - } - - bytes public signature; - bytes public encodedItems; - uint256 public nonce; - - string public minterLabel = "minter"; - string public playerLabel = "player"; - - Wallet public minterWallet; - Wallet public playerWallet; - - uint256 private _seed; - uint256[] public _tokenItemsIds; - LibItems.TokenCreate[] public _tokens; - - function getWallet(string memory walletLabel) public returns (Wallet memory) { - (address addr, uint256 privateKey) = makeAddrAndKey(walletLabel); - Wallet memory wallet = Wallet(addr, privateKey); - return wallet; - } - - function concatenateStrings(string memory a, string memory b) internal pure returns (string memory) { - return string(abi.encodePacked(a, b)); - } - - function generateRandomItemId() internal returns (uint256) { - _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); - return _seed; - } - - function generateRandomLevel() internal returns (uint256) { - _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); - return (_seed % 10) + 1; // 1 - 10 - } - - function generateRandomTier() internal returns (uint256) { - _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); - uint256 random = _seed % 5; // 0 - 4 - - return random; - } - - function encode(uint256[] memory itemIds) public pure returns (bytes memory) { - return (abi.encode(itemIds)); - } - - function generateSignature( - address wallet, - bytes memory encodedItems, - string memory signerLabel - ) public returns (uint256, bytes memory) { - Wallet memory signerWallet = getWallet(signerLabel); - - uint256 _nonce = uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty, signerWallet.addr))) % - 50; - - bytes32 message = keccak256(abi.encodePacked(wallet, encodedItems, _nonce)); - bytes32 hash = ECDSA.toEthSignedMessageHash(message); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerWallet.privateKey, hash); - return (_nonce, abi.encodePacked(r, s, v)); - } - - function setupItems() internal returns (bytes memory) { - for (uint256 i = 0; i < 10; i++) { - uint256 _tokenId = generateRandomItemId(); // totally random - uint256 _level = generateRandomLevel(); // level 1-10 - uint256 _tier = generateRandomTier(); // tier 0-4 - - LibItems.TokenCreate memory _token = LibItems.TokenCreate({ - tokenId: _tokenId, - tokenUri: string(abi.encodePacked("https://something.com", "/", _tokenId.toString())) - }); - - _tokens.push(_token); - - _tokenItemsIds.push(_tokenId); - } - - LibItems.TokenCreate memory defaultItem = LibItems.TokenCreate({ tokenId: defaultItemId, tokenUri: "" }); - - _tokens.push(defaultItem); - - LibItems.TokenCreate memory specialItem = LibItems.TokenCreate({ tokenId: specialItemId, tokenUri: "" }); - - _tokens.push(specialItem); - - itemBound.addNewTokens(_tokens); - - encodedItems = encode(_tokenItemsIds); - return encodedItems; - } - - function setUp() public { - playerWallet = getWallet(playerLabel); - minterWallet = getWallet(minterLabel); - itemBound = new ERC1155RoyaltiesSoulbound( - "Test1155", - "T1155", - "MISSING_BASE_URL", - "MISSING_CONTRACT_URL", - 1, - false, - address(this) - ); - - capsuleNft = new FreeMint( - "OpenMint-TEST", - "OM_TEST", - "https://achievo.mypinata.cloud/ipfs/", - "QmPrH4o5q9uB8DGiFd9oDSuT3TnLiCzsFXT4wXQbpUr6c8" - ); - - encodedItems = setupItems(); - - avatarBound = new AvatarBound( - "Test", - "T", - "MISSING_BASE_URL", - "MISSING_CONTRACT_URL", - "MISSING_REVEAL_CAPSULE_URL", - "https://example.api.com", - address(minterWallet.addr), - address(capsuleNft), - address(itemBound), - true, - true, - true, - true - ); - - capsuleNft.grantRole(capsuleNft.MINTER_ROLE(), address(avatarBound)); - capsuleNft.grantRole(capsuleNft.MINTER_ROLE(), address(minterWallet.addr)); - - vm.startPrank(minterWallet.addr); - capsuleNft.safeMint(playerWallet.addr); - avatarBound.setBaseSkin(defaultBaseSkinId, "ipfs://{hash}/baseSkin/1.glb"); - avatarBound.setSpecialItemId(specialItemId); - avatarBound.setDefaultItemId(defaultItemId); - vm.stopPrank(); - - itemBound.grantRole(itemBound.MINTER_ROLE(), address(avatarBound)); - } - - function testMintAvatarNftGating() public { - vm.startPrank(playerWallet.addr); - (nonce, signature) = generateSignature(playerWallet.addr, encodedItems, minterLabel); - - avatarBound.mintAvatarNftGating(defaultCapsuleNftId, defaultBaseSkinId, nonce, encodedItems, signature); - - // missing assert to check the random items - assertEq(avatarBound.balanceOf(playerWallet.addr), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[0]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[1]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[2]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[3]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[4]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[5]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[6]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[7]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[8]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[9]), 1); - // check the default item Id - assertEq(itemBound.balanceOf(playerWallet.addr, defaultItemId), 0); - // check the special item Id - assertEq(itemBound.balanceOf(playerWallet.addr, specialItemId), 1); - // test that the capsule now has the new uri(revealed uri) - assertEq( - capsuleNft.tokenURI(defaultCapsuleNftId), - "https://achievo.mypinata.cloud/ipfs/MISSING_REVEAL_CAPSULE_URL" - ); - vm.stopPrank(); - } - - function testMintAvatarWithoutNftGating() public { - vm.startPrank(playerWallet.addr); - (nonce, signature) = generateSignature(playerWallet.addr, encodedItems, minterLabel); - - avatarBound.mintAvatar(defaultBaseSkinId, nonce, encodedItems, signature); - - // missing assert to check the random items - assertEq(avatarBound.balanceOf(playerWallet.addr), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[0]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[1]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[2]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[3]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[4]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[5]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[6]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[7]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[8]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[9]), 1); - - // check the default item Id - assertEq(itemBound.balanceOf(playerWallet.addr, defaultItemId), 1); - - // check the special item Id - assertEq(itemBound.balanceOf(playerWallet.addr, specialItemId), 0); - vm.stopPrank(); - } - - function testgetAllItems() public { - vm.startPrank(minterWallet.addr); - avatarBound.setBaseSkin(2, "ipfs://{hash}/baseSkin/2.glb"); - avatarBound.setBaseSkin(3, "ipfs://{hash}/baseSkin/3.glb"); - vm.stopPrank(); - vm.startPrank(playerWallet.addr); - AvatarBound.BaseSkinResponse[] memory allBaseSkins = avatarBound.getAllBaseSkins(); - assertEq(allBaseSkins.length, 3); - vm.stopPrank(); - } - - function testAdminMint() public { - vm.startPrank(minterWallet.addr); - avatarBound.adminMint(address(playerWallet.addr), 1); - assertEq(avatarBound.ownerOf(0), address(playerWallet.addr)); - } - - function testPauseUnpause() public { - vm.startPrank(minterWallet.addr); - avatarBound.grantRole(avatarBound.MANAGER_ROLE(), address(this)); - avatarBound.grantRole(avatarBound.MINTER_ROLE(), address(this)); - avatarBound.setBaseSkin(1, "ipfs://{hash}/1.glb"); - avatarBound.pause(); - vm.expectRevert("Pausable: paused"); - avatarBound.adminMint(address(this), 1); - vm.stopPrank(); - } - - function testSetBaseSkin() public { - vm.startPrank(minterWallet.addr); - avatarBound.setBaseSkin(2, "ipfs://{hash}/baseSkin/2.glb"); - assertEq(avatarBound.baseSkins(2), "ipfs://{hash}/baseSkin/2.glb"); - vm.stopPrank(); - } - - function testFailUnauthorizedTransfer() public { - // vm.prank(minterWallet.addr); - avatarBound.adminMint(address(playerWallet.addr), 1); - - vm.startPrank(playerWallet.addr); - vm.expectRevert("Achievo721Soulbound: Operation denied, soulbounded"); - avatarBound.transferFrom(address(playerWallet.addr), address(this), 0); - vm.stopPrank(); - } - - function testSetContractURI() public { - string memory newContractURI = "ipfs://newContractURI"; - vm.startPrank(minterWallet.addr); - avatarBound.setContractURI(newContractURI); - assertEq(avatarBound.contractURI(), newContractURI); - vm.stopPrank(); - } - - function testSetTokenURI() public { - vm.startPrank(minterWallet.addr); - avatarBound.adminMint(playerWallet.addr, 1); - avatarBound.setCompoundURIEnabled(false); - uint256 tokenId = 0; - string memory newURI = "/newURI1.glb"; - avatarBound.setTokenURI(tokenId, newURI); - // Assuming concatenateStrings is a helper function that correctly concatenates strings - assertEq(avatarBound.tokenURI(tokenId), concatenateStrings(avatarBound.baseTokenURI(), newURI)); - vm.stopPrank(); - } - - function testSetBaseURI() public { - string memory newBaseURI = "ipfs://newBaseURI/"; - vm.startPrank(minterWallet.addr); - avatarBound.setCompoundURIEnabled(false); - avatarBound.setBaseURI(newBaseURI); - assertEq(avatarBound.baseTokenURI(), newBaseURI); - vm.stopPrank(); - } - - function testCompoundURI() public { - vm.startPrank(minterWallet.addr); - avatarBound.adminMint(playerWallet.addr, 1); - string memory compoundURI = "https://this.is.a.compound.uri.endpoint/avatar"; - avatarBound.setCompoundURI(compoundURI); - uint256 tokenId = 0; - string memory finalURI = string( - abi.encodePacked( - compoundURI, - "/", - Strings.toHexString(uint160(address(avatarBound)), 20), - "/", - Strings.toString(tokenId) - ) - ); - assertEq(avatarBound.tokenURI(tokenId), finalURI); - vm.stopPrank(); - } - - function onERC721Received( - address operator, - address from, - uint256 tokenId, - bytes calldata data - ) public returns (bytes4) { - return this.onERC721Received.selector; - } - - function onERC1155Received( - address operator, - address from, - uint256 id, - uint256 value, - bytes calldata data - ) public returns (bytes4) { - return this.onERC1155Received.selector; - } -} diff --git a/test/AvatarBoundV1.t.sol b/test/AvatarBoundV1.t.sol deleted file mode 100644 index fa45c1e4..00000000 --- a/test/AvatarBoundV1.t.sol +++ /dev/null @@ -1,370 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.17; - -import "forge-std/Test.sol"; -import "forge-std/StdCheats.sol"; - -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { ECDSAUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol"; - -import { LibItems, TestLibItems } from "../contracts/libraries/LibItems.sol"; -import { AvatarBoundV1 } from "../contracts/upgradeables/games/AvatarBoundV1.sol"; -import { ERC1155RoyaltiesSoulboundV1 } from "../contracts/upgradeables/soulbounds/ERC1155RoyaltiesSoulboundV1.sol"; -import { FreeMint } from "../contracts/airdrops/FreeMint.sol"; -import { MockERC721Receiver } from "../contracts/mocks/MockERC721Receiver.sol"; -import { MockERC1155Receiver } from "../contracts/mocks/MockERC1155Receiver.sol"; - -contract AvatarBoundV1Test is StdCheats, Test { - using Strings for uint256; - - address itemBoundV1Address; - address AvatarBoundV1Address; - - AvatarBoundV1 public avatarBound; - ERC1155RoyaltiesSoulboundV1 public itemBound; - FreeMint public capsuleNft; - - MockERC721Receiver public mockERC721Receiver; - MockERC1155Receiver public mockERC1155Receiver; - - uint256 public defaultBaseSkinId = 1; - uint256 public defaultCapsuleNftId = 0; - uint256 public specialItemId = 777888; - uint256 public defaultItemId = 100001; - - struct Wallet { - address addr; - uint256 privateKey; - } - - bytes public signature; - bytes public encodedItems; - uint256 public nonce; - - string public minterLabel = "minter"; - string public playerLabel = "player"; - - Wallet public minterWallet; - Wallet public playerWallet; - - uint256 private _seed; - uint256[] public _tokenItemsIds; - LibItems.TokenCreate[] public _tokens; - - function getWallet(string memory walletLabel) public returns (Wallet memory) { - (address addr, uint256 privateKey) = makeAddrAndKey(walletLabel); - Wallet memory wallet = Wallet(addr, privateKey); - return wallet; - } - - function concatenateStrings(string memory a, string memory b) internal pure returns (string memory) { - return string(abi.encodePacked(a, b)); - } - - function generateRandomItemId() internal returns (uint256) { - _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); - return _seed; - } - - function generateRandomLevel() internal returns (uint256) { - _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); - return (_seed % 10) + 1; // 1 - 10 - } - - function generateRandomTier() internal returns (uint256) { - _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); - uint256 random = _seed % 5; // 0 - 4 - - return random; - } - - function encode(uint256[] memory itemIds) public pure returns (bytes memory) { - return (abi.encode(itemIds)); - } - - function generateSignature( - address wallet, - bytes memory encodedItems, - string memory signerLabel - ) public returns (uint256, bytes memory) { - Wallet memory signerWallet = getWallet(signerLabel); - - uint256 _nonce = uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty, signerWallet.addr))) % - 50; - - bytes32 message = keccak256(abi.encodePacked(wallet, encodedItems, _nonce)); - bytes32 hash = ECDSAUpgradeable.toEthSignedMessageHash(message); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerWallet.privateKey, hash); - return (_nonce, abi.encodePacked(r, s, v)); - } - - function setupItems() internal returns (bytes memory) { - for (uint256 i = 0; i < 10; i++) { - uint256 _tokenId = generateRandomItemId(); // totally random - uint256 _level = generateRandomLevel(); // level 1-10 - uint256 _tier = generateRandomTier(); // tier 0-4 - - LibItems.TokenCreate memory _token = LibItems.TokenCreate({ - tokenId: _tokenId, - tokenUri: string(abi.encodePacked("https://something.com", "/", _tokenId.toString())) - }); - - _tokens.push(_token); - - _tokenItemsIds.push(_tokenId); - } - - LibItems.TokenCreate memory defaultItem = LibItems.TokenCreate({ tokenId: defaultItemId, tokenUri: "" }); - - _tokens.push(defaultItem); - - LibItems.TokenCreate memory specialItem = LibItems.TokenCreate({ tokenId: specialItemId, tokenUri: "" }); - - _tokens.push(specialItem); - - itemBound.addNewTokens(_tokens); - - encodedItems = encode(_tokenItemsIds); - return encodedItems; - } - - function deployAvatarBoundV1Contract() public returns (AvatarBoundV1) { - AvatarBoundV1 avatarBoundV1 = new AvatarBoundV1(); - ERC1967Proxy proxy = new ERC1967Proxy(address(avatarBoundV1), ""); - AvatarBoundV1(address(proxy)).initialize( - "Test", - "T", - "MISSING_BASE_URL", - "MISSING_CONTRACT_URL", - "MISSING_REVEAL_CAPSULE_URL", - "https://api.example/ipfs", - address(minterWallet.addr), - address(capsuleNft), - address(itemBound), - true, - true, - true, - true - ); - - return AvatarBoundV1(address(proxy)); - } - - function deployItemBoundV1Contract() public returns (ERC1155RoyaltiesSoulboundV1) { - ERC1155RoyaltiesSoulboundV1 itemBoundV1 = new ERC1155RoyaltiesSoulboundV1(); - ERC1967Proxy proxy = new ERC1967Proxy(address(itemBoundV1), ""); - ERC1155RoyaltiesSoulboundV1(address(proxy)).initialize( - "Test1155", - "T1155", - "MISSING_BASE_URL", - "MISSING_CONTRACT_URL", - 1, - false, - address(this) - ); - - return ERC1155RoyaltiesSoulboundV1(address(proxy)); - } - - function setUp() public { - playerWallet = getWallet(playerLabel); - minterWallet = getWallet(minterLabel); - - itemBound = deployItemBoundV1Contract(); - - capsuleNft = new FreeMint( - "OpenMint-TEST", - "OM_TEST", - "https://achievo.mypinata.cloud/ipfs/", - "QmPrH4o5q9uB8DGiFd9oDSuT3TnLiCzsFXT4wXQbpUr6c8" - ); - - encodedItems = setupItems(); - - avatarBound = deployAvatarBoundV1Contract(); - - capsuleNft.grantRole(capsuleNft.MINTER_ROLE(), address(avatarBound)); - capsuleNft.grantRole(capsuleNft.MINTER_ROLE(), address(minterWallet.addr)); - - vm.startPrank(minterWallet.addr); - capsuleNft.safeMint(playerWallet.addr); - avatarBound.setBaseSkin(defaultBaseSkinId, "ipfs://{hash}/baseSkin/1.glb"); - avatarBound.setSpecialItemId(specialItemId); - avatarBound.setDefaultItemId(defaultItemId); - vm.stopPrank(); - - itemBound.grantRole(itemBound.MINTER_ROLE(), address(avatarBound)); - } - - function testMintAvatarNftGating() public { - vm.startPrank(playerWallet.addr); - (nonce, signature) = generateSignature(playerWallet.addr, encodedItems, minterLabel); - - avatarBound.mintAvatarNftGating(defaultCapsuleNftId, defaultBaseSkinId, nonce, encodedItems, signature); - - // missing assert to check the random items - assertEq(avatarBound.balanceOf(playerWallet.addr), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[0]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[1]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[2]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[3]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[4]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[5]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[6]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[7]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[8]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[9]), 1); - // check the default item Id - assertEq(itemBound.balanceOf(playerWallet.addr, defaultItemId), 0); - // check the special item Id - assertEq(itemBound.balanceOf(playerWallet.addr, specialItemId), 1); - // test that the capsule now has the new uri(revealed uri) - assertEq( - capsuleNft.tokenURI(defaultCapsuleNftId), - "https://achievo.mypinata.cloud/ipfs/MISSING_REVEAL_CAPSULE_URL" - ); - vm.stopPrank(); - } - - function testMintAvatarWithoutNftGating() public { - vm.startPrank(playerWallet.addr); - (nonce, signature) = generateSignature(playerWallet.addr, encodedItems, minterLabel); - - avatarBound.mintAvatar(defaultBaseSkinId, nonce, encodedItems, signature); - - // missing assert to check the random items - assertEq(avatarBound.balanceOf(playerWallet.addr), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[0]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[1]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[2]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[3]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[4]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[5]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[6]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[7]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[8]), 1); - assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[9]), 1); - - // check the default item Id - assertEq(itemBound.balanceOf(playerWallet.addr, defaultItemId), 1); - - // check the special item Id - assertEq(itemBound.balanceOf(playerWallet.addr, specialItemId), 0); - vm.stopPrank(); - } - - function testgetAllItems() public { - vm.startPrank(minterWallet.addr); - avatarBound.setBaseSkin(2, "ipfs://{hash}/baseSkin/2.glb"); - avatarBound.setBaseSkin(3, "ipfs://{hash}/baseSkin/3.glb"); - vm.stopPrank(); - vm.startPrank(playerWallet.addr); - AvatarBoundV1.BaseSkinResponse[] memory allBaseSkins = avatarBound.getAllBaseSkins(); - assertEq(allBaseSkins.length, 3); - vm.stopPrank(); - } - - function testAdminMint() public { - vm.startPrank(minterWallet.addr); - avatarBound.adminMint(address(playerWallet.addr), 1); - assertEq(avatarBound.ownerOf(0), address(playerWallet.addr)); - } - - function testPauseUnpause() public { - vm.startPrank(minterWallet.addr); - avatarBound.grantRole(avatarBound.MANAGER_ROLE(), address(this)); - avatarBound.grantRole(avatarBound.MINTER_ROLE(), address(this)); - avatarBound.setBaseSkin(1, "ipfs://{hash}/1.glb"); - avatarBound.pause(); - vm.expectRevert("Pausable: paused"); - avatarBound.adminMint(address(this), 1); - vm.stopPrank(); - } - - function testSetBaseSkin() public { - vm.startPrank(minterWallet.addr); - avatarBound.setBaseSkin(2, "ipfs://{hash}/baseSkin/2.glb"); - assertEq(avatarBound.baseSkins(2), "ipfs://{hash}/baseSkin/2.glb"); - vm.stopPrank(); - } - - function testFailUnauthorizedTransfer() public { - // vm.prank(minterWallet.addr); - avatarBound.adminMint(address(playerWallet.addr), 1); - - vm.startPrank(playerWallet.addr); - vm.expectRevert("Achievo721Soulbound: Operation denied, soulbounded"); - avatarBound.transferFrom(address(playerWallet.addr), address(this), 0); - vm.stopPrank(); - } - - function testSetContractURI() public { - string memory newContractURI = "ipfs://newContractURI"; - vm.startPrank(minterWallet.addr); - avatarBound.setContractURI(newContractURI); - assertEq(avatarBound.contractURI(), newContractURI); - vm.stopPrank(); - } - - function testSetTokenURI() public { - vm.startPrank(minterWallet.addr); - avatarBound.adminMint(playerWallet.addr, 1); - avatarBound.setCompoundURIEnabled(false); - uint256 tokenId = 0; - string memory newURI = "/newURI1.glb"; - avatarBound.setTokenURI(tokenId, newURI); - // Assuming concatenateStrings is a helper function that correctly concatenates strings - assertEq(avatarBound.tokenURI(tokenId), concatenateStrings(avatarBound.baseTokenURI(), newURI)); - vm.stopPrank(); - } - - function testSetBaseURI() public { - string memory newBaseURI = "ipfs://newBaseURI/"; - vm.startPrank(minterWallet.addr); - avatarBound.setCompoundURIEnabled(false); - avatarBound.setBaseURI(newBaseURI); - assertEq(avatarBound.baseTokenURI(), newBaseURI); - vm.stopPrank(); - } - - function testCompoundURI() public { - vm.startPrank(minterWallet.addr); - avatarBound.adminMint(playerWallet.addr, 1); - string memory compoundURI = "https://this.is.a.compound.uri.endpoint/avatar"; - avatarBound.setCompoundURI(compoundURI); - uint256 tokenId = 0; - string memory finalURI = string( - abi.encodePacked( - compoundURI, - "/", - Strings.toHexString(uint160(address(avatarBound)), 20), - "/", - Strings.toString(tokenId) - ) - ); - assertEq(avatarBound.tokenURI(tokenId), finalURI); - vm.stopPrank(); - } - - function onERC721Received( - address operator, - address from, - uint256 tokenId, - bytes calldata data - ) public returns (bytes4) { - return this.onERC721Received.selector; - } - - function onERC1155Received( - address operator, - address from, - uint256 id, - uint256 value, - bytes calldata data - ) public returns (bytes4) { - return this.onERC1155Received.selector; - } -} diff --git a/test/games/GameSummary.t.sol b/test/games/GameSummary.t.sol new file mode 100644 index 00000000..732cebb4 --- /dev/null +++ b/test/games/GameSummary.t.sol @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import "forge-std/StdCheats.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; + +import { GameSummary } from "../../contracts/games/GameSummary.sol"; +import { MockERC1155Receiver } from "../../contracts/mocks/MockERC1155Receiver.sol"; +import { LibItems, TestLibItems } from "../../contracts/libraries/LibItems.sol"; + +contract GameSummaryBoundTest is StdCheats, Test { + using Strings for uint256; + + GameSummary public gameSummary; + MockERC1155Receiver public mockERC1155Receiver; + + struct Wallet { + address addr; + uint256 privateKey; + } + + string public minterLabel = "minter"; + string public playerLabel = "player"; + string public player2Label = "player2"; + string public player3Label = "player3"; + + Wallet public minterWallet; + Wallet public playerWallet; + Wallet public playerWallet2; + Wallet public playerWallet3; + + uint256 public seed1 = 1234; + uint256 public seed2 = 4321; + uint256 public nonce; + bytes public signature; + bytes public encodedItems1; + uint256 public nonce2; + bytes public signature2; + bytes public encodedItems2; + + uint256 private _seed; + LibItems.TokenCreate[] public _tokens; + uint256[] public _tokenIds; + + uint256 public chainId = 31337; + + function getWallet(string memory walletLabel) public returns (Wallet memory) { + (address addr, uint256 privateKey) = makeAddrAndKey(walletLabel); + Wallet memory wallet = Wallet(addr, privateKey); + return wallet; + } + + function generateSignature( + address wallet, + bytes memory encodedItems, + string memory signerLabel + ) public returns (uint256, bytes memory) { + Wallet memory signerWallet = getWallet(signerLabel); + + uint256 _nonce = uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty, signerWallet.addr))) % + 50; + + bytes32 message = keccak256(abi.encodePacked(wallet, encodedItems, _nonce)); + bytes32 hash = ECDSA.toEthSignedMessageHash(message); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerWallet.privateKey, hash); + return (_nonce, abi.encodePacked(r, s, v)); + } + + function concatenateStrings(string memory a, string memory b) internal pure returns (string memory) { + return string(abi.encodePacked(a, b)); + } + + function generateRandomStoreId() internal returns (uint256) { + _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); + return _seed; + } + + function generateRandomPlayerId() internal returns (uint256) { + uint256 _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); + return _seed % 1000000; + } + + function generateRandomGameId() internal returns (uint256) { + uint256 _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); + return _seed % 10000000; + } + + function generateTokenId(uint256 storeId, uint256 playerId, uint256 gameId) internal returns (uint256) { + uint256 tokenId = uint256(keccak256(abi.encode(storeId, playerId, gameId))); + return tokenId; + } + + function encode(address contractAddress, uint256[] memory itemIds) public view returns (bytes memory) { + return (abi.encode(contractAddress, chainId, itemIds)); + } + + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = bytes1(uint8(value & 0xf)); + value >>= 4; + if (i % 2 == 0) { + buffer[i] = bytes1(uint8(buffer[i]) + (bytes1(uint8(buffer[i])) < bytes1(uint8(0xa)) ? 48 : 87)); + } + } + return string(buffer); + } + function setUp() public { + playerWallet = getWallet(playerLabel); + playerWallet2 = getWallet(player2Label); + playerWallet3 = getWallet(player3Label); + minterWallet = getWallet(minterLabel); + + gameSummary = new GameSummary(address(this)); + + gameSummary.initialize( + "Test1155", + "T1155", + "MISSING_BASE_URL", + "MISSING_CONTRACT_URL", + "https://example.api.com", + 1, + false, + address(this) + ); + + gameSummary.addWhitelistSigner(minterWallet.addr); + + mockERC1155Receiver = new MockERC1155Receiver(); + + for (uint256 i = 0; i < 1300; i++) { + uint256 _storeId = generateRandomStoreId(); + uint256 _playerId = generateRandomPlayerId(); + uint256 _gameId = generateRandomGameId(); + uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); + + LibItems.TokenCreate memory _token = LibItems.TokenCreate({ + tokenId: _tokenId, + tokenUri: string(abi.encodePacked("https://something.com", "/", _tokenId.toString())) + }); + + _tokens.push(_token); + _tokenIds.push(_tokenId); + } + + gameSummary.addNewTokens(_tokens); + + uint256[] memory _itemIds1 = new uint256[](3); + _itemIds1[0] = _tokenIds[0]; + _itemIds1[1] = _tokenIds[1]; + _itemIds1[2] = _tokenIds[2]; + + encodedItems1 = encode(address(gameSummary), _itemIds1); + + uint256[] memory _itemIds2 = new uint256[](3); + _itemIds2[0] = _tokenIds[3]; + _itemIds2[1] = _tokenIds[4]; + _itemIds2[2] = _tokenIds[5]; + + encodedItems2 = encode(address(gameSummary), _itemIds2); + + (nonce, signature) = generateSignature(playerWallet.addr, encodedItems1, minterLabel); + (nonce2, signature2) = generateSignature(playerWallet2.addr, encodedItems2, minterLabel); + } + + function testTokenExists() public { + uint256 _tokenId = generateRandomStoreId(); + + vm.expectRevert("TokenNotExist"); + gameSummary.isTokenExist(_tokenId); + + vm.expectRevert("TokenNotExist"); + gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, true); + + LibItems.TokenCreate memory _token = LibItems.TokenCreate({ + tokenId: _tokenId, + tokenUri: string(abi.encodePacked("https://something222.com", "/", _tokenId.toString())) + }); + + gameSummary.addNewToken(_token); + gameSummary.isTokenExist(_tokenId); + gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, true); + } + + function testAddNewTokens() public { + LibItems.TokenCreate[] memory _tokens = new LibItems.TokenCreate[](3); + + skip(36000); + for (uint256 i = 0; i < 3; i++) { + uint256 _storeId = generateRandomStoreId(); + uint256 _playerId = generateRandomPlayerId(); + uint256 _gameId = generateRandomGameId(); + uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); + + LibItems.TokenCreate memory _token = LibItems.TokenCreate({ + tokenId: _tokenId, + tokenUri: string(abi.encodePacked("https://something.com", "/", _tokenId.toString())) + }); + + _tokens[i] = _token; + } + + gameSummary.addNewTokens(_tokens); + } + + function testPauseUnpause() public { + uint256 _tokenId = _tokenIds[0]; + + gameSummary.pause(); + vm.expectRevert("Pausable: paused"); + gameSummary.adminMintId(address(this), _tokenId, 1, true); + gameSummary.unpause(); + + gameSummary.adminMintId(address(mockERC1155Receiver), _tokenId, 1, true); + assertEq(gameSummary.balanceOf(address(mockERC1155Receiver), _tokenId), 1); + } + + function testPauseUnpauseSpecificToken() public { + uint256 _tokenId = _tokenIds[0]; + + gameSummary.updateTokenMintPaused(_tokenId, true); + + vm.expectRevert("TokenMintPaused"); + gameSummary.adminMintId(address(mockERC1155Receiver), _tokenId, 1, true); + + vm.expectRevert("TokenMintPaused"); + gameSummary.adminMint(address(mockERC1155Receiver), encodedItems1, true); + + vm.expectRevert("TokenMintPaused"); + vm.prank(playerWallet.addr); + gameSummary.mint(encodedItems1, 1, true, nonce, signature); + + gameSummary.updateTokenMintPaused(_tokenId, false); + + vm.prank(playerWallet.addr); + gameSummary.mint(encodedItems1, 1, true, nonce, signature); + + assertEq(gameSummary.balanceOf(playerWallet.addr, _tokenId), 1); + } + + // testVerifySignature + function testInvalidSignature() public { + vm.prank(playerWallet.addr); + vm.expectRevert("InvalidSignature"); + gameSummary.mint(encodedItems1, 1, true, nonce, signature2); + } + + function testReuseSignatureMint() public { + vm.prank(playerWallet.addr); + gameSummary.mint(encodedItems1, 1, true, nonce, signature); + vm.prank(playerWallet.addr); + vm.expectRevert("AlreadyUsedSignature"); + gameSummary.mint(encodedItems1, 1, true, nonce, signature); + } + + function testMintShouldPass() public { + vm.prank(playerWallet.addr); + gameSummary.mint(encodedItems1, 1, true, nonce, signature); + + vm.expectRevert( + "Achievo1155Soulbound: The amount of soulbounded tokens is more than the amount of tokens to be transferred" + ); + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenIds[0], 1, ""); + + vm.expectRevert("Achievo1155Soulbound: can't be zero amount"); + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenIds[0], 0, ""); + + vm.prank(playerWallet2.addr); + gameSummary.mint(encodedItems2, 1, false, nonce2, signature2); + + vm.prank(playerWallet2.addr); + gameSummary.safeTransferFrom(playerWallet2.addr, minterWallet.addr, _tokenIds[3], 1, ""); + + assertEq(gameSummary.balanceOf(playerWallet.addr, _tokenIds[0]), 1); + assertEq(gameSummary.balanceOf(playerWallet2.addr, _tokenIds[3]), 0); + assertEq(gameSummary.balanceOf(minterWallet.addr, _tokenIds[3]), 1); + } + + function testMintMoreThanLimit() public { + vm.expectRevert("ExceedMaxMint"); + vm.prank(playerWallet.addr); + gameSummary.mint(encodedItems1, 2, true, nonce, signature); + } + + function testMintInvalidTokenId() public { + uint256[] memory _itemIds3 = new uint256[](3); + _itemIds3[0] = 1233; + _itemIds3[1] = 3322; + + bytes memory encodedItems3 = encode(address(gameSummary), _itemIds3); + + (uint256 _nonce, bytes memory _signature) = generateSignature(playerWallet.addr, encodedItems3, minterLabel); + + vm.expectRevert("TokenNotExist"); + vm.prank(playerWallet.addr); + gameSummary.mint(encodedItems3, 1, true, _nonce, _signature); + } + + function testAdminMintNotMinterRole() public { + vm.expectRevert( + "AccessControl: account 0x44e97af4418b7a17aabd8090bea0a471a366305c is missing role 0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6" + ); + vm.prank(playerWallet.addr); + gameSummary.adminMint(playerWallet.addr, encodedItems1, true); + } + + function testAdminMint() public { + gameSummary.adminMint(address(mockERC1155Receiver), encodedItems1, true); + assertEq(gameSummary.balanceOf(address(mockERC1155Receiver), _tokenIds[0]), 1); + assertEq(gameSummary.balanceOf(address(mockERC1155Receiver), _tokenIds[1]), 1); + assertEq(gameSummary.balanceOf(address(mockERC1155Receiver), _tokenIds[2]), 1); + } + + function testAdminMintIdNotMinterRole() public { + uint256 _tokenId = _tokenIds[0]; + vm.expectRevert( + "AccessControl: account 0x44e97af4418b7a17aabd8090bea0a471a366305c is missing role 0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6" + ); + vm.prank(playerWallet.addr); + gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, true); + } + + function testAdminMintId() public { + uint256 _tokenId = _tokenIds[0]; + gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, true); + assertEq(gameSummary.balanceOf(playerWallet.addr, _tokenIds[0]), 1); + } + + function testBurnNotOwnerShouldFail() public { + vm.prank(playerWallet.addr); + gameSummary.mint(encodedItems1, 1, false, nonce, signature); + assertEq(gameSummary.balanceOf(playerWallet.addr, _tokenIds[0]), 1); + + vm.expectRevert("ERC1155: caller is not token owner or approved"); + vm.prank(playerWallet2.addr); + gameSummary.burn(playerWallet.addr, _tokenIds[0], 1); + } + + function testBurn() public { + vm.prank(playerWallet.addr); + gameSummary.mint(encodedItems1, 1, true, nonce, signature); + assertEq(gameSummary.balanceOf(playerWallet.addr, _tokenIds[0]), 1); + + vm.expectRevert( + "Achievo1155Soulbound: The amount of soulbounded tokens is more than the amount of tokens to be transferred" + ); + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenIds[0], 1, ""); + + vm.expectRevert( + "Achievo1155Soulbound: The amount of soulbounded tokens is more than the amount of tokens to be transferred" + ); + vm.prank(playerWallet.addr); + gameSummary.burn(playerWallet.addr, _tokenIds[0], 1); + + vm.prank(playerWallet2.addr); + gameSummary.mint(encodedItems2, 1, false, nonce2, signature2); + + vm.prank(playerWallet2.addr); + gameSummary.safeTransferFrom(playerWallet2.addr, playerWallet3.addr, _tokenIds[3], 1, ""); + + assertEq(gameSummary.balanceOf(playerWallet2.addr, _tokenIds[3]), 0); + assertEq(gameSummary.balanceOf(playerWallet3.addr, _tokenIds[3]), 1); + + vm.prank(playerWallet3.addr); + gameSummary.burn(playerWallet3.addr, _tokenIds[3], 1); + + assertEq(gameSummary.balanceOf(playerWallet3.addr, _tokenIds[3]), 0); + } + + function testBurnIfHoldBothNonSoulboundAndSouldbound() public { + vm.prank(playerWallet.addr); + gameSummary.mint(encodedItems1, 1, true, nonce, signature); + + gameSummary.adminMint(playerWallet2.addr, encodedItems1, false); + + vm.prank(playerWallet2.addr); + gameSummary.safeTransferFrom(playerWallet2.addr, playerWallet.addr, _tokenIds[0], 1, ""); + + assertEq(gameSummary.balanceOf(playerWallet.addr, _tokenIds[0]), 2); + + vm.expectRevert( + "Achievo1155Soulbound: The amount of soulbounded tokens is more than the amount of tokens to be transferred" + ); + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenIds[0], 2, ""); + + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenIds[0], 1, ""); + } + + function testBurnBatchNotOwnerShouldFail() public { + uint256[] memory _itemIds1 = new uint256[](3); + _itemIds1[0] = _tokenIds[0]; + _itemIds1[1] = _tokenIds[1]; + _itemIds1[2] = _tokenIds[2]; + + uint256[] memory _amount1 = new uint256[](3); + _amount1[0] = 1; + _amount1[1] = 1; + _amount1[2] = 1; + + vm.prank(playerWallet.addr); + gameSummary.mint(encodedItems1, 1, false, nonce, signature); + assertEq(gameSummary.balanceOf(playerWallet.addr, _tokenIds[0]), 1); + + vm.expectRevert("ERC1155: caller is not token owner or approved"); + vm.prank(playerWallet2.addr); + gameSummary.burnBatch(playerWallet.addr, _itemIds1, _amount1); + } + + function testBurnBatch() public { + uint256[] memory _itemIds1 = new uint256[](3); + _itemIds1[0] = _tokenIds[0]; + _itemIds1[1] = _tokenIds[1]; + _itemIds1[2] = _tokenIds[2]; + + uint256[] memory _itemIds2 = new uint256[](3); + _itemIds2[0] = _tokenIds[3]; + _itemIds2[1] = _tokenIds[4]; + _itemIds2[2] = _tokenIds[5]; + + uint256[] memory _amount1 = new uint256[](3); + _amount1[0] = 1; + _amount1[1] = 1; + _amount1[2] = 1; + + vm.prank(playerWallet.addr); + gameSummary.mint(encodedItems1, 1, true, nonce, signature); + assertEq(gameSummary.balanceOf(playerWallet.addr, _tokenIds[0]), 1); + + vm.expectRevert( + "Achievo1155Soulbound: The amount of soulbounded tokens is more than the amount of tokens to be transferred" + ); + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenIds[0], 1, ""); + + vm.expectRevert( + "Achievo1155Soulbound: The amount of soulbounded tokens is more than the amount of tokens to be transferred" + ); + vm.prank(playerWallet.addr); + gameSummary.burnBatch(playerWallet.addr, _itemIds1, _amount1); + + vm.prank(playerWallet2.addr); + gameSummary.mint(encodedItems2, 1, false, nonce2, signature2); + + vm.prank(playerWallet2.addr); + gameSummary.safeTransferFrom(playerWallet2.addr, playerWallet3.addr, _tokenIds[3], 1, ""); + vm.prank(playerWallet2.addr); + gameSummary.safeTransferFrom(playerWallet2.addr, playerWallet3.addr, _tokenIds[4], 1, ""); + vm.prank(playerWallet2.addr); + gameSummary.safeTransferFrom(playerWallet2.addr, playerWallet3.addr, _tokenIds[5], 1, ""); + + assertEq(gameSummary.balanceOf(playerWallet2.addr, _tokenIds[3]), 0); + assertEq(gameSummary.balanceOf(playerWallet3.addr, _tokenIds[3]), 1); + + vm.prank(playerWallet3.addr); + gameSummary.burnBatch(playerWallet3.addr, _itemIds2, _amount1); + + assertEq(gameSummary.balanceOf(playerWallet3.addr, _tokenIds[3]), 0); + } + + function testBatchTransferFrom() public { + uint256[] memory _itemIds1 = new uint256[](3); + _itemIds1[0] = _tokenIds[0]; + _itemIds1[1] = _tokenIds[1]; + _itemIds1[2] = _tokenIds[2]; + + uint256[] memory _itemIds2 = new uint256[](3); + _itemIds2[0] = _tokenIds[3]; + _itemIds2[1] = _tokenIds[4]; + _itemIds2[2] = _tokenIds[5]; + + uint256[] memory _amount1 = new uint256[](3); + _amount1[0] = 1; + _amount1[1] = 1; + _amount1[2] = 1; + + vm.prank(playerWallet.addr); + gameSummary.mint(encodedItems1, 1, true, nonce, signature); + assertEq(gameSummary.balanceOf(playerWallet.addr, _tokenIds[0]), 1); + + gameSummary.adminMint(playerWallet2.addr, encodedItems1, false); + + vm.prank(playerWallet2.addr); + gameSummary.safeTransferFrom(playerWallet2.addr, playerWallet.addr, _tokenIds[0], 1, ""); + + assertEq(gameSummary.balanceOf(playerWallet.addr, _tokenIds[0]), 2); + + uint256[] memory _itemIds3 = new uint256[](2); + _itemIds3[0] = _tokenIds[0]; + _itemIds3[1] = _tokenIds[0]; + + uint256[] memory _amount3 = new uint256[](2); + _amount3[0] = 1; + _amount3[1] = 1; + + vm.expectRevert("ERC1155: duplicate ID"); + vm.prank(playerWallet.addr); + gameSummary.safeBatchTransferFrom(playerWallet.addr, minterWallet.addr, _itemIds3, _amount3, ""); + + assertEq(gameSummary.balanceOf(minterWallet.addr, _tokenIds[0]), 0); + + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenIds[0], 1, ""); + assertEq(gameSummary.balanceOf(minterWallet.addr, _tokenIds[0]), 1); + } + + function testTokenURIIfTokenIdNotExist() public { + vm.expectRevert("TokenNotExist"); + gameSummary.uri(1); + } + + function testTokenURIIfTokenIdExistNOSpeficTokenURIFallbackToBaseURI() public { + uint256 _storeId = generateRandomStoreId(); + uint256 _playerId = generateRandomPlayerId(); + uint256 _gameId = generateRandomGameId(); + uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); + LibItems.TokenCreate memory _token = LibItems.TokenCreate({ tokenId: _tokenId, tokenUri: "" }); + + gameSummary.addNewToken(_token); + + gameSummary.setCompoundURIEnabled(false); + + assertEq(gameSummary.uri(_tokenId), string(abi.encodePacked("MISSING_BASE_URL", "/", _tokenId.toString()))); + } + + function testTokenURIIfTokenIdExistWithSpeficTokenURI() public { + uint256 _storeId = generateRandomStoreId(); + uint256 _playerId = generateRandomPlayerId(); + uint256 _gameId = generateRandomGameId(); + uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); + + LibItems.TokenCreate memory _token = LibItems.TokenCreate({ + tokenId: _tokenId, + tokenUri: "ipfs://specific-token-uri.com" + }); + + gameSummary.addNewToken(_token); + + gameSummary.setCompoundURIEnabled(false); + + assertEq(gameSummary.uri(_tokenId), "ipfs://specific-token-uri.com"); + } + + function testUpdateTokenBaseURIFailNotDevConfigRole() public { + string memory newBaseURI = "https://something-new.com"; + + vm.expectRevert( + "AccessControl: account 0x44e97af4418b7a17aabd8090bea0a471a366305c is missing role 0x3b359cf0b4471a5de84269135285268e64ac56f52d3161392213003a780ad63b" + ); + vm.prank(playerWallet.addr); + gameSummary.setBaseUri(newBaseURI); + } + + function testUpdateTokenBaseURIPass() public { + uint256 _storeId = generateRandomStoreId(); + uint256 _playerId = generateRandomPlayerId(); + uint256 _gameId = generateRandomGameId(); + uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); + LibItems.TokenCreate memory _token = LibItems.TokenCreate({ tokenId: _tokenId, tokenUri: "" }); + + gameSummary.addNewToken(_token); + + gameSummary.setCompoundURIEnabled(false); + + string memory newBaseURI = "https://something-new.com"; + + assertEq(gameSummary.uri(_tokenId), string(abi.encodePacked("MISSING_BASE_URL", "/", _tokenId.toString()))); + gameSummary.setBaseUri(newBaseURI); + assertEq( + gameSummary.uri(_tokenId), + string(abi.encodePacked("https://something-new.com", "/", _tokenId.toString())) + ); + } + + function testUpdateTokenURIFailNoDevConfigRole() public { + string memory newTokenUri = "https://something-new.com/232"; + + vm.expectRevert( + "AccessControl: account 0x44e97af4418b7a17aabd8090bea0a471a366305c is missing role 0x3b359cf0b4471a5de84269135285268e64ac56f52d3161392213003a780ad63b" + ); + vm.prank(playerWallet.addr); + gameSummary.updateTokenUri(0, newTokenUri); + } + + function testUpdateTokenURIPass() public { + uint256 _storeId = generateRandomStoreId(); + uint256 _playerId = generateRandomPlayerId(); + uint256 _gameId = generateRandomGameId(); + uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); + + LibItems.TokenCreate memory _token = LibItems.TokenCreate({ tokenId: _tokenId, tokenUri: "" }); + + gameSummary.addNewToken(_token); + + gameSummary.setCompoundURIEnabled(false); + + string memory newTokenUri = "https://something-new.com/232"; + + assertEq(gameSummary.uri(_tokenId), string(abi.encodePacked("MISSING_BASE_URL", "/", _tokenId.toString()))); + gameSummary.updateTokenUri(_tokenId, newTokenUri); + assertEq(gameSummary.uri(_tokenId), "https://something-new.com/232"); + } + + function testUpdateCompountURIPass() public { + uint256 _storeId = generateRandomStoreId(); + uint256 _playerId = generateRandomPlayerId(); + uint256 _gameId = generateRandomGameId(); + uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); + + LibItems.TokenCreate memory _token = LibItems.TokenCreate({ tokenId: _tokenId, tokenUri: "" }); + + gameSummary.addNewToken(_token); + string memory newCompoundUri = "https://something-new.com/232"; + + assertEq( + gameSummary.uri(_tokenId), + string( + abi.encodePacked( + "https://example.api.com/", + Strings.toHexString(uint160(address(gameSummary)), 20), + "/", + _tokenId.toString() + ) + ) + ); + + gameSummary.setCompoundURI(newCompoundUri); + + assertEq( + gameSummary.uri(_tokenId), + string( + abi.encodePacked( + "https://something-new.com/232/", + Strings.toHexString(uint160(address(gameSummary)), 20), + "/", + _tokenId.toString() + ) + ) + ); + } + + function testNonSoulboundTokenTransfer() public { + uint256 _tokenId = _tokenIds[0]; + gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, false); + + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenId, 1, ""); + + assertEq(gameSummary.balanceOf(playerWallet.addr, _tokenId), 0); + assertEq(gameSummary.balanceOf(minterWallet.addr, _tokenId), 1); + } + + function testSoulboundTokenNotTransfer() public { + uint256 _tokenId = _tokenIds[0]; + gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, true); + + vm.expectRevert( + "Achievo1155Soulbound: The amount of soulbounded tokens is more than the amount of tokens to be transferred" + ); + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenId, 1, ""); + + vm.expectRevert("Achievo1155Soulbound: can't be zero amount"); + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenId, 0, ""); + } + + function testSoulboundTokenTransferOnlyWhitelistAddresses() public { + uint256 _tokenId = _tokenIds[0]; + gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, true); + + vm.expectRevert( + "Achievo1155Soulbound: The amount of soulbounded tokens is more than the amount of tokens to be transferred" + ); + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, playerWallet3.addr, _tokenId, 1, ""); + + gameSummary.updateWhitelistAddress(playerWallet3.addr, true); + + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, playerWallet3.addr, _tokenId, 1, ""); + + vm.prank(playerWallet3.addr); + gameSummary.safeTransferFrom(playerWallet3.addr, playerWallet.addr, _tokenId, 1, ""); + + gameSummary.updateWhitelistAddress(playerWallet3.addr, false); + + vm.expectRevert( + "Achievo1155Soulbound: The amount of soulbounded tokens is more than the amount of tokens to be transferred" + ); + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, playerWallet3.addr, _tokenId, 1, ""); + } + + function testgetAllItems() public { + gameSummary.setCompoundURIEnabled(false); + + bytes memory encodedItemsAll = encode(address(gameSummary), _tokenIds); + gameSummary.adminMint(playerWallet.addr, encodedItemsAll, false); + + string memory newTokenUri = "https://something-new.com/232"; + gameSummary.updateTokenUri(_tokenIds[23], newTokenUri); + assertEq(gameSummary.uri(_tokenIds[23]), "https://something-new.com/232"); + + vm.prank(playerWallet.addr); + LibItems.TokenReturn[] memory allTokensInfo = gameSummary.getAllItems(); + assertEq(allTokensInfo.length, 1300); + + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenIds[24], 1, ""); + + vm.prank(playerWallet.addr); + LibItems.TokenReturn[] memory allTokensInfo2 = gameSummary.getAllItems(); + assertEq(allTokensInfo2.length, 1299); + + for (uint256 i = 0; i < allTokensInfo.length; i++) { + assertEq(allTokensInfo[i].tokenId, _tokenIds[i]); + + if (i == 23) { + assertEq(allTokensInfo[i].tokenUri, newTokenUri); + assertEq(allTokensInfo[i].amount, 1); + } else { + assertEq(allTokensInfo[i].amount, 1); + assertEq( + allTokensInfo[i].tokenUri, + string(abi.encodePacked("https://something.com", "/", _tokenIds[i].toString())) + ); + } + } + + vm.prank(minterWallet.addr); + LibItems.TokenReturn[] memory allTokensInfo3 = gameSummary.getAllItems(); + assertEq(allTokensInfo3.length, 1); + } + + function testgetAllItemsAdmin() public { + gameSummary.setCompoundURIEnabled(false); + bytes memory encodedItemsAll = encode(address(gameSummary), _tokenIds); + gameSummary.adminMint(playerWallet.addr, encodedItemsAll, false); + + string memory newTokenUri = "https://something-new.com/232"; + gameSummary.updateTokenUri(_tokenIds[23], newTokenUri); + assertEq(gameSummary.uri(_tokenIds[23]), "https://something-new.com/232"); + + LibItems.TokenReturn[] memory allTokensInfo = gameSummary.getAllItemsAdmin(playerWallet.addr); + assertEq(allTokensInfo.length, 1300); + + vm.prank(playerWallet.addr); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenIds[24], 1, ""); + + LibItems.TokenReturn[] memory allTokensInfo2 = gameSummary.getAllItemsAdmin(playerWallet.addr); + assertEq(allTokensInfo2.length, 1300); + + for (uint256 i = 0; i < allTokensInfo.length; i++) { + assertEq(allTokensInfo[i].tokenId, _tokenIds[i]); + + if (i == 23) { + assertEq(allTokensInfo[i].tokenUri, newTokenUri); + assertEq(allTokensInfo[i].amount, 1); + } else { + assertEq(allTokensInfo[i].amount, 1); + assertEq( + allTokensInfo[i].tokenUri, + string(abi.encodePacked("https://something.com", "/", _tokenIds[i].toString())) + ); + } + } + + LibItems.TokenReturn[] memory allTokensInfo3 = gameSummary.getAllItemsAdmin(minterWallet.addr); + assertEq(allTokensInfo3.length, 1300); + } +} diff --git a/test/hardhatTests/gameSummary1155.test.ts b/test/hardhatTests/gameSummary1155.test.ts deleted file mode 100644 index e2ca07a6..00000000 --- a/test/hardhatTests/gameSummary1155.test.ts +++ /dev/null @@ -1,770 +0,0 @@ -import { expect } from 'chai'; -// @ts-ignore-next-line -import { ethers } from 'hardhat'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { GameSummary } from '../../typechain-types'; -import { generateSignature } from '../../helpers/signature'; -import { hashIds } from '../../helpers/hashing'; -import { log } from 'debug'; - -describe('GameSummary', function () { - let GameSummary: GameSummary; - let minterAccount: SignerWithAddress; - let playerAccount: SignerWithAddress; - const DEFAULT_GAME_ID = 11; - const DEFAULT_STORE_ID = 22; - const DEFAULT_TOKEN_ID = hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID); - const defaultBaseURI = 'https://achievo.mypinata.cloud/ipfs/'; - let defaultCommonGameSummary: GameSummary.GameSummaryStruct; - beforeEach(async function () { - const contract = (await ethers.getContractFactory('GameSummary')) as unknown as GameSummary; - const [adminAccount, player] = await ethers.getSigners(); - minterAccount = adminAccount; - playerAccount = player; - // @ts-ignore-next-line - GameSummary = await contract.deploy(defaultBaseURI); - await GameSummary.waitForDeployment(); - const minterRole = await GameSummary.MINTER_ROLE(); - const gameCreatorRole = await GameSummary.GAME_CREATOR_ROLE(); - await GameSummary.grantRole(minterRole, minterAccount.address); - await GameSummary.grantRole(gameCreatorRole, minterAccount.address); - await ( - await GameSummary.createCommonGameSummary( - DEFAULT_STORE_ID, - DEFAULT_GAME_ID, - 'GameTest1234', - 'test://on_chain_uri', - 'external_uri', - 500 - ) - ).wait(); - - defaultCommonGameSummary = await GameSummary.getGameSummary(DEFAULT_TOKEN_ID); - }); - - it('As Player must mint game summary achievement', async function () { - const { signature, nonce } = await generateSignature({ - walletAddress: playerAccount.address, - signer: minterAccount, - }); - const whitelistTx = await GameSummary.setSigner(minterAccount.address); - await whitelistTx.wait(); - const randomAchievementIds = ['100_KILLS_WITH_AWP', 'WEAPON_MASTER', 'X_ACHIEVEMENT']; - const tx = await GameSummary.connect(playerAccount).mintGameSummaryWithSignature( - DEFAULT_GAME_ID, - randomAchievementIds.length, - DEFAULT_STORE_ID, - nonce, - signature - ); - await tx.wait(); - const tokenId = await GameSummary.getTokenId(DEFAULT_STORE_ID, DEFAULT_GAME_ID); - const balance = await GameSummary.balanceOf(playerAccount.address, tokenId.toString()); - expect(Number(balance)).to.equal(1); - }); - - it('As Admin must mint a game summary for a player', async function () { - const randomAchievementIds = [1234, 65441, 12312]; - const tx = await GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID, - randomAchievementIds.length, - DEFAULT_STORE_ID, - true - ); - await tx.wait(); - const balance = await GameSummary.balanceOf(playerAccount.address, hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID)); - const balanceInt = Number(balance); - expect(balanceInt).to.equal(1); - }); - - it('As Admin must mint a game summary but not the same game summary twice', async function () { - const randomAchievementIds = [1234, 65441, 12312]; - const tx = await GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID, - randomAchievementIds.length, - DEFAULT_STORE_ID, - true - ); - await tx.wait(); - - await expect( - GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID, - randomAchievementIds.length, - DEFAULT_STORE_ID, - true - ) - // @ts-ignore-next-line - ).to.be.revertedWith('Token already exists'); - }); - - it('The pause functionality should works as expected', async function () { - const randomAchievementIds = [1234, 65441, 12312]; - const tx = await GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID, - randomAchievementIds.length, - DEFAULT_STORE_ID, - true - ); - await tx.wait(); - - await GameSummary.pause(); - - await expect( - GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID + 1, - randomAchievementIds.length, - DEFAULT_STORE_ID, - true - ) - // @ts-ignore-next-line - ).to.be.revertedWith('Pausable: paused'); - - await GameSummary.unpause(); - - await ( - await await GameSummary.createCommonGameSummary( - DEFAULT_STORE_ID, - DEFAULT_GAME_ID + 2, - 'GameTest1234', - 'test://on_chain_uri', - 'external_uri', - 500 - ) - ).wait(); - const tx2 = await GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID + 2, - randomAchievementIds.length, - DEFAULT_STORE_ID, - true - ); - await tx2.wait(); - const balance = await GameSummary.balanceOf( - playerAccount.address, - hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID + 2) - ); - expect(Number(balance)).to.equal(1); - }); - - it('As admin must mint a batch of game summaries achievement for players', async function () { - const GAME_IDS = [208, 444, 555]; - const randomArrayAchievementsIds1 = [12345, 654421, 123132, 12312]; - const randomArrayAchievementsIds2 = [1231234, 6545641, 9999]; - const randomArrayAchievementsIds3 = [6661234, 33365441, 22212312]; - - for await (const gameID of GAME_IDS) { - await ( - await GameSummary.createCommonGameSummary( - DEFAULT_STORE_ID, - gameID, - 'GameTest1234', - 'test://on_chain_uri', - 'external_uri', - 9999999999 - ) - ).wait(); - } - - const tx = await GameSummary.adminBatchMintGameSummary( - [playerAccount.address, playerAccount.address, playerAccount.address], - GAME_IDS, - [ - randomArrayAchievementsIds1.length, - randomArrayAchievementsIds2.length, - randomArrayAchievementsIds3.length, - ], - [DEFAULT_STORE_ID, DEFAULT_STORE_ID, DEFAULT_STORE_ID], - [true, false, true] - ); - await tx.wait(); - - for (let i = 0; i < GAME_IDS.length; i++) { - const tokenId = hashIds(DEFAULT_STORE_ID, GAME_IDS[i]); - const balance = await GameSummary.balanceOf(playerAccount.address, tokenId); - const balanceInt = Number(balance); - expect(balanceInt).to.equal(1); - } - }); - - it('As game creator role I can create game summaries', async function () { - const summary = await GameSummary.getGameSummary(hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID)); - expect(Number(summary.gameId)).to.equal(DEFAULT_GAME_ID); - expect(Number(summary.totalAchievements)).to.equal(500); - expect(summary.name).to.equal('GameTest1234'); - expect(summary.image).to.equal('test://on_chain_uri'); - expect(summary.externalURI).to.equal('external_uri'); - }); - - it('As an User I can get 1 GameSummary that the admin minted per game', async function () { - const tx = await GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID, - 20, - DEFAULT_STORE_ID, - true - ); - await tx.wait(); - const gameSummary = await GameSummary.connect(playerAccount).getGameSummary( - hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID) - ); - expect(Number(gameSummary.gameId)).to.equal(DEFAULT_GAME_ID); - expect(Number(gameSummary.totalAchievements)).to.equal(500); - expect(gameSummary.name).to.equal('GameTest1234'); - expect(gameSummary.image).to.equal('test://on_chain_uri'); - expect(gameSummary.externalURI).to.equal('external_uri'); - }); - - it('As an User I can get 1 GameSummary that the admin minted per game and check the PlayerData', async function () { - const PLAYER_ACHIEVEMENTS_LENGTH = 20; - const TOTAL_ACHIEVEMENTS_IN_GAME = 500; - await ( - await GameSummary.createCommonGameSummary( - DEFAULT_STORE_ID + 1, - DEFAULT_GAME_ID, - 'GameTest1234', - 'test://on_chain_uri', - 'external_uri', - TOTAL_ACHIEVEMENTS_IN_GAME - ) - ).wait(); - const tx = await GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID, - PLAYER_ACHIEVEMENTS_LENGTH, - DEFAULT_STORE_ID + 1, - true - ); - await tx.wait(); - const gameSummary = await GameSummary.connect(playerAccount).getGameSummary( - hashIds(DEFAULT_STORE_ID + 1, DEFAULT_GAME_ID) - ); - expect(Number(gameSummary.gameId)).to.equal(DEFAULT_GAME_ID); - expect(Number(gameSummary.totalAchievements)).to.equal(TOTAL_ACHIEVEMENTS_IN_GAME); - const playerData = await GameSummary.connect(playerAccount).getPlayerGameData( - playerAccount.address, - hashIds(DEFAULT_STORE_ID + 1, DEFAULT_GAME_ID) - ); - expect(Number(playerData.achievementsMinted)).to.equal(PLAYER_ACHIEVEMENTS_LENGTH); - // missing achievements = total achievements - achievements minted - expect(Number(gameSummary.totalAchievements) - Number(playerData.achievementsMinted)).to.equal(480); - }); - - it('If the admin update one GameSummary, everyone will have this update', async function () { - const createCommonGameSummaryTrx = await GameSummary.createCommonGameSummary( - DEFAULT_STORE_ID + 1, - DEFAULT_GAME_ID, - 'GameTest1234', - 'test://on_chain_uri', - 'external_uri', - 500 - ); - await createCommonGameSummaryTrx.wait(); - const tx = await GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID, - 20, - DEFAULT_STORE_ID + 1, - true - ); - await tx.wait(); - const gameSummary = await GameSummary.connect(playerAccount).getGameSummary( - hashIds(DEFAULT_STORE_ID + 1, DEFAULT_GAME_ID) - ); - expect(gameSummary.image).to.equal('test://on_chain_uri'); - await GameSummary.updateCommonGameSummary( - hashIds(DEFAULT_STORE_ID + 1, DEFAULT_GAME_ID), - 'New_NAME', - 'test://on_chain_uri_2', - 'external_uri', - 577 - ); - const gameSummaryUpdated = await GameSummary.connect(playerAccount).getGameSummary( - hashIds(DEFAULT_STORE_ID + 1, DEFAULT_GAME_ID) - ); - expect(gameSummaryUpdated.image).to.equal('test://on_chain_uri_2'); - expect(gameSummaryUpdated.name).to.equal('New_NAME'); - expect(Number(gameSummaryUpdated.totalAchievements)).to.equal(500 + 77); - }); - - it('As an Admin the BaseURI functionality should works', async function () { - const tx = await GameSummary.setBaseUri('ipfs://some1234hash/folder/'); - await tx.wait(); - const baseURI = await GameSummary.baseUri(); - expect(baseURI).to.equal('ipfs://some1234hash/folder/'); - - const mintTx = await GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID, - 222, - DEFAULT_STORE_ID, - true - ); - await mintTx.wait(); - const uri = await GameSummary.uri(hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID)); - expect(uri).to.equal(`ipfs://some1234hash/folder/${hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID)}.json`); - }); - - it('As user I cant transfer/sell any achievement if is a SBT', async function () { - const tx = await GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID, - 20, - DEFAULT_STORE_ID, - true - ); - await tx.wait(); - await expect( - GameSummary.connect(playerAccount).safeTransferFrom( - playerAccount.address, - minterAccount.address, - hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID), - 1, - ethers.toUtf8Bytes('') - ) - // @ts-ignore-next-line - ).to.be.revertedWith("You can't transfer this token"); - - // simulating a listing on any marketplace - await GameSummary.setApprovalForAll('0xa5409ec958c83c3f309868babaca7c86dcb077c1', true); - await expect( - GameSummary.connect(playerAccount).safeTransferFrom( - playerAccount.address, - '0xa5409ec958c83c3f309868babaca7c86dcb077c1', - hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID), - 1, - ethers.toUtf8Bytes('') - ) - // @ts-ignore-next-line - ).to.be.revertedWith("You can't transfer this token"); - }); - - it('As user I can transfer/sell any achievement if is not a SBT', async function () { - const tx = await GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID, - 20, - DEFAULT_STORE_ID, - false - ); - await tx.wait(); - const transferTx = await GameSummary.connect(playerAccount).safeTransferFrom( - playerAccount.address, - minterAccount.address, - hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID), - 1, - ethers.toUtf8Bytes('') - ); - await transferTx.wait(); - const balance = await GameSummary.balanceOf(minterAccount.address, hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID)); - expect(Number(balance)).to.equal(1); - }); - - it('As user I cant transfer any SBT using the safeBatchTransferFrom', async function () { - const GAME_IDS = [DEFAULT_GAME_ID, DEFAULT_GAME_ID + 1, DEFAULT_GAME_ID + 2, DEFAULT_GAME_ID + 3]; - for await (const gameID of GAME_IDS) { - if (gameID != DEFAULT_GAME_ID) { - await ( - await GameSummary.createCommonGameSummary( - DEFAULT_STORE_ID, - gameID, - 'GameTest1234', - 'test://on_chain_uri', - 'external_uri', - 9999999999 - ) - ).wait(); - } - } - const TOKEN_ID_1 = hashIds(DEFAULT_STORE_ID, GAME_IDS[0]); - const TOKEN_ID_2 = hashIds(DEFAULT_STORE_ID, GAME_IDS[1]); - const TOKEN_ID_3 = hashIds(DEFAULT_STORE_ID, GAME_IDS[2]); - const TOKEN_ID_4 = hashIds(DEFAULT_STORE_ID, GAME_IDS[3]); - const sbt1Trx = await GameSummary.adminMintGameSummary( - playerAccount.address, - GAME_IDS[0], - 1, - DEFAULT_STORE_ID, - false - ); - const sbt2Trx = await GameSummary.adminMintGameSummary( - playerAccount.address, - GAME_IDS[1], - 1, - DEFAULT_STORE_ID, - true - ); - const sbt3Trx = await GameSummary.adminMintGameSummary( - playerAccount.address, - GAME_IDS[2], - 1, - DEFAULT_STORE_ID, - false - ); - const sbt4Trx = await GameSummary.adminMintGameSummary( - playerAccount.address, - GAME_IDS[3], - 1, - DEFAULT_STORE_ID, - false - ); - await sbt1Trx.wait(); - await sbt2Trx.wait(); - await sbt3Trx.wait(); - await sbt4Trx.wait(); - - // must revert because at least 1 token is an SBT - await expect( - GameSummary.connect(playerAccount).safeBatchTransferFrom( - playerAccount.address, - minterAccount.address, - [TOKEN_ID_1, TOKEN_ID_2, TOKEN_ID_3], - [1, 1, 1], - ethers.toUtf8Bytes('') - ) - // @ts-ignore-next-line - ).to.be.revertedWith("You can't transfer this token"); - - const balance = await GameSummary.balanceOf(minterAccount.address, `${TOKEN_ID_1}`); - const balance2 = await GameSummary.balanceOf(minterAccount.address, `${TOKEN_ID_2}`); - expect(Number(balance)).to.equal(0); - expect(Number(balance2)).to.equal(0); - - // But if the token is not an SBT, it should work - const transferTx = await GameSummary.connect(playerAccount).safeBatchTransferFrom( - playerAccount.address, - minterAccount.address, - [TOKEN_ID_3, TOKEN_ID_4], - [1, 1], - ethers.toUtf8Bytes('') - ); - await transferTx.wait(); - const balance3 = await GameSummary.balanceOf(minterAccount.address, `${TOKEN_ID_3}`); - const balance4 = await GameSummary.balanceOf(minterAccount.address, `${TOKEN_ID_4}`); - expect(Number(balance3)).to.equal(1); - expect(Number(balance4)).to.equal(1); - }); - - it('As a user I could burn any not SBT', async function () { - const TOKEN_ID_1 = hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID); - const sbt1Trx = await GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID, - 1, - DEFAULT_STORE_ID, - false - ); - await sbt1Trx.wait(); - - const burnTx = await GameSummary.connect(playerAccount).burn(TOKEN_ID_1); - await burnTx.wait(); - - const balance = await GameSummary.balanceOf(playerAccount.address, TOKEN_ID_1); - expect(Number(balance)).to.equal(0); - }); - - it('As a user I can burn any SBT but the info of the player will be cleared', async function () { - const TOKEN_ID_1 = hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID); - const sbt1Trx = await GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID, - 1, - DEFAULT_STORE_ID, - true - ); - await sbt1Trx.wait(); - await (await GameSummary.connect(playerAccount).burn(TOKEN_ID_1)).wait(); - - const gameSummaryBurned = await GameSummary.connect(playerAccount).getPlayerGameData( - playerAccount.address, - TOKEN_ID_1 - ); - await expect(gameSummaryBurned.achievementsMinted).to.equal(0); - await expect(gameSummaryBurned.tokenId).to.equal(0); - await expect(gameSummaryBurned.soulbounded).to.be.false; - }); - - it('should revert if a non-admin tries to set a signer', async function () { - await expect(GameSummary.connect(playerAccount).setSigner(minterAccount.address)) - // @ts-ignore-next-line - .to.be.revertedWith( - 'AccessControl: account 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000' - ); - }); - - it('should revert if a non-admin tries to remove a signer', async function () { - await expect(GameSummary.connect(playerAccount).removeSigner(minterAccount.address)) - // @ts-ignore-next-line - .to.be.revertedWith( - 'AccessControl: account 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000' - ); - }); - - it('should return true for supported interfaces', async function () { - expect(await GameSummary.supportsInterface('0x01ffc9a7')).to.be.true; // ERC165 - expect(await GameSummary.supportsInterface('0xd9b67a26')).to.be.true; // ERC1155 - }); - - it('should return false for unsupported interfaces', async function () { - expect(await GameSummary.supportsInterface('0xabcdef12')).to.be.false; - }); - - it('should return the correct uri for a given token ID', async function () { - expect(await GameSummary.uri(123)).to.equal('https://achievo.mypinata.cloud/ipfs/123.json'); - }); - - it('should allow batch burning of tokens', async function () { - const TOKEN_ID_1 = hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID); - const TOKEN_ID_2 = hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID + 1); - - await await GameSummary.createCommonGameSummary( - DEFAULT_STORE_ID, - DEFAULT_GAME_ID + 1, - 'testGame', - 'ipfs://hash', - 'https://cdn.comg/jpg', - 200 - ); - await GameSummary.adminMintGameSummary(playerAccount.address, DEFAULT_GAME_ID, 1, DEFAULT_STORE_ID, false); - await GameSummary.adminMintGameSummary(playerAccount.address, DEFAULT_GAME_ID + 1, 1, DEFAULT_STORE_ID, false); - - const burnTx = await GameSummary.connect(playerAccount).burnBatch([TOKEN_ID_1, TOKEN_ID_2]); - await burnTx.wait(); - - const balance1 = await GameSummary.balanceOf(playerAccount.address, TOKEN_ID_1); - const balance2 = await GameSummary.balanceOf(playerAccount.address, TOKEN_ID_2); - expect(Number(balance1)).to.equal(0); - expect(Number(balance2)).to.equal(0); - }); - - it('should revert if the signature is not valid', async function () { - await generateSignature({ walletAddress: playerAccount.address, signer: minterAccount }); - const whitelistTx = await GameSummary.setSigner(minterAccount.address); - await whitelistTx.wait(); - - // incorrect signer - const { signature, nonce } = await generateSignature({ - walletAddress: minterAccount.address, - signer: playerAccount, - }); - - await expect( - GameSummary.connect(playerAccount).mintGameSummaryWithSignature( - DEFAULT_GAME_ID, - 20, - DEFAULT_STORE_ID, - nonce, - signature - ) - // @ts-ignore-next-line - ).to.be.revertedWith('Invalid signature'); - }); - - it('as admin should update the qty of achievements for a player', async function () { - const tx = await GameSummary.adminMintGameSummary( - playerAccount.address, - DEFAULT_GAME_ID, - 20, - DEFAULT_STORE_ID, - true - ); - await tx.wait(); - - const updateTx = await GameSummary.adminUpdatePlayerAchievements( - playerAccount.address, - hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID), - 23 - ); - await updateTx.wait(); - - const playerDataUpdated = await GameSummary.connect(playerAccount).getPlayerGameData( - playerAccount.address, - hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID) - ); - expect(Number(playerDataUpdated.achievementsMinted)).to.equal(43); - }); - - it('as admin should update the qty of achievements for a player using the batch', async function () { - const GAME_IDS = [100, 234, 255]; - - for await (const gameID of GAME_IDS) { - await ( - await GameSummary.createCommonGameSummary( - DEFAULT_STORE_ID, - gameID, - 'GameTest1234', - 'test://on_chain_uri', - 'external_uri', - 9999999999 - ) - ).wait(); - } - - const TOKEN_IDS = GAME_IDS.map((gameId) => hashIds(DEFAULT_STORE_ID, gameId)); - - const tx = await GameSummary.adminBatchMintGameSummary( - [playerAccount.address, playerAccount.address, playerAccount.address], - GAME_IDS, - [20, 44, 55], - [DEFAULT_STORE_ID, DEFAULT_STORE_ID, DEFAULT_STORE_ID], - [true, true, true] - ); - await tx.wait(); - - const updateTx = await GameSummary.adminBatchPlayerUpdateAchievements( - [playerAccount.address, playerAccount.address, playerAccount.address], - TOKEN_IDS, - [1, 2, 3] - ); - await updateTx.wait(); - - const playerDataUpdated = await GameSummary.connect(playerAccount).getPlayerGameData( - playerAccount.address, - TOKEN_IDS[0] - ); - expect(Number(playerDataUpdated.achievementsMinted)).to.equal(21); - - const playerDataUpdated2 = await GameSummary.connect(playerAccount).getPlayerGameData( - playerAccount.address, - TOKEN_IDS[1] - ); - expect(Number(playerDataUpdated2.achievementsMinted)).to.equal(46); - - const playerDataUpdated3 = await GameSummary.connect(playerAccount).getPlayerGameData( - playerAccount.address, - TOKEN_IDS[2] - ); - expect(Number(playerDataUpdated3.achievementsMinted)).to.equal(58); - }); - - it('should update the qty of achievements for a player using the signature', async function () { - const { signature, nonce } = await generateSignature({ - walletAddress: playerAccount.address, - signer: minterAccount, - }); - const whitelistTx = await GameSummary.setSigner(minterAccount.address); - await whitelistTx.wait(); - - const tx = await GameSummary.connect(playerAccount).mintGameSummaryWithSignature( - DEFAULT_GAME_ID, - 20, - DEFAULT_STORE_ID, - nonce, - signature - ); - await tx.wait(); - - const { signature: signature2, nonce: nonce2 } = await generateSignature({ - walletAddress: playerAccount.address, - signer: minterAccount, - }); - - const updateTx = await GameSummary.connect(playerAccount).updatePlayerAchievementsWithSignature( - hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID), - 23, - nonce2, - signature2 - ); - await updateTx.wait(); - - const playerDataUpdated = await GameSummary.connect(playerAccount).getPlayerGameData( - playerAccount.address, - hashIds(DEFAULT_STORE_ID, DEFAULT_GAME_ID) - ); - expect(Number(playerDataUpdated.achievementsMinted)).to.equal(43); - }); - - it('the batchUpdate using the signature should works as expected', async function () { - const GAME_IDS = [100, 234, 255]; - - for await (const gameID of GAME_IDS) { - await ( - await GameSummary.createCommonGameSummary( - DEFAULT_STORE_ID, - gameID, - 'GameTest1234', - 'test://on_chain_uri', - 'external_uri', - 9999999999 - ) - ).wait(); - } - - const TOKEN_IDS = GAME_IDS.map((gameId) => hashIds(DEFAULT_STORE_ID, gameId)); - - const { signature, nonce } = await generateSignature({ - walletAddress: playerAccount.address, - signer: minterAccount, - }); - const whitelistTx = await GameSummary.setSigner(minterAccount.address); - await whitelistTx.wait(); - - const tx = await GameSummary.connect(playerAccount).batchMintGameSummaryWithSignature( - GAME_IDS, - [20, 44, 55], - [DEFAULT_STORE_ID, DEFAULT_STORE_ID, DEFAULT_STORE_ID], - nonce, - signature - ); - await tx.wait(); - - const gameData = await GameSummary.connect(playerAccount).getPlayerGamesData(playerAccount.address, TOKEN_IDS); - expect(gameData.length).to.equal(3); - - const { signature: signature2, nonce: nonce2 } = await generateSignature({ - walletAddress: playerAccount.address, - signer: minterAccount, - }); - - const updateTx = await GameSummary.connect(playerAccount).batchPlayerUpdateAchievementsWithSignature( - TOKEN_IDS, - [2, 90, 20], - nonce2, - signature2 - ); - await updateTx.wait(); - - const playerDataUpdated = await GameSummary.connect(playerAccount).getPlayerGameData( - playerAccount.address, - TOKEN_IDS[0] - ); - expect(Number(playerDataUpdated.achievementsMinted)).to.equal(22); - - const playerDataUpdated2 = await GameSummary.connect(playerAccount).getPlayerGameData( - playerAccount.address, - TOKEN_IDS[1] - ); - expect(Number(playerDataUpdated2.achievementsMinted)).to.equal(134); - - const playerDataUpdated3 = await GameSummary.connect(playerAccount).getPlayerGameData( - playerAccount.address, - TOKEN_IDS[2] - ); - expect(Number(playerDataUpdated3.achievementsMinted)).to.equal(75); - }); - - it("As admin or user minting shouldn't have collisions between ids", async function () { - const GAME_ID = 110011; - const STORE_ID = 10101010; - await ( - await await GameSummary.createCommonGameSummary( - STORE_ID, - GAME_ID, - 'GameTest1234', - 'test://on_chain_uri', - 'external_uri', - 500 - ) - ).wait(); - const tokenID = hashIds(STORE_ID, GAME_ID); - const tx = await GameSummary.adminMintGameSummary(playerAccount.address, GAME_ID, 20, STORE_ID, true); - await tx.wait(); - const balanceBN = await GameSummary.connect(playerAccount).balanceOf(playerAccount.address, tokenID); - expect(Number(balanceBN)).to.equal(1); - }); -}); From 522b0d57c5dc1d0c83d3f7bcdead2427dbcdd380 Mon Sep 17 00:00:00 2001 From: Max Vasin Limsukhawat Date: Fri, 3 May 2024 13:01:42 -0600 Subject: [PATCH 4/6] Feat: update GameSummary contract --- constants/constructor-args.ts | 4 +- contracts/games/GameSummary.sol | 194 +++++++++++++++++-------- contracts/libraries/LibGameSummary.sol | 36 +++++ 3 files changed, 171 insertions(+), 63 deletions(-) create mode 100644 contracts/libraries/LibGameSummary.sol diff --git a/constants/constructor-args.ts b/constants/constructor-args.ts index 84d7bca4..f6c39669 100644 --- a/constants/constructor-args.ts +++ b/constants/constructor-args.ts @@ -262,7 +262,7 @@ export const GameSummaryArgs = { _symbol: 'GS', _defaultTokenURI: 'FILL_ME', _contractURI: 'FILL_ME', - _compoundURI: 'https://api.achievo.xyz/v1/uri/avatar', + _compoundURI: 'https://api.achievo.xyz/v1/uri/achievements', _maxPerMint: 1, _isPaused: false, _devWallet: 'DEPLOYER_WALLET', @@ -272,7 +272,7 @@ export const GameSummaryArgs = { _symbol: 'GS', _defaultTokenURI: 'FILL_ME', _contractURI: 'FILL_ME', - _compoundURI: 'https://staging-api.achievo.xyz/v1/uri/avatar', + _compoundURI: 'https://staging-api.achievo.xyz/v1/uri/achievements', _maxPerMint: 1, _isPaused: false, _devWallet: 'DEPLOYER_WALLET', diff --git a/contracts/games/GameSummary.sol b/contracts/games/GameSummary.sol index da4c779f..f866ee53 100644 --- a/contracts/games/GameSummary.sol +++ b/contracts/games/GameSummary.sol @@ -32,7 +32,7 @@ import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable import { Achievo1155Soulbound } from "../ercs/extensions/Achievo1155Soulbound.sol"; import { ERCWhitelistSignature } from "../ercs/ERCWhitelistSignature.sol"; -import { LibItems } from "../libraries/LibItems.sol"; +import { LibGameSummary } from "../libraries/LibGameSummary.sol"; error AddressIsZero(); error InvalidInput(); @@ -70,6 +70,11 @@ contract GameSummary is mapping(uint256 => bool) private tokenExists; mapping(uint256 => string) public tokenUris; // tokenId => tokenUri + + mapping(uint256 => uint256) public storeIds; // tokenId => storeIds + mapping(uint256 => uint256) public playerIds; // tokenId => playerIds + mapping(uint256 => uint256) public gameIds; // tokenId => gameIds + mapping(uint256 => bool) public isTokenMintPaused; // tokenId => bool - default is false uint256[] public itemIds; @@ -126,9 +131,11 @@ contract GameSummary is if (_isPaused) _pause(); } - function getAllItems() public view returns (LibItems.TokenReturn[] memory) { + function getAllItems() public view returns (LibGameSummary.GameSummaryReturn[] memory) { uint256 totalTokens = itemIds.length; - LibItems.TokenReturn[] memory tokenReturns = new LibItems.TokenReturn[](totalTokens); + LibGameSummary.GameSummaryReturn[] memory GameSummaryReturns = new LibGameSummary.GameSummaryReturn[]( + totalTokens + ); uint index; for (uint i = 0; i < totalTokens; i++) { @@ -136,20 +143,23 @@ contract GameSummary is uint256 amount = balanceOf(_msgSender(), tokenId); if (amount > 0) { - LibItems.TokenReturn memory tokenReturn = LibItems.TokenReturn({ + LibGameSummary.GameSummaryReturn memory GameSummaryReturn = LibGameSummary.GameSummaryReturn({ tokenId: tokenId, tokenUri: uri(tokenId), + storeId: storeIds[tokenId], + playerId: playerIds[tokenId], + gameId: gameIds[tokenId], amount: amount }); - tokenReturns[index] = tokenReturn; + GameSummaryReturns[index] = GameSummaryReturn; index++; } } // truncate the array - LibItems.TokenReturn[] memory returnsTruncated = new LibItems.TokenReturn[](index); + LibGameSummary.GameSummaryReturn[] memory returnsTruncated = new LibGameSummary.GameSummaryReturn[](index); for (uint i = 0; i < index; i++) { - returnsTruncated[i] = tokenReturns[i]; + returnsTruncated[i] = GameSummaryReturns[i]; } return returnsTruncated; @@ -157,28 +167,33 @@ contract GameSummary is function getAllItemsAdmin( address _owner - ) public view onlyRole(MINTER_ROLE) returns (LibItems.TokenReturn[] memory) { + ) public view onlyRole(MINTER_ROLE) returns (LibGameSummary.GameSummaryReturn[] memory) { uint256 totalTokens = itemIds.length; - LibItems.TokenReturn[] memory tokenReturns = new LibItems.TokenReturn[](totalTokens); + LibGameSummary.GameSummaryReturn[] memory GameSummaryReturns = new LibGameSummary.GameSummaryReturn[]( + totalTokens + ); uint index; for (uint i = 0; i < totalTokens; i++) { uint256 tokenId = itemIds[i]; uint256 amount = balanceOf(_owner, tokenId); - LibItems.TokenReturn memory tokenReturn = LibItems.TokenReturn({ + LibGameSummary.GameSummaryReturn memory GameSummaryReturn = LibGameSummary.GameSummaryReturn({ tokenId: tokenId, tokenUri: uri(tokenId), + storeId: storeIds[tokenId], + playerId: playerIds[tokenId], + gameId: gameIds[tokenId], amount: amount }); - tokenReturns[index] = tokenReturn; + GameSummaryReturns[index] = GameSummaryReturn; index++; } // truncate the array - LibItems.TokenReturn[] memory returnsTruncated = new LibItems.TokenReturn[](index); + LibGameSummary.GameSummaryReturn[] memory returnsTruncated = new LibGameSummary.GameSummaryReturn[](index); for (uint i = 0; i < index; i++) { - returnsTruncated[i] = tokenReturns[i]; + returnsTruncated[i] = GameSummaryReturns[i]; } return returnsTruncated; @@ -186,33 +201,51 @@ contract GameSummary is function isTokenExist(uint256 _tokenId) public view returns (bool) { if (!tokenExists[_tokenId]) { - revert("TokenNotExist"); + return false; } return true; } - function _verifyContractChainIdAndDecode(bytes calldata data) private view returns (uint256[] memory) { + function _verifyContractChainIdAndDecode( + bytes calldata data + ) private view returns (uint256[] memory, uint256[] memory, uint256[] memory) { uint256 currentChainId = getChainID(); - (address contractAddress, uint256 chainId, uint256[] memory tokenIds) = _decodeData(data); + ( + address contractAddress, + uint256 chainId, + uint256[] memory _storeIds, + uint256[] memory _playerIds, + uint256[] memory _gameIds + ) = _decodeData(data); if (chainId != currentChainId || contractAddress != address(this)) { revert InvalidInput(); } - return tokenIds; + return (_storeIds, _playerIds, _gameIds); } function decodeData( bytes calldata _data - ) public view onlyRole(DEV_CONFIG_ROLE) returns (address, uint256, uint256[] memory) { + ) + public + view + onlyRole(DEV_CONFIG_ROLE) + returns (address, uint256, uint256[] memory, uint256[] memory, uint256[] memory) + { return _decodeData(_data); } - function _decodeData(bytes calldata _data) private view returns (address, uint256, uint256[] memory) { - (address contractAddress, uint256 chainId, uint256[] memory _tokenIds) = abi.decode( - _data, - (address, uint256, uint256[]) - ); - return (contractAddress, chainId, _tokenIds); + function _decodeData( + bytes calldata _data + ) private pure returns (address, uint256, uint256[] memory, uint256[] memory, uint256[] memory) { + ( + address contractAddress, + uint256 chainId, + uint256[] memory _storeIds, + uint256[] memory _playerIds, + uint256[] memory _gameIds + ) = abi.decode(_data, (address, uint256, uint256[], uint256[], uint256[])); + return (contractAddress, chainId, _storeIds, _playerIds, _gameIds); } function pause() external onlyRole(MANAGER_ROLE) { @@ -223,23 +256,26 @@ contract GameSummary is _unpause(); } - function addNewToken(LibItems.TokenCreate calldata _token) public onlyRole(DEV_CONFIG_ROLE) { + function getTokenId(uint256 storeId, uint256 playerId, uint256 gameId) public pure returns (uint256) { + uint256 tokenId = uint256(keccak256(abi.encode(storeId, playerId, gameId))); + return tokenId; + } + + function addNewToken(LibGameSummary.GameSummaryCreate memory _token) public onlyRole(DEV_CONFIG_ROLE) { if (bytes(_token.tokenUri).length > 0) { tokenUris[_token.tokenId] = _token.tokenUri; } tokenExists[_token.tokenId] = true; + storeIds[_token.tokenId] = _token.storeId; + playerIds[_token.tokenId] = _token.playerId; + gameIds[_token.tokenId] = _token.gameId; + itemIds.push(_token.tokenId); emit TokenAdded(_token.tokenId); } - function addNewTokens(LibItems.TokenCreate[] calldata _tokens) external onlyRole(DEV_CONFIG_ROLE) { - for (uint256 i = 0; i < _tokens.length; i++) { - addNewToken(_tokens[i]); - } - } - function updateTokenUri(uint256 _tokenId, string calldata _tokenUri) public onlyRole(DEV_CONFIG_ROLE) { tokenUris[_tokenId] = _tokenUri; } @@ -260,21 +296,53 @@ contract GameSummary is isTokenMintPaused[_tokenId] = _isTokenMintPaused; } - function _mintBatch(address to, uint256[] memory _tokenIds, uint256 amount, bool soulbound) private { - for (uint256 i = 0; i < _tokenIds.length; i++) { - uint256 _id = _tokenIds[i]; - isTokenExist(_id); - if (isTokenMintPaused[_id]) { - revert("TokenMintPaused"); - } + function _mintBatch( + address to, + uint256[] memory _storeIds, + uint256[] memory _playerIds, + uint256[] memory _gameIds, + uint256 amount, + bool soulbound + ) private { + uint256[] memory _tokenIds = new uint256[](_storeIds.length); + for (uint256 i = 0; i < _storeIds.length; i++) { + uint256 _id = _internalMint(to, _storeIds[i], _playerIds[i], _gameIds[i], amount, soulbound); + _tokenIds[i] = _id; + } + emit Minted(to, _tokenIds, amount, soulbound); + } - if (soulbound) { - _soulbound(to, _id, amount); - } + function _internalMint( + address to, + uint256 _storeId, + uint256 _playerId, + uint256 _gameId, + uint256 amount, + bool soulbound + ) private returns (uint256) { + uint256 _id = getTokenId(_storeId, _playerId, _gameId); + + if (!isTokenExist(_id)) { + LibGameSummary.GameSummaryCreate memory gameSummaryCreate = LibGameSummary.GameSummaryCreate({ + tokenId: _id, + tokenUri: "", + storeId: _storeId, + playerId: _playerId, + gameId: _gameId + }); + addNewToken(gameSummaryCreate); + } - _mint(to, _id, amount, ""); + if (isTokenMintPaused[_id]) { + revert("TokenMintPaused"); } - emit Minted(to, _tokenIds, amount, soulbound); + + if (soulbound) { + _soulbound(to, _id, amount); + } + + _mint(to, _id, amount, ""); + return _id; } function mint( @@ -284,33 +352,33 @@ contract GameSummary is uint256 nonce, bytes calldata signature ) external nonReentrant signatureCheck(_msgSender(), nonce, data, signature) maxPerMintCheck(amount) whenNotPaused { - uint256[] memory _tokenIds = _verifyContractChainIdAndDecode(data); - _mintBatch(_msgSender(), _tokenIds, amount, soulbound); + ( + uint256[] memory _storeIds, + uint256[] memory _playerIds, + uint256[] memory _gameIds + ) = _verifyContractChainIdAndDecode(data); + _mintBatch(_msgSender(), _storeIds, _playerIds, _gameIds, amount, soulbound); } function adminMint(address to, bytes calldata data, bool soulbound) external onlyRole(MINTER_ROLE) whenNotPaused { - uint256[] memory _tokenIds = _verifyContractChainIdAndDecode(data); - _mintBatch(to, _tokenIds, 1, soulbound); + ( + uint256[] memory _storeIds, + uint256[] memory _playerIds, + uint256[] memory _gameIds + ) = _verifyContractChainIdAndDecode(data); + _mintBatch(to, _storeIds, _playerIds, _gameIds, 1, soulbound); } function adminMintId( address to, - uint256 id, + uint256 _storeId, + uint256 _playerId, + uint256 _gameId, uint256 amount, bool soulbound ) external onlyRole(MINTER_ROLE) whenNotPaused { - isTokenExist(id); - - if (isTokenMintPaused[id]) { - revert("TokenMintPaused"); - } - - if (soulbound) { - _soulbound(to, id, amount); - } - - _mint(to, id, amount, ""); - emit MintedId(to, id, amount, soulbound); + uint256 _id = _internalMint(to, _storeId, _playerId, _gameId, amount, soulbound); + emit MintedId(to, _id, amount, soulbound); } function _beforeTokenTransfer( @@ -437,7 +505,11 @@ contract GameSummary is "/", Strings.toHexString(uint160(address(this)), 20), "/", - Strings.toString(tokenId) + Strings.toString(storeIds[tokenId]), + "-", + Strings.toString(playerIds[tokenId]), + "-", + Strings.toString(gameIds[tokenId]) ) ); } diff --git a/contracts/libraries/LibGameSummary.sol b/contracts/libraries/LibGameSummary.sol new file mode 100644 index 00000000..093520e8 --- /dev/null +++ b/contracts/libraries/LibGameSummary.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +// MMMMNkc. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM +// MWXd,. .cONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM +// Wx' .cKMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM +// x. ;KMMMMMMMMMMMMWKxlcclxKWMMMMMMMMWKxlc::::::::ckWMMXd:cOMMMMMMMMKo:oKMMWkccxWMWKdccccccccccccoKMM0l:l0MMMMMMMMMWkc:dXMMMXkoc::::::clxKW +// ' lNMMMMMMMMMMNd. .. .dNMMMMMMNd. ..........oWMM0' oWMMMMMMMk. .kMMN: :XMNl .''''''''';OMMX: ,0MMMMMMMWk. oNMWk' ........ .o +// . :XMMMMMMMMMWd. .o00l. .dWMMMMWx. .o0KKXKKXXXXNMMM0' oNWWWWWWWk. .kMMN: :NMNc .kNNNNNNNNNNWMMM0, :XMMMMMM0, cXMMO. c0KKKKXK0o. +// , .lkxo. ;dkx, oWMMMMMMMMWk. oNMMNo. .kWMMMWl ;KMMMMMMMMMMMMMM0' .',',,,,,. .kMMN: :NMNc ,:;;;;;;dXMMMMMMO. lNMMMMK: ;KMMMd. .OMMMMMMMMX; +// : :KWX: .xMWx. .kMMMMMMMMM0' cXMMMMXc ,0MMMWl ;KMMMMMMMMMMMMMM0' .',,'',,,. .kMMN: :NMNc ',,;;,;;oXMMMMMMWx. .dWMMNc 'OMMMMd. .OMMMMMMMMX; +// l ,0WO:oXWd. .OMMMMMMMMK; ;KMMMMMMK; :KMMWd. .o0KKXXKKKXXNMMM0' oNWWWWWWWx. .kMMN: :XMNc .kNNNNNNNNWWWMMMMMNo. .dK0l. .xWMMMMO. .c0KKKXXK0o. +// o dWMWWMK, '0MMMMMMMXc 'OMMMMMMMMO' cNMMNd. ..........oWMM0' oWMMMMMMMk. .kMMN: :XMNl .,,,,,,,,,:0MMMMMMNo. .. 'xWMMMMMWx' ....... .o +// O' :XMMMMk. cXMMMMMMMKo:cOWMMMMMMMMWOc:oKMMMWKxlc::::::::ckWMMXd:cOMMMMMMMMKo:oKMMWkc:xWMWKoc:::::::::::lKMMMMMMMWKdlcclxXWMMMMMMMMXkoc::::::clxKW +// WO; 'OMMMWl .oXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM +// MMNx'.dWMMK;.:0WMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM +// MMMM0cdNMM0cdNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM + +library LibGameSummary { + struct GameSummaryCreate { + uint256 tokenId; + string tokenUri; + uint256 storeId; + uint256 playerId; + uint256 gameId; + } + + struct GameSummaryReturn { + uint256 tokenId; + string tokenUri; + uint256 storeId; + uint256 playerId; + uint256 gameId; + uint256 amount; + } +} From b758613dd47c15826aa4d90704ea46273594d598 Mon Sep 17 00:00:00 2001 From: Max Vasin Limsukhawat Date: Tue, 14 May 2024 11:51:21 -0600 Subject: [PATCH 5/6] make contract more lean --- contracts/games/GameSummary.sol | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/contracts/games/GameSummary.sol b/contracts/games/GameSummary.sol index f866ee53..8141aa4d 100644 --- a/contracts/games/GameSummary.sol +++ b/contracts/games/GameSummary.sol @@ -69,7 +69,6 @@ contract GameSummary is uint256 public MAX_PER_MINT; mapping(uint256 => bool) private tokenExists; - mapping(uint256 => string) public tokenUris; // tokenId => tokenUri mapping(uint256 => uint256) public storeIds; // tokenId => storeIds mapping(uint256 => uint256) public playerIds; // tokenId => playerIds @@ -262,10 +261,6 @@ contract GameSummary is } function addNewToken(LibGameSummary.GameSummaryCreate memory _token) public onlyRole(DEV_CONFIG_ROLE) { - if (bytes(_token.tokenUri).length > 0) { - tokenUris[_token.tokenId] = _token.tokenUri; - } - tokenExists[_token.tokenId] = true; storeIds[_token.tokenId] = _token.storeId; @@ -276,22 +271,6 @@ contract GameSummary is emit TokenAdded(_token.tokenId); } - function updateTokenUri(uint256 _tokenId, string calldata _tokenUri) public onlyRole(DEV_CONFIG_ROLE) { - tokenUris[_tokenId] = _tokenUri; - } - - function batchUpdateTokenUri( - uint256[] calldata _tokenIds, - string[] calldata _tokenUris - ) public onlyRole(DEV_CONFIG_ROLE) { - if (_tokenIds.length != _tokenUris.length) { - revert("InvalidInput"); - } - for (uint256 i = 0; i < _tokenIds.length; i++) { - updateTokenUri(_tokenIds[i], _tokenUris[i]); - } - } - function updateTokenMintPaused(uint256 _tokenId, bool _isTokenMintPaused) public onlyRole(MANAGER_ROLE) { isTokenMintPaused[_tokenId] = _isTokenMintPaused; } @@ -514,11 +493,7 @@ contract GameSummary is ); } - if (bytes(tokenUris[tokenId]).length > 0) { - return tokenUris[tokenId]; - } else { - return string(abi.encodePacked(baseURI, "/", tokenId.toString())); - } + return string(abi.encodePacked(baseURI, "/", tokenId.toString())); } function setBaseUri(string memory _uri) public onlyRole(DEV_CONFIG_ROLE) { From 60e7402e1e10cb3fbbda3e012b60bdb2009d8061 Mon Sep 17 00:00:00 2001 From: Max Vasin Limsukhawat Date: Wed, 15 May 2024 13:24:25 -0600 Subject: [PATCH 6/6] feat: Update GameSummary Contract and tests --- contracts/games/GameSummary.sol | 22 +- test/AvatarBound.t.sol | 344 +++++++++++++++++++++++++++++ test/AvatarBoundV1.t.sol | 370 ++++++++++++++++++++++++++++++++ test/games/GameSummary.t.sol | 361 +++++++++++++++---------------- 4 files changed, 905 insertions(+), 192 deletions(-) create mode 100644 test/AvatarBound.t.sol create mode 100644 test/AvatarBoundV1.t.sol diff --git a/contracts/games/GameSummary.sol b/contracts/games/GameSummary.sol index 8141aa4d..1ddfcca0 100644 --- a/contracts/games/GameSummary.sol +++ b/contracts/games/GameSummary.sol @@ -61,6 +61,7 @@ contract GameSummary is string public compoundURI; bool public compoundURIEnabled; + bool public isOneTokenPerWallet = true; string public name; string public symbol; @@ -260,7 +261,7 @@ contract GameSummary is return tokenId; } - function addNewToken(LibGameSummary.GameSummaryCreate memory _token) public onlyRole(DEV_CONFIG_ROLE) { + function _addNewToken(LibGameSummary.GameSummaryCreate memory _token) internal { tokenExists[_token.tokenId] = true; storeIds[_token.tokenId] = _token.storeId; @@ -301,6 +302,14 @@ contract GameSummary is ) private returns (uint256) { uint256 _id = getTokenId(_storeId, _playerId, _gameId); + if (isOneTokenPerWallet && balanceOf(to, _id) > 0) { + revert("AlreadyMinted"); + } + + if (isTokenMintPaused[_id]) { + revert("TokenMintPaused"); + } + if (!isTokenExist(_id)) { LibGameSummary.GameSummaryCreate memory gameSummaryCreate = LibGameSummary.GameSummaryCreate({ tokenId: _id, @@ -309,11 +318,7 @@ contract GameSummary is playerId: _playerId, gameId: _gameId }); - addNewToken(gameSummaryCreate); - } - - if (isTokenMintPaused[_id]) { - revert("TokenMintPaused"); + _addNewToken(gameSummaryCreate); } if (soulbound) { @@ -474,7 +479,10 @@ contract GameSummary is } function uri(uint256 tokenId) public view override returns (string memory) { - isTokenExist(tokenId); + if (!isTokenExist(tokenId)) { + revert("TokenNotExist"); + } + if (compoundURIEnabled) { // "{compoundURI}/0x1234567890123456789012345678901234567890/{tokenId}"; return diff --git a/test/AvatarBound.t.sol b/test/AvatarBound.t.sol new file mode 100644 index 00000000..e2ff2383 --- /dev/null +++ b/test/AvatarBound.t.sol @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import { Test, console } from "forge-std/Test.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +import { AvatarBound } from "../contracts/games/AvatarBound.sol"; +import { ERC1155RoyaltiesSoulbound } from "../contracts/soulbounds/ERC1155RoyaltiesSoulbound.sol"; +import { MockERC721Receiver } from "../contracts/mocks/MockERC721Receiver.sol"; +import { MockERC1155Receiver } from "../contracts/mocks/MockERC1155Receiver.sol"; +import { FreeMint } from "../contracts/airdrops/FreeMint.sol"; +import { LibItems } from "../contracts/libraries/LibItems.sol"; + +contract AvatarBoundTest is Test { + using Strings for uint256; + AvatarBound public avatarBound; + MockERC721Receiver public mockERC721Receiver; + MockERC1155Receiver public mockERC1155Receiver; + ERC1155RoyaltiesSoulbound public itemBound; + FreeMint public capsuleNft; + + uint256 public defaultBaseSkinId = 1; + uint256 public defaultCapsuleNftId = 0; + uint256 public specialItemId = 777888; + uint256 public defaultItemId = 100001; + + struct Wallet { + address addr; + uint256 privateKey; + } + + bytes public signature; + bytes public encodedItems; + uint256 public nonce; + + string public minterLabel = "minter"; + string public playerLabel = "player"; + + Wallet public minterWallet; + Wallet public playerWallet; + + uint256 private _seed; + uint256[] public _tokenItemsIds; + LibItems.TokenCreate[] public _tokens; + + function getWallet(string memory walletLabel) public returns (Wallet memory) { + (address addr, uint256 privateKey) = makeAddrAndKey(walletLabel); + Wallet memory wallet = Wallet(addr, privateKey); + return wallet; + } + + function concatenateStrings(string memory a, string memory b) internal pure returns (string memory) { + return string(abi.encodePacked(a, b)); + } + + function generateRandomItemId() internal returns (uint256) { + _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); + return _seed; + } + + function generateRandomLevel() internal returns (uint256) { + _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); + return (_seed % 10) + 1; // 1 - 10 + } + + function generateRandomTier() internal returns (uint256) { + _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); + uint256 random = _seed % 5; // 0 - 4 + + return random; + } + + function encode(uint256[] memory itemIds) public pure returns (bytes memory) { + return (abi.encode(itemIds)); + } + + function generateSignature( + address wallet, + bytes memory encodedItems, + string memory signerLabel + ) public returns (uint256, bytes memory) { + Wallet memory signerWallet = getWallet(signerLabel); + + uint256 _nonce = uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty, signerWallet.addr))) % + 50; + + bytes32 message = keccak256(abi.encodePacked(wallet, encodedItems, _nonce)); + bytes32 hash = ECDSA.toEthSignedMessageHash(message); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerWallet.privateKey, hash); + return (_nonce, abi.encodePacked(r, s, v)); + } + + function setupItems() internal returns (bytes memory) { + for (uint256 i = 0; i < 10; i++) { + uint256 _tokenId = generateRandomItemId(); // totally random + uint256 _level = generateRandomLevel(); // level 1-10 + uint256 _tier = generateRandomTier(); // tier 0-4 + + LibItems.TokenCreate memory _token = LibItems.TokenCreate({ + tokenId: _tokenId, + tokenUri: string(abi.encodePacked("https://something.com", "/", _tokenId.toString())) + }); + + _tokens.push(_token); + + _tokenItemsIds.push(_tokenId); + } + + LibItems.TokenCreate memory defaultItem = LibItems.TokenCreate({ tokenId: defaultItemId, tokenUri: "" }); + + _tokens.push(defaultItem); + + LibItems.TokenCreate memory specialItem = LibItems.TokenCreate({ tokenId: specialItemId, tokenUri: "" }); + + _tokens.push(specialItem); + + itemBound.addNewTokens(_tokens); + + encodedItems = encode(_tokenItemsIds); + return encodedItems; + } + + function setUp() public { + playerWallet = getWallet(playerLabel); + minterWallet = getWallet(minterLabel); + itemBound = new ERC1155RoyaltiesSoulbound( + "Test1155", + "T1155", + "MISSING_BASE_URL", + "MISSING_CONTRACT_URL", + 1, + false, + address(this) + ); + + capsuleNft = new FreeMint( + "OpenMint-TEST", + "OM_TEST", + "https://achievo.mypinata.cloud/ipfs/", + "QmPrH4o5q9uB8DGiFd9oDSuT3TnLiCzsFXT4wXQbpUr6c8" + ); + + encodedItems = setupItems(); + + avatarBound = new AvatarBound( + "Test", + "T", + "MISSING_BASE_URL", + "MISSING_CONTRACT_URL", + "MISSING_REVEAL_CAPSULE_URL", + "https://example.api.com", + address(minterWallet.addr), + address(capsuleNft), + address(itemBound), + true, + true, + true, + true + ); + + capsuleNft.grantRole(capsuleNft.MINTER_ROLE(), address(avatarBound)); + capsuleNft.grantRole(capsuleNft.MINTER_ROLE(), address(minterWallet.addr)); + + vm.startPrank(minterWallet.addr); + capsuleNft.safeMint(playerWallet.addr); + avatarBound.setBaseSkin(defaultBaseSkinId, "ipfs://{hash}/baseSkin/1.glb"); + avatarBound.setSpecialItemId(specialItemId); + avatarBound.setDefaultItemId(defaultItemId); + vm.stopPrank(); + + itemBound.grantRole(itemBound.MINTER_ROLE(), address(avatarBound)); + } + + function testMintAvatarNftGating() public { + vm.startPrank(playerWallet.addr); + (nonce, signature) = generateSignature(playerWallet.addr, encodedItems, minterLabel); + + avatarBound.mintAvatarNftGating(defaultCapsuleNftId, defaultBaseSkinId, nonce, encodedItems, signature); + + // missing assert to check the random items + assertEq(avatarBound.balanceOf(playerWallet.addr), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[0]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[1]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[2]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[3]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[4]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[5]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[6]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[7]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[8]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[9]), 1); + // check the default item Id + assertEq(itemBound.balanceOf(playerWallet.addr, defaultItemId), 0); + // check the special item Id + assertEq(itemBound.balanceOf(playerWallet.addr, specialItemId), 1); + // test that the capsule now has the new uri(revealed uri) + assertEq( + capsuleNft.tokenURI(defaultCapsuleNftId), + "https://achievo.mypinata.cloud/ipfs/MISSING_REVEAL_CAPSULE_URL" + ); + vm.stopPrank(); + } + + function testMintAvatarWithoutNftGating() public { + vm.startPrank(playerWallet.addr); + (nonce, signature) = generateSignature(playerWallet.addr, encodedItems, minterLabel); + + avatarBound.mintAvatar(defaultBaseSkinId, nonce, encodedItems, signature); + + // missing assert to check the random items + assertEq(avatarBound.balanceOf(playerWallet.addr), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[0]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[1]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[2]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[3]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[4]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[5]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[6]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[7]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[8]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[9]), 1); + + // check the default item Id + assertEq(itemBound.balanceOf(playerWallet.addr, defaultItemId), 1); + + // check the special item Id + assertEq(itemBound.balanceOf(playerWallet.addr, specialItemId), 0); + vm.stopPrank(); + } + + function testgetAllItems() public { + vm.startPrank(minterWallet.addr); + avatarBound.setBaseSkin(2, "ipfs://{hash}/baseSkin/2.glb"); + avatarBound.setBaseSkin(3, "ipfs://{hash}/baseSkin/3.glb"); + vm.stopPrank(); + vm.startPrank(playerWallet.addr); + AvatarBound.BaseSkinResponse[] memory allBaseSkins = avatarBound.getAllBaseSkins(); + assertEq(allBaseSkins.length, 3); + vm.stopPrank(); + } + + function testAdminMint() public { + vm.startPrank(minterWallet.addr); + avatarBound.adminMint(address(playerWallet.addr), 1); + assertEq(avatarBound.ownerOf(0), address(playerWallet.addr)); + } + + function testPauseUnpause() public { + vm.startPrank(minterWallet.addr); + avatarBound.grantRole(avatarBound.MANAGER_ROLE(), address(this)); + avatarBound.grantRole(avatarBound.MINTER_ROLE(), address(this)); + avatarBound.setBaseSkin(1, "ipfs://{hash}/1.glb"); + avatarBound.pause(); + vm.expectRevert("Pausable: paused"); + avatarBound.adminMint(address(this), 1); + vm.stopPrank(); + } + + function testSetBaseSkin() public { + vm.startPrank(minterWallet.addr); + avatarBound.setBaseSkin(2, "ipfs://{hash}/baseSkin/2.glb"); + assertEq(avatarBound.baseSkins(2), "ipfs://{hash}/baseSkin/2.glb"); + vm.stopPrank(); + } + + function testFailUnauthorizedTransfer() public { + // vm.prank(minterWallet.addr); + avatarBound.adminMint(address(playerWallet.addr), 1); + + vm.startPrank(playerWallet.addr); + vm.expectRevert("Achievo721Soulbound: Operation denied, soulbounded"); + avatarBound.transferFrom(address(playerWallet.addr), address(this), 0); + vm.stopPrank(); + } + + function testSetContractURI() public { + string memory newContractURI = "ipfs://newContractURI"; + vm.startPrank(minterWallet.addr); + avatarBound.setContractURI(newContractURI); + assertEq(avatarBound.contractURI(), newContractURI); + vm.stopPrank(); + } + + function testSetTokenURI() public { + vm.startPrank(minterWallet.addr); + avatarBound.adminMint(playerWallet.addr, 1); + avatarBound.setCompoundURIEnabled(false); + uint256 tokenId = 0; + string memory newURI = "/newURI1.glb"; + avatarBound.setTokenURI(tokenId, newURI); + // Assuming concatenateStrings is a helper function that correctly concatenates strings + assertEq(avatarBound.tokenURI(tokenId), concatenateStrings(avatarBound.baseTokenURI(), newURI)); + vm.stopPrank(); + } + + function testSetBaseURI() public { + string memory newBaseURI = "ipfs://newBaseURI/"; + vm.startPrank(minterWallet.addr); + avatarBound.setCompoundURIEnabled(false); + avatarBound.setBaseURI(newBaseURI); + assertEq(avatarBound.baseTokenURI(), newBaseURI); + vm.stopPrank(); + } + + function testCompoundURI() public { + vm.startPrank(minterWallet.addr); + avatarBound.adminMint(playerWallet.addr, 1); + string memory compoundURI = "https://this.is.a.compound.uri.endpoint/avatar"; + avatarBound.setCompoundURI(compoundURI); + uint256 tokenId = 0; + string memory finalURI = string( + abi.encodePacked( + compoundURI, + "/", + Strings.toHexString(uint160(address(avatarBound)), 20), + "/", + Strings.toString(tokenId) + ) + ); + assertEq(avatarBound.tokenURI(tokenId), finalURI); + vm.stopPrank(); + } + + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) public returns (bytes4) { + return this.onERC721Received.selector; + } + + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) public returns (bytes4) { + return this.onERC1155Received.selector; + } +} diff --git a/test/AvatarBoundV1.t.sol b/test/AvatarBoundV1.t.sol new file mode 100644 index 00000000..fa45c1e4 --- /dev/null +++ b/test/AvatarBoundV1.t.sol @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import "forge-std/StdCheats.sol"; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { ECDSAUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol"; + +import { LibItems, TestLibItems } from "../contracts/libraries/LibItems.sol"; +import { AvatarBoundV1 } from "../contracts/upgradeables/games/AvatarBoundV1.sol"; +import { ERC1155RoyaltiesSoulboundV1 } from "../contracts/upgradeables/soulbounds/ERC1155RoyaltiesSoulboundV1.sol"; +import { FreeMint } from "../contracts/airdrops/FreeMint.sol"; +import { MockERC721Receiver } from "../contracts/mocks/MockERC721Receiver.sol"; +import { MockERC1155Receiver } from "../contracts/mocks/MockERC1155Receiver.sol"; + +contract AvatarBoundV1Test is StdCheats, Test { + using Strings for uint256; + + address itemBoundV1Address; + address AvatarBoundV1Address; + + AvatarBoundV1 public avatarBound; + ERC1155RoyaltiesSoulboundV1 public itemBound; + FreeMint public capsuleNft; + + MockERC721Receiver public mockERC721Receiver; + MockERC1155Receiver public mockERC1155Receiver; + + uint256 public defaultBaseSkinId = 1; + uint256 public defaultCapsuleNftId = 0; + uint256 public specialItemId = 777888; + uint256 public defaultItemId = 100001; + + struct Wallet { + address addr; + uint256 privateKey; + } + + bytes public signature; + bytes public encodedItems; + uint256 public nonce; + + string public minterLabel = "minter"; + string public playerLabel = "player"; + + Wallet public minterWallet; + Wallet public playerWallet; + + uint256 private _seed; + uint256[] public _tokenItemsIds; + LibItems.TokenCreate[] public _tokens; + + function getWallet(string memory walletLabel) public returns (Wallet memory) { + (address addr, uint256 privateKey) = makeAddrAndKey(walletLabel); + Wallet memory wallet = Wallet(addr, privateKey); + return wallet; + } + + function concatenateStrings(string memory a, string memory b) internal pure returns (string memory) { + return string(abi.encodePacked(a, b)); + } + + function generateRandomItemId() internal returns (uint256) { + _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); + return _seed; + } + + function generateRandomLevel() internal returns (uint256) { + _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); + return (_seed % 10) + 1; // 1 - 10 + } + + function generateRandomTier() internal returns (uint256) { + _seed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), _seed))); + uint256 random = _seed % 5; // 0 - 4 + + return random; + } + + function encode(uint256[] memory itemIds) public pure returns (bytes memory) { + return (abi.encode(itemIds)); + } + + function generateSignature( + address wallet, + bytes memory encodedItems, + string memory signerLabel + ) public returns (uint256, bytes memory) { + Wallet memory signerWallet = getWallet(signerLabel); + + uint256 _nonce = uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty, signerWallet.addr))) % + 50; + + bytes32 message = keccak256(abi.encodePacked(wallet, encodedItems, _nonce)); + bytes32 hash = ECDSAUpgradeable.toEthSignedMessageHash(message); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerWallet.privateKey, hash); + return (_nonce, abi.encodePacked(r, s, v)); + } + + function setupItems() internal returns (bytes memory) { + for (uint256 i = 0; i < 10; i++) { + uint256 _tokenId = generateRandomItemId(); // totally random + uint256 _level = generateRandomLevel(); // level 1-10 + uint256 _tier = generateRandomTier(); // tier 0-4 + + LibItems.TokenCreate memory _token = LibItems.TokenCreate({ + tokenId: _tokenId, + tokenUri: string(abi.encodePacked("https://something.com", "/", _tokenId.toString())) + }); + + _tokens.push(_token); + + _tokenItemsIds.push(_tokenId); + } + + LibItems.TokenCreate memory defaultItem = LibItems.TokenCreate({ tokenId: defaultItemId, tokenUri: "" }); + + _tokens.push(defaultItem); + + LibItems.TokenCreate memory specialItem = LibItems.TokenCreate({ tokenId: specialItemId, tokenUri: "" }); + + _tokens.push(specialItem); + + itemBound.addNewTokens(_tokens); + + encodedItems = encode(_tokenItemsIds); + return encodedItems; + } + + function deployAvatarBoundV1Contract() public returns (AvatarBoundV1) { + AvatarBoundV1 avatarBoundV1 = new AvatarBoundV1(); + ERC1967Proxy proxy = new ERC1967Proxy(address(avatarBoundV1), ""); + AvatarBoundV1(address(proxy)).initialize( + "Test", + "T", + "MISSING_BASE_URL", + "MISSING_CONTRACT_URL", + "MISSING_REVEAL_CAPSULE_URL", + "https://api.example/ipfs", + address(minterWallet.addr), + address(capsuleNft), + address(itemBound), + true, + true, + true, + true + ); + + return AvatarBoundV1(address(proxy)); + } + + function deployItemBoundV1Contract() public returns (ERC1155RoyaltiesSoulboundV1) { + ERC1155RoyaltiesSoulboundV1 itemBoundV1 = new ERC1155RoyaltiesSoulboundV1(); + ERC1967Proxy proxy = new ERC1967Proxy(address(itemBoundV1), ""); + ERC1155RoyaltiesSoulboundV1(address(proxy)).initialize( + "Test1155", + "T1155", + "MISSING_BASE_URL", + "MISSING_CONTRACT_URL", + 1, + false, + address(this) + ); + + return ERC1155RoyaltiesSoulboundV1(address(proxy)); + } + + function setUp() public { + playerWallet = getWallet(playerLabel); + minterWallet = getWallet(minterLabel); + + itemBound = deployItemBoundV1Contract(); + + capsuleNft = new FreeMint( + "OpenMint-TEST", + "OM_TEST", + "https://achievo.mypinata.cloud/ipfs/", + "QmPrH4o5q9uB8DGiFd9oDSuT3TnLiCzsFXT4wXQbpUr6c8" + ); + + encodedItems = setupItems(); + + avatarBound = deployAvatarBoundV1Contract(); + + capsuleNft.grantRole(capsuleNft.MINTER_ROLE(), address(avatarBound)); + capsuleNft.grantRole(capsuleNft.MINTER_ROLE(), address(minterWallet.addr)); + + vm.startPrank(minterWallet.addr); + capsuleNft.safeMint(playerWallet.addr); + avatarBound.setBaseSkin(defaultBaseSkinId, "ipfs://{hash}/baseSkin/1.glb"); + avatarBound.setSpecialItemId(specialItemId); + avatarBound.setDefaultItemId(defaultItemId); + vm.stopPrank(); + + itemBound.grantRole(itemBound.MINTER_ROLE(), address(avatarBound)); + } + + function testMintAvatarNftGating() public { + vm.startPrank(playerWallet.addr); + (nonce, signature) = generateSignature(playerWallet.addr, encodedItems, minterLabel); + + avatarBound.mintAvatarNftGating(defaultCapsuleNftId, defaultBaseSkinId, nonce, encodedItems, signature); + + // missing assert to check the random items + assertEq(avatarBound.balanceOf(playerWallet.addr), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[0]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[1]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[2]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[3]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[4]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[5]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[6]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[7]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[8]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[9]), 1); + // check the default item Id + assertEq(itemBound.balanceOf(playerWallet.addr, defaultItemId), 0); + // check the special item Id + assertEq(itemBound.balanceOf(playerWallet.addr, specialItemId), 1); + // test that the capsule now has the new uri(revealed uri) + assertEq( + capsuleNft.tokenURI(defaultCapsuleNftId), + "https://achievo.mypinata.cloud/ipfs/MISSING_REVEAL_CAPSULE_URL" + ); + vm.stopPrank(); + } + + function testMintAvatarWithoutNftGating() public { + vm.startPrank(playerWallet.addr); + (nonce, signature) = generateSignature(playerWallet.addr, encodedItems, minterLabel); + + avatarBound.mintAvatar(defaultBaseSkinId, nonce, encodedItems, signature); + + // missing assert to check the random items + assertEq(avatarBound.balanceOf(playerWallet.addr), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[0]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[1]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[2]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[3]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[4]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[5]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[6]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[7]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[8]), 1); + assertEq(itemBound.balanceOf(playerWallet.addr, _tokenItemsIds[9]), 1); + + // check the default item Id + assertEq(itemBound.balanceOf(playerWallet.addr, defaultItemId), 1); + + // check the special item Id + assertEq(itemBound.balanceOf(playerWallet.addr, specialItemId), 0); + vm.stopPrank(); + } + + function testgetAllItems() public { + vm.startPrank(minterWallet.addr); + avatarBound.setBaseSkin(2, "ipfs://{hash}/baseSkin/2.glb"); + avatarBound.setBaseSkin(3, "ipfs://{hash}/baseSkin/3.glb"); + vm.stopPrank(); + vm.startPrank(playerWallet.addr); + AvatarBoundV1.BaseSkinResponse[] memory allBaseSkins = avatarBound.getAllBaseSkins(); + assertEq(allBaseSkins.length, 3); + vm.stopPrank(); + } + + function testAdminMint() public { + vm.startPrank(minterWallet.addr); + avatarBound.adminMint(address(playerWallet.addr), 1); + assertEq(avatarBound.ownerOf(0), address(playerWallet.addr)); + } + + function testPauseUnpause() public { + vm.startPrank(minterWallet.addr); + avatarBound.grantRole(avatarBound.MANAGER_ROLE(), address(this)); + avatarBound.grantRole(avatarBound.MINTER_ROLE(), address(this)); + avatarBound.setBaseSkin(1, "ipfs://{hash}/1.glb"); + avatarBound.pause(); + vm.expectRevert("Pausable: paused"); + avatarBound.adminMint(address(this), 1); + vm.stopPrank(); + } + + function testSetBaseSkin() public { + vm.startPrank(minterWallet.addr); + avatarBound.setBaseSkin(2, "ipfs://{hash}/baseSkin/2.glb"); + assertEq(avatarBound.baseSkins(2), "ipfs://{hash}/baseSkin/2.glb"); + vm.stopPrank(); + } + + function testFailUnauthorizedTransfer() public { + // vm.prank(minterWallet.addr); + avatarBound.adminMint(address(playerWallet.addr), 1); + + vm.startPrank(playerWallet.addr); + vm.expectRevert("Achievo721Soulbound: Operation denied, soulbounded"); + avatarBound.transferFrom(address(playerWallet.addr), address(this), 0); + vm.stopPrank(); + } + + function testSetContractURI() public { + string memory newContractURI = "ipfs://newContractURI"; + vm.startPrank(minterWallet.addr); + avatarBound.setContractURI(newContractURI); + assertEq(avatarBound.contractURI(), newContractURI); + vm.stopPrank(); + } + + function testSetTokenURI() public { + vm.startPrank(minterWallet.addr); + avatarBound.adminMint(playerWallet.addr, 1); + avatarBound.setCompoundURIEnabled(false); + uint256 tokenId = 0; + string memory newURI = "/newURI1.glb"; + avatarBound.setTokenURI(tokenId, newURI); + // Assuming concatenateStrings is a helper function that correctly concatenates strings + assertEq(avatarBound.tokenURI(tokenId), concatenateStrings(avatarBound.baseTokenURI(), newURI)); + vm.stopPrank(); + } + + function testSetBaseURI() public { + string memory newBaseURI = "ipfs://newBaseURI/"; + vm.startPrank(minterWallet.addr); + avatarBound.setCompoundURIEnabled(false); + avatarBound.setBaseURI(newBaseURI); + assertEq(avatarBound.baseTokenURI(), newBaseURI); + vm.stopPrank(); + } + + function testCompoundURI() public { + vm.startPrank(minterWallet.addr); + avatarBound.adminMint(playerWallet.addr, 1); + string memory compoundURI = "https://this.is.a.compound.uri.endpoint/avatar"; + avatarBound.setCompoundURI(compoundURI); + uint256 tokenId = 0; + string memory finalURI = string( + abi.encodePacked( + compoundURI, + "/", + Strings.toHexString(uint160(address(avatarBound)), 20), + "/", + Strings.toString(tokenId) + ) + ); + assertEq(avatarBound.tokenURI(tokenId), finalURI); + vm.stopPrank(); + } + + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) public returns (bytes4) { + return this.onERC721Received.selector; + } + + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) public returns (bytes4) { + return this.onERC1155Received.selector; + } +} diff --git a/test/games/GameSummary.t.sol b/test/games/GameSummary.t.sol index 732cebb4..cfb5b5b3 100644 --- a/test/games/GameSummary.t.sol +++ b/test/games/GameSummary.t.sol @@ -9,7 +9,7 @@ import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { GameSummary } from "../../contracts/games/GameSummary.sol"; import { MockERC1155Receiver } from "../../contracts/mocks/MockERC1155Receiver.sol"; -import { LibItems, TestLibItems } from "../../contracts/libraries/LibItems.sol"; +import { LibGameSummary } from "../../contracts/libraries/LibGameSummary.sol"; contract GameSummaryBoundTest is StdCheats, Test { using Strings for uint256; @@ -42,8 +42,11 @@ contract GameSummaryBoundTest is StdCheats, Test { bytes public encodedItems2; uint256 private _seed; - LibItems.TokenCreate[] public _tokens; + LibGameSummary.GameSummaryCreate[] public _tokens; uint256[] public _tokenIds; + uint256[] public _storeIds; + uint256[] public _playerIds; + uint256[] public _gameIds; uint256 public chainId = 31337; @@ -60,7 +63,7 @@ contract GameSummaryBoundTest is StdCheats, Test { ) public returns (uint256, bytes memory) { Wallet memory signerWallet = getWallet(signerLabel); - uint256 _nonce = uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty, signerWallet.addr))) % + uint256 _nonce = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, signerWallet.addr))) % 50; bytes32 message = keccak256(abi.encodePacked(wallet, encodedItems, _nonce)); @@ -94,8 +97,13 @@ contract GameSummaryBoundTest is StdCheats, Test { return tokenId; } - function encode(address contractAddress, uint256[] memory itemIds) public view returns (bytes memory) { - return (abi.encode(contractAddress, chainId, itemIds)); + function encode( + address contractAddress, + uint256[] memory storeIds, + uint256[] memory playerIds, + uint256[] memory gameIds + ) public view returns (bytes memory) { + return (abi.encode(contractAddress, chainId, _storeIds, _playerIds, _gameIds)); } function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { @@ -140,94 +148,87 @@ contract GameSummaryBoundTest is StdCheats, Test { uint256 _gameId = generateRandomGameId(); uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); - LibItems.TokenCreate memory _token = LibItems.TokenCreate({ - tokenId: _tokenId, - tokenUri: string(abi.encodePacked("https://something.com", "/", _tokenId.toString())) - }); - - _tokens.push(_token); + _storeIds.push(_storeId); + _playerIds.push(_playerId); + _gameIds.push(_gameId); _tokenIds.push(_tokenId); } - gameSummary.addNewTokens(_tokens); + uint256[] memory _storeIds1 = new uint256[](3); + _storeIds1[0] = _storeIds[0]; + _storeIds1[1] = _storeIds[1]; + _storeIds1[2] = _storeIds[2]; - uint256[] memory _itemIds1 = new uint256[](3); - _itemIds1[0] = _tokenIds[0]; - _itemIds1[1] = _tokenIds[1]; - _itemIds1[2] = _tokenIds[2]; + uint256[] memory _playerIds1 = new uint256[](3); + _playerIds1[0] = _playerIds[0]; + _playerIds1[1] = _playerIds[1]; + _playerIds1[2] = _playerIds[2]; - encodedItems1 = encode(address(gameSummary), _itemIds1); + uint256[] memory _gameIds1 = new uint256[](3); + _gameIds1[0] = _gameIds[0]; + _gameIds1[1] = _gameIds[1]; + _gameIds1[2] = _gameIds[2]; - uint256[] memory _itemIds2 = new uint256[](3); - _itemIds2[0] = _tokenIds[3]; - _itemIds2[1] = _tokenIds[4]; - _itemIds2[2] = _tokenIds[5]; + encodedItems1 = encode(address(gameSummary), _storeIds1, _playerIds1, _gameIds1); - encodedItems2 = encode(address(gameSummary), _itemIds2); + uint256[] memory _storeIds2 = new uint256[](3); + _storeIds2[0] = _storeIds[3]; + _storeIds2[1] = _storeIds[4]; + _storeIds2[2] = _storeIds[5]; - (nonce, signature) = generateSignature(playerWallet.addr, encodedItems1, minterLabel); - (nonce2, signature2) = generateSignature(playerWallet2.addr, encodedItems2, minterLabel); - } + uint256[] memory _playerIds2 = new uint256[](3); + _playerIds2[0] = _playerIds[3]; + _playerIds2[1] = _playerIds[4]; + _playerIds2[2] = _playerIds[5]; - function testTokenExists() public { - uint256 _tokenId = generateRandomStoreId(); - - vm.expectRevert("TokenNotExist"); - gameSummary.isTokenExist(_tokenId); + uint256[] memory _gameIds2 = new uint256[](3); + _gameIds2[0] = _gameIds[3]; + _gameIds2[1] = _gameIds[4]; + _gameIds2[2] = _gameIds[5]; - vm.expectRevert("TokenNotExist"); - gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, true); - - LibItems.TokenCreate memory _token = LibItems.TokenCreate({ - tokenId: _tokenId, - tokenUri: string(abi.encodePacked("https://something222.com", "/", _tokenId.toString())) - }); + encodedItems2 = encode(address(gameSummary), _storeIds2, _playerIds2, _gameIds2); - gameSummary.addNewToken(_token); - gameSummary.isTokenExist(_tokenId); - gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, true); + (nonce, signature) = generateSignature(playerWallet.addr, encodedItems1, minterLabel); + (nonce2, signature2) = generateSignature(playerWallet2.addr, encodedItems2, minterLabel); } - function testAddNewTokens() public { - LibItems.TokenCreate[] memory _tokens = new LibItems.TokenCreate[](3); - - skip(36000); - for (uint256 i = 0; i < 3; i++) { - uint256 _storeId = generateRandomStoreId(); - uint256 _playerId = generateRandomPlayerId(); - uint256 _gameId = generateRandomGameId(); - uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); - - LibItems.TokenCreate memory _token = LibItems.TokenCreate({ - tokenId: _tokenId, - tokenUri: string(abi.encodePacked("https://something.com", "/", _tokenId.toString())) - }); + function testTokenExists() public { + uint256 _storeId = generateRandomStoreId(); + uint256 _playerId = generateRandomPlayerId(); + uint256 _gameId = generateRandomGameId(); + uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); - _tokens[i] = _token; - } + assertEq(gameSummary.isTokenExist(_tokenId), false); - gameSummary.addNewTokens(_tokens); + gameSummary.adminMintId(playerWallet.addr, _storeId, _playerId, _gameId, 1, true); + assertEq(gameSummary.isTokenExist(_tokenId), true); } function testPauseUnpause() public { - uint256 _tokenId = _tokenIds[0]; + uint256 _storeId = _storeIds[0]; + uint256 _playerId = _playerIds[0]; + uint256 _gameId = _gameIds[0]; + uint256 _tokenId = gameSummary.getTokenId(_storeId, _playerId, _gameId); gameSummary.pause(); vm.expectRevert("Pausable: paused"); - gameSummary.adminMintId(address(this), _tokenId, 1, true); + gameSummary.adminMintId(address(this), _storeId, _playerId, _gameId, 1, true); gameSummary.unpause(); - gameSummary.adminMintId(address(mockERC1155Receiver), _tokenId, 1, true); + gameSummary.adminMintId(address(mockERC1155Receiver), _storeId, _playerId, _gameId, 1, true); assertEq(gameSummary.balanceOf(address(mockERC1155Receiver), _tokenId), 1); } function testPauseUnpauseSpecificToken() public { - uint256 _tokenId = _tokenIds[0]; + uint256 _storeId = _storeIds[0]; + uint256 _playerId = _playerIds[0]; + uint256 _gameId = _gameIds[0]; + uint256 _tokenId = gameSummary.getTokenId(_storeId, _playerId, _gameId); gameSummary.updateTokenMintPaused(_tokenId, true); vm.expectRevert("TokenMintPaused"); - gameSummary.adminMintId(address(mockERC1155Receiver), _tokenId, 1, true); + gameSummary.adminMintId(address(mockERC1155Receiver), _storeId, _playerId, _gameId, 1, true); vm.expectRevert("TokenMintPaused"); gameSummary.adminMint(address(mockERC1155Receiver), encodedItems1, true); @@ -284,24 +285,23 @@ contract GameSummaryBoundTest is StdCheats, Test { assertEq(gameSummary.balanceOf(minterWallet.addr, _tokenIds[3]), 1); } - function testMintMoreThanLimit() public { - vm.expectRevert("ExceedMaxMint"); - vm.prank(playerWallet.addr); - gameSummary.mint(encodedItems1, 2, true, nonce, signature); - } - - function testMintInvalidTokenId() public { - uint256[] memory _itemIds3 = new uint256[](3); - _itemIds3[0] = 1233; - _itemIds3[1] = 3322; + function testMintMoreThanOneTokenPerWallet() public { + uint256 _storeId = _storeIds[0]; + uint256 _playerId = _playerIds[0]; + uint256 _gameId = _gameIds[0]; + uint256 _tokenId = gameSummary.getTokenId(_storeId, _playerId, _gameId); - bytes memory encodedItems3 = encode(address(gameSummary), _itemIds3); + gameSummary.adminMintId(address(mockERC1155Receiver), _storeId, _playerId, _gameId, 1, true); + assertEq(gameSummary.balanceOf(address(mockERC1155Receiver), _tokenId), 1); - (uint256 _nonce, bytes memory _signature) = generateSignature(playerWallet.addr, encodedItems3, minterLabel); + vm.expectRevert("AlreadyMinted"); + gameSummary.adminMintId(address(mockERC1155Receiver), _storeId, _playerId, _gameId, 1, true); + } - vm.expectRevert("TokenNotExist"); + function testMintMoreThanLimit() public { + vm.expectRevert("ExceedMaxMint"); vm.prank(playerWallet.addr); - gameSummary.mint(encodedItems3, 1, true, _nonce, _signature); + gameSummary.mint(encodedItems1, 2, true, nonce, signature); } function testAdminMintNotMinterRole() public { @@ -320,17 +320,25 @@ contract GameSummaryBoundTest is StdCheats, Test { } function testAdminMintIdNotMinterRole() public { - uint256 _tokenId = _tokenIds[0]; + uint256 _storeId = _storeIds[0]; + uint256 _playerId = _playerIds[0]; + uint256 _gameId = _gameIds[0]; + uint256 _tokenId = gameSummary.getTokenId(_storeId, _playerId, _gameId); + vm.expectRevert( "AccessControl: account 0x44e97af4418b7a17aabd8090bea0a471a366305c is missing role 0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6" ); vm.prank(playerWallet.addr); - gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, true); + gameSummary.adminMintId(playerWallet.addr, _storeId, _playerId, _gameId, 1, true); } function testAdminMintId() public { - uint256 _tokenId = _tokenIds[0]; - gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, true); + uint256 _storeId = _storeIds[0]; + uint256 _playerId = _playerIds[0]; + uint256 _gameId = _gameIds[0]; + uint256 _tokenId = gameSummary.getTokenId(_storeId, _playerId, _gameId); + + gameSummary.adminMintId(playerWallet.addr, _storeId, _playerId, _gameId, 1, true); assertEq(gameSummary.balanceOf(playerWallet.addr, _tokenIds[0]), 1); } @@ -524,33 +532,12 @@ contract GameSummaryBoundTest is StdCheats, Test { uint256 _playerId = generateRandomPlayerId(); uint256 _gameId = generateRandomGameId(); uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); - LibItems.TokenCreate memory _token = LibItems.TokenCreate({ tokenId: _tokenId, tokenUri: "" }); - - gameSummary.addNewToken(_token); + gameSummary.adminMintId(playerWallet.addr, _storeId, _playerId, _gameId, 1, false); gameSummary.setCompoundURIEnabled(false); - assertEq(gameSummary.uri(_tokenId), string(abi.encodePacked("MISSING_BASE_URL", "/", _tokenId.toString()))); } - function testTokenURIIfTokenIdExistWithSpeficTokenURI() public { - uint256 _storeId = generateRandomStoreId(); - uint256 _playerId = generateRandomPlayerId(); - uint256 _gameId = generateRandomGameId(); - uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); - - LibItems.TokenCreate memory _token = LibItems.TokenCreate({ - tokenId: _tokenId, - tokenUri: "ipfs://specific-token-uri.com" - }); - - gameSummary.addNewToken(_token); - - gameSummary.setCompoundURIEnabled(false); - - assertEq(gameSummary.uri(_tokenId), "ipfs://specific-token-uri.com"); - } - function testUpdateTokenBaseURIFailNotDevConfigRole() public { string memory newBaseURI = "https://something-new.com"; @@ -566,10 +553,8 @@ contract GameSummaryBoundTest is StdCheats, Test { uint256 _playerId = generateRandomPlayerId(); uint256 _gameId = generateRandomGameId(); uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); - LibItems.TokenCreate memory _token = LibItems.TokenCreate({ tokenId: _tokenId, tokenUri: "" }); - - gameSummary.addNewToken(_token); + gameSummary.adminMintId(playerWallet.addr, _storeId, _playerId, _gameId, 1, false); gameSummary.setCompoundURIEnabled(false); string memory newBaseURI = "https://something-new.com"; @@ -582,44 +567,14 @@ contract GameSummaryBoundTest is StdCheats, Test { ); } - function testUpdateTokenURIFailNoDevConfigRole() public { - string memory newTokenUri = "https://something-new.com/232"; - - vm.expectRevert( - "AccessControl: account 0x44e97af4418b7a17aabd8090bea0a471a366305c is missing role 0x3b359cf0b4471a5de84269135285268e64ac56f52d3161392213003a780ad63b" - ); - vm.prank(playerWallet.addr); - gameSummary.updateTokenUri(0, newTokenUri); - } - - function testUpdateTokenURIPass() public { - uint256 _storeId = generateRandomStoreId(); - uint256 _playerId = generateRandomPlayerId(); - uint256 _gameId = generateRandomGameId(); - uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); - - LibItems.TokenCreate memory _token = LibItems.TokenCreate({ tokenId: _tokenId, tokenUri: "" }); - - gameSummary.addNewToken(_token); - - gameSummary.setCompoundURIEnabled(false); - - string memory newTokenUri = "https://something-new.com/232"; - - assertEq(gameSummary.uri(_tokenId), string(abi.encodePacked("MISSING_BASE_URL", "/", _tokenId.toString()))); - gameSummary.updateTokenUri(_tokenId, newTokenUri); - assertEq(gameSummary.uri(_tokenId), "https://something-new.com/232"); - } - function testUpdateCompountURIPass() public { uint256 _storeId = generateRandomStoreId(); uint256 _playerId = generateRandomPlayerId(); uint256 _gameId = generateRandomGameId(); uint256 _tokenId = generateTokenId(_storeId, _playerId, _gameId); - LibItems.TokenCreate memory _token = LibItems.TokenCreate({ tokenId: _tokenId, tokenUri: "" }); + gameSummary.adminMintId(playerWallet.addr, _storeId, _playerId, _gameId, 1, false); - gameSummary.addNewToken(_token); string memory newCompoundUri = "https://something-new.com/232"; assertEq( @@ -629,7 +584,11 @@ contract GameSummaryBoundTest is StdCheats, Test { "https://example.api.com/", Strings.toHexString(uint160(address(gameSummary)), 20), "/", - _tokenId.toString() + Strings.toString(_storeId), + "-", + Strings.toString(_playerId), + "-", + Strings.toString(_gameId) ) ) ); @@ -643,15 +602,22 @@ contract GameSummaryBoundTest is StdCheats, Test { "https://something-new.com/232/", Strings.toHexString(uint160(address(gameSummary)), 20), "/", - _tokenId.toString() + Strings.toString(_storeId), + "-", + Strings.toString(_playerId), + "-", + Strings.toString(_gameId) ) ) ); } function testNonSoulboundTokenTransfer() public { - uint256 _tokenId = _tokenIds[0]; - gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, false); + uint256 _storeId = _storeIds[0]; + uint256 _playerId = _playerIds[0]; + uint256 _gameId = _gameIds[0]; + uint256 _tokenId = gameSummary.getTokenId(_storeId, _playerId, _gameId); + gameSummary.adminMintId(playerWallet.addr, _storeId, _playerId, _gameId, 1, false); vm.prank(playerWallet.addr); gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenId, 1, ""); @@ -661,8 +627,11 @@ contract GameSummaryBoundTest is StdCheats, Test { } function testSoulboundTokenNotTransfer() public { - uint256 _tokenId = _tokenIds[0]; - gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, true); + uint256 _storeId = _storeIds[0]; + uint256 _playerId = _playerIds[0]; + uint256 _gameId = _gameIds[0]; + uint256 _tokenId = gameSummary.getTokenId(_storeId, _playerId, _gameId); + gameSummary.adminMintId(playerWallet.addr, _storeId, _playerId, _gameId, 1, true); vm.expectRevert( "Achievo1155Soulbound: The amount of soulbounded tokens is more than the amount of tokens to be transferred" @@ -676,8 +645,11 @@ contract GameSummaryBoundTest is StdCheats, Test { } function testSoulboundTokenTransferOnlyWhitelistAddresses() public { - uint256 _tokenId = _tokenIds[0]; - gameSummary.adminMintId(playerWallet.addr, _tokenId, 1, true); + uint256 _storeId = _storeIds[0]; + uint256 _playerId = _playerIds[0]; + uint256 _gameId = _gameIds[0]; + uint256 _tokenId = gameSummary.getTokenId(_storeId, _playerId, _gameId); + gameSummary.adminMintId(playerWallet.addr, _storeId, _playerId, _gameId, 1, true); vm.expectRevert( "Achievo1155Soulbound: The amount of soulbounded tokens is more than the amount of tokens to be transferred" @@ -704,79 +676,98 @@ contract GameSummaryBoundTest is StdCheats, Test { function testgetAllItems() public { gameSummary.setCompoundURIEnabled(false); - - bytes memory encodedItemsAll = encode(address(gameSummary), _tokenIds); + bytes memory encodedItemsAll = encode(address(gameSummary), _storeIds, _playerIds, _gameIds); gameSummary.adminMint(playerWallet.addr, encodedItemsAll, false); - string memory newTokenUri = "https://something-new.com/232"; - gameSummary.updateTokenUri(_tokenIds[23], newTokenUri); - assertEq(gameSummary.uri(_tokenIds[23]), "https://something-new.com/232"); - vm.prank(playerWallet.addr); - LibItems.TokenReturn[] memory allTokensInfo = gameSummary.getAllItems(); + LibGameSummary.GameSummaryReturn[] memory allTokensInfo = gameSummary.getAllItems(); assertEq(allTokensInfo.length, 1300); vm.prank(playerWallet.addr); - gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenIds[24], 1, ""); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenIds[14], 1, ""); vm.prank(playerWallet.addr); - LibItems.TokenReturn[] memory allTokensInfo2 = gameSummary.getAllItems(); + LibGameSummary.GameSummaryReturn[] memory allTokensInfo2 = gameSummary.getAllItems(); assertEq(allTokensInfo2.length, 1299); for (uint256 i = 0; i < allTokensInfo.length; i++) { assertEq(allTokensInfo[i].tokenId, _tokenIds[i]); - if (i == 23) { - assertEq(allTokensInfo[i].tokenUri, newTokenUri); - assertEq(allTokensInfo[i].amount, 1); - } else { - assertEq(allTokensInfo[i].amount, 1); - assertEq( - allTokensInfo[i].tokenUri, - string(abi.encodePacked("https://something.com", "/", _tokenIds[i].toString())) - ); + assertEq(allTokensInfo[i].amount, 1); + assertEq(allTokensInfo[i].tokenUri, string(abi.encodePacked("MISSING_BASE_URL/", _tokenIds[i].toString()))); + } + + gameSummary.setCompoundURIEnabled(true); + + LibGameSummary.GameSummaryReturn[] memory allTokensInfoAfter = gameSummary.getAllItemsAdmin(playerWallet.addr); + for (uint256 i = 0; i < allTokensInfoAfter.length; i++) { + assertEq(allTokensInfoAfter[i].tokenId, _tokenIds[i]); + if (i != 14) { + assertEq(allTokensInfoAfter[i].amount, 1); } + assertEq( + allTokensInfoAfter[i].tokenUri, + string( + abi.encodePacked( + "https://example.api.com/", + Strings.toHexString(uint160(address(gameSummary)), 20), + "/", + Strings.toString(_storeIds[i]), + "-", + Strings.toString(_playerIds[i]), + "-", + Strings.toString(_gameIds[i]) + ) + ) + ); } vm.prank(minterWallet.addr); - LibItems.TokenReturn[] memory allTokensInfo3 = gameSummary.getAllItems(); + LibGameSummary.GameSummaryReturn[] memory allTokensInfo3 = gameSummary.getAllItems(); assertEq(allTokensInfo3.length, 1); } function testgetAllItemsAdmin() public { gameSummary.setCompoundURIEnabled(false); - bytes memory encodedItemsAll = encode(address(gameSummary), _tokenIds); + bytes memory encodedItemsAll = encode(address(gameSummary), _storeIds, _playerIds, _gameIds); gameSummary.adminMint(playerWallet.addr, encodedItemsAll, false); - string memory newTokenUri = "https://something-new.com/232"; - gameSummary.updateTokenUri(_tokenIds[23], newTokenUri); - assertEq(gameSummary.uri(_tokenIds[23]), "https://something-new.com/232"); - - LibItems.TokenReturn[] memory allTokensInfo = gameSummary.getAllItemsAdmin(playerWallet.addr); + LibGameSummary.GameSummaryReturn[] memory allTokensInfo = gameSummary.getAllItemsAdmin(playerWallet.addr); assertEq(allTokensInfo.length, 1300); vm.prank(playerWallet.addr); - gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenIds[24], 1, ""); - - LibItems.TokenReturn[] memory allTokensInfo2 = gameSummary.getAllItemsAdmin(playerWallet.addr); - assertEq(allTokensInfo2.length, 1300); + gameSummary.safeTransferFrom(playerWallet.addr, minterWallet.addr, _tokenIds[14], 1, ""); for (uint256 i = 0; i < allTokensInfo.length; i++) { assertEq(allTokensInfo[i].tokenId, _tokenIds[i]); - if (i == 23) { - assertEq(allTokensInfo[i].tokenUri, newTokenUri); - assertEq(allTokensInfo[i].amount, 1); - } else { - assertEq(allTokensInfo[i].amount, 1); - assertEq( - allTokensInfo[i].tokenUri, - string(abi.encodePacked("https://something.com", "/", _tokenIds[i].toString())) - ); - } + assertEq(allTokensInfo[i].amount, 1); + assertEq(allTokensInfo[i].tokenUri, string(abi.encodePacked("MISSING_BASE_URL/", _tokenIds[i].toString()))); } - LibItems.TokenReturn[] memory allTokensInfo3 = gameSummary.getAllItemsAdmin(minterWallet.addr); - assertEq(allTokensInfo3.length, 1300); + gameSummary.setCompoundURIEnabled(true); + + LibGameSummary.GameSummaryReturn[] memory allTokensInfoAfter = gameSummary.getAllItemsAdmin(playerWallet.addr); + for (uint256 i = 0; i < allTokensInfoAfter.length; i++) { + assertEq(allTokensInfoAfter[i].tokenId, _tokenIds[i]); + if (i != 14) { + assertEq(allTokensInfoAfter[i].amount, 1); + } + assertEq( + allTokensInfoAfter[i].tokenUri, + string( + abi.encodePacked( + "https://example.api.com/", + Strings.toHexString(uint160(address(gameSummary)), 20), + "/", + Strings.toString(_storeIds[i]), + "-", + Strings.toString(_playerIds[i]), + "-", + Strings.toString(_gameIds[i]) + ) + ) + ); + } } }