From 1b289da7aff30c6cd97cbeaea5cf219f8860f486 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Mon, 13 Jun 2022 06:14:46 -0700 Subject: [PATCH 01/31] Added LibSignatures --- contracts/diamond/libraries/LibSignatures.sol | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 contracts/diamond/libraries/LibSignatures.sol diff --git a/contracts/diamond/libraries/LibSignatures.sol b/contracts/diamond/libraries/LibSignatures.sol new file mode 100644 index 00000000..72caec91 --- /dev/null +++ b/contracts/diamond/libraries/LibSignatures.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +/** + * Much of the functionality in this library is adapted from OpenZeppelin's EIP712 implementation: + * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/draft-EIP712.sol + */ + +import "@openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; + +library LibSignatures { + bytes32 constant SIGNATURES_STORAGE_POSITION = + keccak256("moonstreamdao.eth.storage.signatures"); + + struct SignaturesStorage { + string name; + string version; + bytes32 CACHED_DOMAIN_SEPARATOR; + uint256 CACHED_CHAIN_ID; + bytes32 HASHED_NAME; + bytes32 HASHED_VERSION; + bytes32 TYPE_HASH; + mapping(uint256 => bool) completedRequests; + } + + function signaturesStorage() + internal + pure + returns (SignaturesStorage storage ss) + { + bytes32 position = SIGNATURES_STORAGE_POSITION; + assembly { + ss.slot := position + } + } + + function _buildDomainSeparator( + bytes32 typeHash, + bytes32 nameHash, + bytes32 versionHash + ) internal view returns (bytes32) { + return + keccak256( + abi.encode( + typeHash, + nameHash, + versionHash, + block.chainid, + address(this) + ) + ); + } + + function _setEIP712Parameters(string memory name, string memory version) + internal + { + SignaturesStorage storage ss = signaturesStorage(); + ss.name = name; + ss.version = version; + bytes32 hashedName = keccak256(bytes(name)); + bytes32 hashedVersion = keccak256(bytes(version)); + bytes32 typeHash = keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ); + ss.HASHED_NAME = hashedName; + ss.HASHED_VERSION = hashedVersion; + ss.CACHED_CHAIN_ID = block.chainid; + ss.CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator( + typeHash, + hashedName, + hashedVersion + ); + ss.TYPE_HASH = typeHash; + } + + function _domainSeparatorV4() internal view returns (bytes32) { + SignaturesStorage storage ss = signaturesStorage(); + if (block.chainid == ss.CACHED_CHAIN_ID) { + return ss.CACHED_DOMAIN_SEPARATOR; + } else { + return + _buildDomainSeparator( + ss.TYPE_HASH, + ss.HASHED_NAME, + ss.HASHED_VERSION + ); + } + } + + function _hashTypedDataV4(bytes32 structHash) + internal + view + returns (bytes32) + { + return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash); + } + + function _completeRequest(uint256 requestId) internal { + SignaturesStorage storage ss = signaturesStorage(); + ss.completedRequests[requestId] = true; + } + + function _clearRequest(uint256 requestId) internal { + SignaturesStorage storage ss = signaturesStorage(); + ss.completedRequests[requestId] = false; + } + + function _checkRequest(uint256 requestId) internal view returns (bool) { + SignaturesStorage storage ss = signaturesStorage(); + return ss.completedRequests[requestId]; + } +} From a37bfb1031e71a03d0b76308f46fc6296abfcbdd Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Mon, 13 Jun 2022 10:25:11 -0700 Subject: [PATCH 02/31] Draft implementation of DropperFacet in DropperV2 --- brownie-config.yaml | 4 +- contracts/DropperV2/DropperFacet.sol | 350 ++++++++++++++++++ contracts/DropperV2/DropperV2Initializer.sol | 30 ++ contracts/DropperV2/LibDropperV2.sol | 45 +++ .../diamond/libraries/LibReentrancyGuard.sol | 2 - contracts/diamond/libraries/LibSignatures.sol | 2 +- contracts/interfaces/IERC721Mint.sol | 11 + 7 files changed, 439 insertions(+), 5 deletions(-) create mode 100644 contracts/DropperV2/DropperFacet.sol create mode 100644 contracts/DropperV2/DropperV2Initializer.sol create mode 100644 contracts/DropperV2/LibDropperV2.sol create mode 100644 contracts/interfaces/IERC721Mint.sol diff --git a/brownie-config.yaml b/brownie-config.yaml index cc9ee9b9..474abaa7 100644 --- a/brownie-config.yaml +++ b/brownie-config.yaml @@ -1,11 +1,11 @@ dependencies: - "OpenZeppelin/openzeppelin-contracts@4.3.2" - - "bugout-dev/dao@0.0.3" + - "bugout-dev/dao@0.0.5" - "smartcontractkit/chainlink@1.0.1" compiler: solc: remappings: - "@openzeppelin-contracts=OpenZeppelin/openzeppelin-contracts@4.3.2" - - "@moonstream=bugout-dev/dao@0.0.3" + - "@moonstream=bugout-dev/dao@0.0.5" - "@chainlink=smartcontractkit/chainlink@1.0.1" diff --git a/contracts/DropperV2/DropperFacet.sol b/contracts/DropperV2/DropperFacet.sol new file mode 100644 index 00000000..048b24a1 --- /dev/null +++ b/contracts/DropperV2/DropperFacet.sol @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: Apache-2.0 + +/** + * Authors: Moonstream Engineering (engineering@moonstream.to) + * GitHub: https://github.com/bugout-dev/dao + */ + +pragma solidity ^0.8.9; + +import "@moonstream/contracts/terminus/TerminusFacet.sol"; +import "@moonstream/contracts/terminus/TerminusPermissions.sol"; +import "@openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin-contracts/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol"; +import "@openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol"; +import "@openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import "@openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol"; + +import "./LibDropperV2.sol"; +import "../interfaces/IERC721Mint.sol"; +import "../diamond/security/DiamondReentrancyGuard.sol"; +import "../diamond/libraries/LibSignatures.sol"; + +/** + * @title Moonstream DropperV2 + * @author Moonstream Engineering (engineering@moonstream.to) + * @notice This contract manages drops for ERC20, ERC1155, and ERC721 tokens. + */ +contract DropperV2Facet is + IERC721Receiver, + ERC1155Holder, + TerminusPermissions, + DiamondReentrancyGuard +{ + event Claimed( + uint256 indexed dropId, + address indexed claimant, + uint256 requestID, + uint256 amount + ); + event DropCreated( + uint256 dropId, + uint256 indexed tokenType, + address indexed tokenAddress, + uint256 indexed tokenId, + uint256 amount + ); + event DropStatusChanged(uint256 indexed dropId, bool status); + event DropSignerChanged(uint256 indexed dropId, address signer); + event Withdrawal( + address recipient, + uint256 indexed tokenType, + address indexed tokenAddress, + uint256 indexed tokenId, + uint256 amount + ); + + modifier onlyTerminusAdmin() { + LibDropperV2.DropperV2Storage storage ds = LibDropperV2 + .dropperV2Storage(); + require( + _holdsPoolToken( + ds.TerminusAdminContractAddress, + ds.TerminusAdminPoolID, + 1 + ), + "DropperFacet.onlyTerminusAdmin: Sender does not hold administrator token" + ); + + // Execute modified function + _; + } + + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4) { + return IERC721Receiver.onERC721Received.selector; + } + + function createDrop( + uint256 tokenType, + address tokenAddress, + uint256 tokenId, + uint256 amount, + uint256 _maxClaimable + ) external onlyTerminusAdmin returns (uint256) { + require( + tokenType == ERC20_TYPE || + tokenType == ERC721_TYPE || + tokenType == ERC1155_TYPE || + tokenType == TERMINUS_MINTABLE_TYPE || + tokenType == ERC721_MINTABLE_TYPE, + "DropperV2: createDrop -- Unknown token type" + ); + + require( + amount != 0, + "DropperV2: createDrop -- Amount must be greater than 0" + ); + + LibDropperV2.DropperV2Storage storage ds = LibDropperV2 + .dropperV2Storage(); + + ds.NumDrops++; + + DroppableToken memory tokenMetadata; + tokenMetadata.tokenType = tokenType; + tokenMetadata.tokenAddress = tokenAddress; + tokenMetadata.tokenId = tokenId; + tokenMetadata.amount = amount; + ds.DropToken[ds.NumDrops] = tokenMetadata; + emit DropCreated(ds.NumDrops, tokenType, tokenAddress, tokenId, amount); + + ds.IsDropActive[ds.NumDrops] = true; + emit DropStatusChanged(ds.NumDrops, true); + + ds.MaxClaimable[ds.NumDrops] = _maxClaimable; + + return ds.NumDrops; + } + + function numDrops() external view returns (uint256) { + return LibDropperV2.dropperV2Storage().NumDrops; + } + + function getDrop(uint256 dropId) + external + view + returns (DroppableToken memory) + { + return LibDropperV2.dropperV2Storage().DropToken[dropId]; + } + + function setDropStatus(uint256 dropId, bool status) + external + onlyTerminusAdmin + { + LibDropperV2.DropperV2Storage storage ds = LibDropperV2 + .dropperV2Storage(); + ds.IsDropActive[dropId] = status; + emit DropStatusChanged(dropId, status); + } + + function dropStatus(uint256 dropId) external view returns (bool) { + return LibDropperV2.dropperV2Storage().IsDropActive[dropId]; + } + + function setSignerForDrop(uint256 dropId, address signer) + public + onlyTerminusAdmin + { + LibDropperV2.DropperV2Storage storage ds = LibDropperV2 + .dropperV2Storage(); + ds.DropSigner[dropId] = signer; + emit DropSignerChanged(dropId, signer); + } + + function getSignerForDrop(uint256 dropId) external view returns (address) { + return LibDropperV2.dropperV2Storage().DropSigner[dropId]; + } + + function maxClaimable(uint256 dropId) external view returns (uint256) { + return LibDropperV2.dropperV2Storage().MaxClaimable[dropId]; + } + + function getAmountClaimed(address claimant, uint256 dropId) + external + view + returns (uint256) + { + return LibDropperV2.dropperV2Storage().AmountClaimed[claimant][dropId]; + } + + function claimMessageHash( + uint256 requestID, + uint256 dropId, + address claimant, + uint256 blockDeadline, + uint256 amount + ) public view returns (bytes32) { + bytes32 structHash = keccak256( + abi.encode( + keccak256( + "ClaimPayload(uint256 requestID,uint256 dropId,address claimant,uint256 blockDeadline,uint256 amount)" + ), + requestID, + dropId, + claimant, + blockDeadline, + amount + ) + ); + bytes32 digest = LibSignatures._hashTypedDataV4(structHash); + return digest; + } + + function claim( + uint256 requestID, + uint256 dropId, + uint256 blockDeadline, + uint256 amount, + bytes memory signature + ) external diamondNonReentrant { + require( + block.number <= blockDeadline, + "DropperV2: claim -- Block deadline exceeded." + ); + + LibDropperV2.DropperV2Storage storage ds = LibDropperV2 + .dropperV2Storage(); + + require( + ds.AmountClaimed[msg.sender][dropId] + amount <= + ds.MaxClaimable[dropId], + "DropperV2: claim -- Claimant would exceed the maximum claimable number of tokens for this drop." + ); + + bytes32 hash = claimMessageHash( + requestID, + dropId, + msg.sender, + blockDeadline, + amount + ); + require( + SignatureChecker.isValidSignatureNow( + ds.DropSigner[dropId], + hash, + signature + ), + "DropperV2: claim -- Invalid signer for claim." + ); + + DroppableToken memory claimToken = ds.DropToken[dropId]; + + if (amount == 0) { + amount = claimToken.amount; + } + + if (claimToken.tokenType == ERC20_TYPE) { + IERC20 erc20Contract = IERC20(claimToken.tokenAddress); + erc20Contract.transfer(msg.sender, amount); + } else if (claimToken.tokenType == ERC721_TYPE) { + IERC721 erc721Contract = IERC721(claimToken.tokenAddress); + erc721Contract.safeTransferFrom( + address(this), + msg.sender, + claimToken.tokenId, + "" + ); + } else if (claimToken.tokenType == ERC1155_TYPE) { + IERC1155 erc1155Contract = IERC1155(claimToken.tokenAddress); + erc1155Contract.safeTransferFrom( + address(this), + msg.sender, + claimToken.tokenId, + amount, + "" + ); + } else if (claimToken.tokenType == TERMINUS_MINTABLE_TYPE) { + TerminusFacet terminusFacetContract = TerminusFacet( + claimToken.tokenAddress + ); + terminusFacetContract.mint( + msg.sender, + claimToken.tokenId, + amount, + "" + ); + } else if (claimToken.tokenType == ERC721_MINTABLE_TYPE) { + IERC721Mint erc721MintableContract = IERC721Mint( + claimToken.tokenAddress + ); + uint256 numTokens = erc721MintableContract.totalSupply(); + erc721MintableContract.mint(msg.sender, numTokens + 1); + } else { + revert("Dropper -- claim: Unknown token type in claim"); + } + + ds.AmountClaimed[msg.sender][dropId] += amount; + + emit Claimed(dropId, msg.sender, requestID, amount); + } + + function withdrawERC20(address tokenAddress, uint256 amount) + public + onlyTerminusAdmin + { + IERC20 erc20Contract = IERC20(tokenAddress); + erc20Contract.transfer(msg.sender, amount); + emit Withdrawal(msg.sender, ERC20_TYPE, tokenAddress, 0, amount); + } + + function withdrawERC721(address tokenAddress, uint256 tokenId) + public + onlyTerminusAdmin + { + IERC721 erc721Contract = IERC721(tokenAddress); + erc721Contract.safeTransferFrom(address(this), msg.sender, tokenId, ""); + emit Withdrawal(msg.sender, ERC721_TYPE, tokenAddress, tokenId, 1); + } + + function withdrawERC1155( + address tokenAddress, + uint256 tokenId, + uint256 amount + ) public onlyTerminusAdmin { + IERC1155 erc1155Contract = IERC1155(tokenAddress); + erc1155Contract.safeTransferFrom( + address(this), + msg.sender, + tokenId, + amount, + "" + ); + emit Withdrawal( + msg.sender, + ERC1155_TYPE, + tokenAddress, + tokenId, + amount + ); + } + + function surrenderPoolControl( + uint256 poolId, + address terminusAddress, + address newPoolController + ) public onlyTerminusAdmin { + TerminusFacet terminusFacetContract = TerminusFacet(terminusAddress); + terminusFacetContract.setPoolController(poolId, newPoolController); + } + + function dropUri(uint256 dropId) public view returns (string memory) { + return LibDropperV2.dropperV2Storage().DropURI[dropId]; + } + + function setDropUri(uint256 dropId, string memory uri) + external + onlyTerminusAdmin + { + LibDropperV2.DropperV2Storage storage ds = LibDropperV2 + .dropperV2Storage(); + + ds.DropURI[dropId] = uri; + } +} diff --git a/contracts/DropperV2/DropperV2Initializer.sol b/contracts/DropperV2/DropperV2Initializer.sol new file mode 100644 index 00000000..5579d0cd --- /dev/null +++ b/contracts/DropperV2/DropperV2Initializer.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 + +/** + * Authors: Moonstream Engineering (engineering@moonstream.to) + * GitHub: https://github.com/bugout-dev/dao + * + * Initializer for Terminus contract. Used when mounting a new TerminusFacet onto its diamond proxy. + */ + +pragma solidity ^0.8.9; + +import "./LibDropperV2.sol"; +import "../diamond/libraries/LibSignatures.sol"; + +contract DropperV2Initializer { + function init( + address terminusAdminContractAddress, + uint256 terminusAdminPoolID + ) external { + // Set up server side signing parameters for EIP712 + LibSignatures._setEIP712Parameters("Moonstream Dropper", "2.0.0"); + + // Initialize Terminus administration information + LibDropperV2.DropperV2Storage storage ds = LibDropperV2 + .dropperV2Storage(); + + ds.TerminusAdminContractAddress = terminusAdminContractAddress; + ds.TerminusAdminPoolID = terminusAdminPoolID; + } +} diff --git a/contracts/DropperV2/LibDropperV2.sol b/contracts/DropperV2/LibDropperV2.sol new file mode 100644 index 00000000..ad552aea --- /dev/null +++ b/contracts/DropperV2/LibDropperV2.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +struct DroppableToken { + uint256 tokenType; + address tokenAddress; // address of the token + uint256 tokenId; + uint256 amount; +} + +uint256 constant ERC20_TYPE = 20; +uint256 constant ERC721_TYPE = 721; +uint256 constant ERC1155_TYPE = 1155; +uint256 constant TERMINUS_MINTABLE_TYPE = 1; +uint256 constant ERC721_MINTABLE_TYPE = 2; + +library LibDropperV2 { + bytes32 constant DROPPERV2_STORAGE_POSITION = + keccak256("moonstreamdao.eth.storage.DropperV2"); + + struct DropperV2Storage { + address TerminusAdminContractAddress; + uint256 TerminusAdminPoolID; + uint256 NumDrops; + mapping(uint256 => bool) IsDropActive; + mapping(uint256 => address) DropSigner; + mapping(uint256 => DroppableToken) DropToken; + mapping(uint256 => string) DropURI; + // dropID => maximum number of tokens a user can claim as part of this drop + mapping(uint256 => uint256) MaxClaimable; + // address => dropID => total amount claimed for that drop + mapping(address => mapping(uint256 => uint256)) AmountClaimed; + } + + function dropperV2Storage() + internal + pure + returns (DropperV2Storage storage ds) + { + bytes32 position = DROPPERV2_STORAGE_POSITION; + assembly { + ds.slot := position + } + } +} diff --git a/contracts/diamond/libraries/LibReentrancyGuard.sol b/contracts/diamond/libraries/LibReentrancyGuard.sol index f17908cb..ac61d6e2 100644 --- a/contracts/diamond/libraries/LibReentrancyGuard.sol +++ b/contracts/diamond/libraries/LibReentrancyGuard.sol @@ -7,8 +7,6 @@ pragma solidity ^0.8.0; -import {IDiamondCut} from "../../diamond/interfaces/IDiamondCut.sol"; - library LibReentrancyGuard { bytes32 constant REENTRANCY_GUARD_STORAGE_POSITION = keccak256("moonstreamdao.eth.storage.reentrancy"); diff --git a/contracts/diamond/libraries/LibSignatures.sol b/contracts/diamond/libraries/LibSignatures.sol index 72caec91..2072681c 100644 --- a/contracts/diamond/libraries/LibSignatures.sol +++ b/contracts/diamond/libraries/LibSignatures.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; /** diff --git a/contracts/interfaces/IERC721Mint.sol b/contracts/interfaces/IERC721Mint.sol new file mode 100644 index 00000000..56acf07d --- /dev/null +++ b/contracts/interfaces/IERC721Mint.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.0; + +import "@openzeppelin-contracts/contracts/token/ERC721/IERC721.sol"; + +interface IERC721Mint is IERC721 { + function mint(address to, uint256 tokenId) external; + + function totalSupply() external view returns (uint256); +} From 74966550d8a1c9914f32108af573ecc2e1fd2318 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Mon, 13 Jun 2022 10:26:12 -0700 Subject: [PATCH 03/31] Filename DropperFacet -> DropperV2Facet --- contracts/DropperV2/{DropperFacet.sol => DropperV2Facet.sol} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contracts/DropperV2/{DropperFacet.sol => DropperV2Facet.sol} (100%) diff --git a/contracts/DropperV2/DropperFacet.sol b/contracts/DropperV2/DropperV2Facet.sol similarity index 100% rename from contracts/DropperV2/DropperFacet.sol rename to contracts/DropperV2/DropperV2Facet.sol From 67d8aab017e0c60ae54e4ee1ad7b19386e490f09 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Fri, 12 Aug 2022 12:20:57 -0700 Subject: [PATCH 04/31] DropperV2 -> Dropper With better versioning. Added versioned ABIs in `abi/` directory. --- abi/Dropper/v0.1.0/Dropper.json | 779 ++++++++++++++++++ abi/Dropper/v0.2.0/DropperFacet.json | 713 ++++++++++++++++ .../{DropperV2Facet.sol => DropperFacet.sol} | 72 +- contracts/DropperV2/DropperV2Initializer.sol | 30 - .../{LibDropperV2.sol => LibDropper.sol} | 10 +- 5 files changed, 1542 insertions(+), 62 deletions(-) create mode 100644 abi/Dropper/v0.1.0/Dropper.json create mode 100644 abi/Dropper/v0.2.0/DropperFacet.json rename contracts/DropperV2/{DropperV2Facet.sol => DropperFacet.sol} (81%) delete mode 100644 contracts/DropperV2/DropperV2Initializer.sol rename contracts/DropperV2/{LibDropperV2.sol => LibDropper.sol} (86%) diff --git a/abi/Dropper/v0.1.0/Dropper.json b/abi/Dropper/v0.1.0/Dropper.json new file mode 100644 index 00000000..48f7b556 --- /dev/null +++ b/abi/Dropper/v0.1.0/Dropper.json @@ -0,0 +1,779 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenType", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ClaimCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "ClaimSignerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "name": "ClaimStatusChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "claimant", + "type": "address" + } + ], + "name": "Claimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenType", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + }, + { + "inputs": [], + "name": "ERC1155_TYPE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ERC20_TYPE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ERC721_TYPE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TERMINUS_MINTABLE_TYPE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "blockDeadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "claim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "claimant", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockDeadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "claimMessageHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + } + ], + "name": "claimStatus", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + } + ], + "name": "claimUri", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenType", + "type": "uint256" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "createClaim", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + } + ], + "name": "getClaim", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "tokenType", + "type": "uint256" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Dropper.ClaimableToken", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "claimant", + "type": "address" + } + ], + "name": "getClaimStatus", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + } + ], + "name": "getSignerForClaim", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "numClaims", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155BatchReceived", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "onERC721Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "name": "setClaimStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "uri", + "type": "string" + } + ], + "name": "setClaimUri", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "claimId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "setSignerForClaim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "poolId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "terminusAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "newPoolController", + "type": "address" + } + ], + "name": "surrenderPoolControl", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdrawERC1155", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdrawERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "withdrawERC721", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/abi/Dropper/v0.2.0/DropperFacet.json b/abi/Dropper/v0.2.0/DropperFacet.json new file mode 100644 index 00000000..28ed3310 --- /dev/null +++ b/abi/Dropper/v0.2.0/DropperFacet.json @@ -0,0 +1,713 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "claimant", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "requestID", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Claimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenType", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "DropCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "DropSignerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "name": "DropStatusChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenType", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestID", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "blockDeadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "claim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestID", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "claimant", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockDeadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "claimMessageHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenType", + "type": "uint256" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxClaimable", + "type": "uint256" + } + ], + "name": "createDrop", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + } + ], + "name": "dropStatus", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + } + ], + "name": "dropUri", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "dropperVersion", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + }, + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "claimant", + "type": "address" + }, + { + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + } + ], + "name": "getAmountClaimed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + } + ], + "name": "getDrop", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "tokenType", + "type": "uint256" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct DroppableToken", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + } + ], + "name": "getSignerForDrop", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "terminusAdminContractAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "terminusAdminPoolID", + "type": "uint256" + } + ], + "name": "init", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + } + ], + "name": "maxClaimable", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "numDrops", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155BatchReceived", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "onERC721Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "name": "setDropStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "uri", + "type": "string" + } + ], + "name": "setDropUri", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "dropId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "setSignerForDrop", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "poolId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "terminusAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "newPoolController", + "type": "address" + } + ], + "name": "surrenderPoolControl", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdrawERC1155", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdrawERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "withdrawERC721", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/contracts/DropperV2/DropperV2Facet.sol b/contracts/DropperV2/DropperFacet.sol similarity index 81% rename from contracts/DropperV2/DropperV2Facet.sol rename to contracts/DropperV2/DropperFacet.sol index 048b24a1..be5744a1 100644 --- a/contracts/DropperV2/DropperV2Facet.sol +++ b/contracts/DropperV2/DropperFacet.sol @@ -16,17 +16,17 @@ import "@openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol"; import "@openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol"; import "@openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol"; -import "./LibDropperV2.sol"; +import "./LibDropper.sol"; import "../interfaces/IERC721Mint.sol"; import "../diamond/security/DiamondReentrancyGuard.sol"; import "../diamond/libraries/LibSignatures.sol"; /** - * @title Moonstream DropperV2 + * @title Moonstream Dropper * @author Moonstream Engineering (engineering@moonstream.to) * @notice This contract manages drops for ERC20, ERC1155, and ERC721 tokens. */ -contract DropperV2Facet is +contract DropperFacet is IERC721Receiver, ERC1155Holder, TerminusPermissions, @@ -56,8 +56,7 @@ contract DropperV2Facet is ); modifier onlyTerminusAdmin() { - LibDropperV2.DropperV2Storage storage ds = LibDropperV2 - .dropperV2Storage(); + LibDropper.DropperStorage storage ds = LibDropper.dropperStorage(); require( _holdsPoolToken( ds.TerminusAdminContractAddress, @@ -71,6 +70,30 @@ contract DropperV2Facet is _; } + function init( + address terminusAdminContractAddress, + uint256 terminusAdminPoolID + ) external { + // Set up server side signing parameters for EIP712 + LibSignatures._setEIP712Parameters("Moonstream Dropper", "0.2.0"); + + // Initialize Terminus administration information + LibDropper.DropperStorage storage ds = LibDropper.dropperStorage(); + + ds.TerminusAdminContractAddress = terminusAdminContractAddress; + ds.TerminusAdminPoolID = terminusAdminPoolID; + } + + function dropperVersion() + public + view + returns (string memory, string memory) + { + LibSignatures.SignaturesStorage storage ss = LibSignatures + .signaturesStorage(); + return (ss.name, ss.version); + } + function onERC721Received( address operator, address from, @@ -93,16 +116,15 @@ contract DropperV2Facet is tokenType == ERC1155_TYPE || tokenType == TERMINUS_MINTABLE_TYPE || tokenType == ERC721_MINTABLE_TYPE, - "DropperV2: createDrop -- Unknown token type" + "Dropper: createDrop -- Unknown token type" ); require( amount != 0, - "DropperV2: createDrop -- Amount must be greater than 0" + "Dropper: createDrop -- Amount must be greater than 0" ); - LibDropperV2.DropperV2Storage storage ds = LibDropperV2 - .dropperV2Storage(); + LibDropper.DropperStorage storage ds = LibDropper.dropperStorage(); ds.NumDrops++; @@ -123,7 +145,7 @@ contract DropperV2Facet is } function numDrops() external view returns (uint256) { - return LibDropperV2.dropperV2Storage().NumDrops; + return LibDropper.dropperStorage().NumDrops; } function getDrop(uint256 dropId) @@ -131,39 +153,37 @@ contract DropperV2Facet is view returns (DroppableToken memory) { - return LibDropperV2.dropperV2Storage().DropToken[dropId]; + return LibDropper.dropperStorage().DropToken[dropId]; } function setDropStatus(uint256 dropId, bool status) external onlyTerminusAdmin { - LibDropperV2.DropperV2Storage storage ds = LibDropperV2 - .dropperV2Storage(); + LibDropper.DropperStorage storage ds = LibDropper.dropperStorage(); ds.IsDropActive[dropId] = status; emit DropStatusChanged(dropId, status); } function dropStatus(uint256 dropId) external view returns (bool) { - return LibDropperV2.dropperV2Storage().IsDropActive[dropId]; + return LibDropper.dropperStorage().IsDropActive[dropId]; } function setSignerForDrop(uint256 dropId, address signer) public onlyTerminusAdmin { - LibDropperV2.DropperV2Storage storage ds = LibDropperV2 - .dropperV2Storage(); + LibDropper.DropperStorage storage ds = LibDropper.dropperStorage(); ds.DropSigner[dropId] = signer; emit DropSignerChanged(dropId, signer); } function getSignerForDrop(uint256 dropId) external view returns (address) { - return LibDropperV2.dropperV2Storage().DropSigner[dropId]; + return LibDropper.dropperStorage().DropSigner[dropId]; } function maxClaimable(uint256 dropId) external view returns (uint256) { - return LibDropperV2.dropperV2Storage().MaxClaimable[dropId]; + return LibDropper.dropperStorage().MaxClaimable[dropId]; } function getAmountClaimed(address claimant, uint256 dropId) @@ -171,7 +191,7 @@ contract DropperV2Facet is view returns (uint256) { - return LibDropperV2.dropperV2Storage().AmountClaimed[claimant][dropId]; + return LibDropper.dropperStorage().AmountClaimed[claimant][dropId]; } function claimMessageHash( @@ -206,16 +226,15 @@ contract DropperV2Facet is ) external diamondNonReentrant { require( block.number <= blockDeadline, - "DropperV2: claim -- Block deadline exceeded." + "Dropper: claim -- Block deadline exceeded." ); - LibDropperV2.DropperV2Storage storage ds = LibDropperV2 - .dropperV2Storage(); + LibDropper.DropperStorage storage ds = LibDropper.dropperStorage(); require( ds.AmountClaimed[msg.sender][dropId] + amount <= ds.MaxClaimable[dropId], - "DropperV2: claim -- Claimant would exceed the maximum claimable number of tokens for this drop." + "Dropper: claim -- Claimant would exceed the maximum claimable number of tokens for this drop." ); bytes32 hash = claimMessageHash( @@ -231,7 +250,7 @@ contract DropperV2Facet is hash, signature ), - "DropperV2: claim -- Invalid signer for claim." + "Dropper: claim -- Invalid signer for claim." ); DroppableToken memory claimToken = ds.DropToken[dropId]; @@ -335,15 +354,14 @@ contract DropperV2Facet is } function dropUri(uint256 dropId) public view returns (string memory) { - return LibDropperV2.dropperV2Storage().DropURI[dropId]; + return LibDropper.dropperStorage().DropURI[dropId]; } function setDropUri(uint256 dropId, string memory uri) external onlyTerminusAdmin { - LibDropperV2.DropperV2Storage storage ds = LibDropperV2 - .dropperV2Storage(); + LibDropper.DropperStorage storage ds = LibDropper.dropperStorage(); ds.DropURI[dropId] = uri; } diff --git a/contracts/DropperV2/DropperV2Initializer.sol b/contracts/DropperV2/DropperV2Initializer.sol deleted file mode 100644 index 5579d0cd..00000000 --- a/contracts/DropperV2/DropperV2Initializer.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/** - * Authors: Moonstream Engineering (engineering@moonstream.to) - * GitHub: https://github.com/bugout-dev/dao - * - * Initializer for Terminus contract. Used when mounting a new TerminusFacet onto its diamond proxy. - */ - -pragma solidity ^0.8.9; - -import "./LibDropperV2.sol"; -import "../diamond/libraries/LibSignatures.sol"; - -contract DropperV2Initializer { - function init( - address terminusAdminContractAddress, - uint256 terminusAdminPoolID - ) external { - // Set up server side signing parameters for EIP712 - LibSignatures._setEIP712Parameters("Moonstream Dropper", "2.0.0"); - - // Initialize Terminus administration information - LibDropperV2.DropperV2Storage storage ds = LibDropperV2 - .dropperV2Storage(); - - ds.TerminusAdminContractAddress = terminusAdminContractAddress; - ds.TerminusAdminPoolID = terminusAdminPoolID; - } -} diff --git a/contracts/DropperV2/LibDropperV2.sol b/contracts/DropperV2/LibDropper.sol similarity index 86% rename from contracts/DropperV2/LibDropperV2.sol rename to contracts/DropperV2/LibDropper.sol index ad552aea..37870bfe 100644 --- a/contracts/DropperV2/LibDropperV2.sol +++ b/contracts/DropperV2/LibDropper.sol @@ -14,11 +14,11 @@ uint256 constant ERC1155_TYPE = 1155; uint256 constant TERMINUS_MINTABLE_TYPE = 1; uint256 constant ERC721_MINTABLE_TYPE = 2; -library LibDropperV2 { +library LibDropper { bytes32 constant DROPPERV2_STORAGE_POSITION = - keccak256("moonstreamdao.eth.storage.DropperV2"); + keccak256("moonstreamdao.eth.storage.Dropper"); - struct DropperV2Storage { + struct DropperStorage { address TerminusAdminContractAddress; uint256 TerminusAdminPoolID; uint256 NumDrops; @@ -32,10 +32,10 @@ library LibDropperV2 { mapping(address => mapping(uint256 => uint256)) AmountClaimed; } - function dropperV2Storage() + function dropperStorage() internal pure - returns (DropperV2Storage storage ds) + returns (DropperStorage storage ds) { bytes32 position = DROPPERV2_STORAGE_POSITION; assembly { From 7c67fcf9f01d57dd0f732543352603d1faae336f Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Fri, 12 Aug 2022 13:53:15 -0700 Subject: [PATCH 05/31] [WIP] Testing Dropper 0.2.0 --- cli/enginecli/Dropper.py | 921 ------------------ .../{DropperV2Facet.py => DropperFacet.py} | 106 +- cli/enginecli/cli.py | 12 +- cli/enginecli/core.py | 249 ++++- cli/enginecli/test_dropper.py | 22 +- cli/enginecli/test_lootbox.py | 4 +- cli/enginecli/test_random_lootbox.py | 2 +- cli/enginecli/test_reentrancy_guard.py | 2 +- contracts/Dropper.sol | 344 ------- .../{DropperV2 => Dropper}/DropperFacet.sol | 0 .../{DropperV2 => Dropper}/LibDropper.sol | 0 11 files changed, 331 insertions(+), 1331 deletions(-) delete mode 100644 cli/enginecli/Dropper.py rename cli/enginecli/{DropperV2Facet.py => DropperFacet.py} (91%) delete mode 100644 contracts/Dropper.sol rename contracts/{DropperV2 => Dropper}/DropperFacet.sol (100%) rename contracts/{DropperV2 => Dropper}/LibDropper.sol (100%) diff --git a/cli/enginecli/Dropper.py b/cli/enginecli/Dropper.py deleted file mode 100644 index 7167dc38..00000000 --- a/cli/enginecli/Dropper.py +++ /dev/null @@ -1,921 +0,0 @@ -# Code generated by moonworm : https://github.com/bugout-dev/moonworm -# Moonworm version : 0.2.1 - -import argparse -import json -import os -from pathlib import Path -from typing import Any, Dict, List, Optional, Union - -from brownie import Contract, network, project -from brownie.network.contract import ContractContainer -from eth_typing.evm import ChecksumAddress - - -PROJECT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) -BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "build", "contracts") - - -def boolean_argument_type(raw_value: str) -> bool: - TRUE_VALUES = ["1", "t", "y", "true", "yes"] - FALSE_VALUES = ["0", "f", "n", "false", "no"] - - if raw_value.lower() in TRUE_VALUES: - return True - elif raw_value.lower() in FALSE_VALUES: - return False - - raise ValueError( - f"Invalid boolean argument: {raw_value}. Value must be one of: {','.join(TRUE_VALUES + FALSE_VALUES)}" - ) - - -def bytes_argument_type(raw_value: str) -> str: - return raw_value - - -def get_abi_json(abi_name: str) -> List[Dict[str, Any]]: - abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") - if not os.path.isfile(abi_full_path): - raise IOError( - f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" - ) - - with open(abi_full_path, "r") as ifp: - build = json.load(ifp) - - abi_json = build.get("abi") - if abi_json is None: - raise ValueError(f"Could not find ABI definition in: {abi_full_path}") - - return abi_json - - -def contract_from_build(abi_name: str) -> ContractContainer: - # This is workaround because brownie currently doesn't support loading the same project multiple - # times. This causes problems when using multiple contracts from the same project in the same - # python project. - PROJECT = project.main.Project("moonworm", Path(PROJECT_DIRECTORY)) - - abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") - if not os.path.isfile(abi_full_path): - raise IOError( - f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" - ) - - with open(abi_full_path, "r") as ifp: - build = json.load(ifp) - - return ContractContainer(PROJECT, build) - - -class Dropper: - def __init__(self, contract_address: Optional[ChecksumAddress]): - self.contract_name = "Dropper" - self.address = contract_address - self.contract = None - self.abi = get_abi_json("Dropper") - if self.address is not None: - self.contract: Optional[Contract] = Contract.from_abi( - self.contract_name, self.address, self.abi - ) - - def deploy(self, transaction_config): - contract_class = contract_from_build(self.contract_name) - deployed_contract = contract_class.deploy(transaction_config) - self.address = deployed_contract.address - self.contract = deployed_contract - - def assert_contract_is_instantiated(self) -> None: - if self.contract is None: - raise Exception("contract has not been instantiated") - - def verify_contract(self): - self.assert_contract_is_instantiated() - contract_class = contract_from_build(self.contract_name) - contract_class.publish_source(self.contract) - - def erc1155_type(self) -> Any: - self.assert_contract_is_instantiated() - return self.contract.ERC1155_TYPE.call() - - def erc20_type(self) -> Any: - self.assert_contract_is_instantiated() - return self.contract.ERC20_TYPE.call() - - def erc721_type(self) -> Any: - self.assert_contract_is_instantiated() - return self.contract.ERC721_TYPE.call() - - def terminus_mintable_type(self) -> Any: - self.assert_contract_is_instantiated() - return self.contract.TERMINUS_MINTABLE_TYPE.call() - - def claim( - self, - claim_id: int, - block_deadline: int, - amount: int, - signature: bytes, - transaction_config, - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.claim( - claim_id, block_deadline, amount, signature, transaction_config - ) - - def claim_message_hash( - self, claim_id: int, claimant: ChecksumAddress, block_deadline: int, amount: int - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.claimMessageHash.call( - claim_id, claimant, block_deadline, amount - ) - - def claim_status(self, claim_id: int) -> Any: - self.assert_contract_is_instantiated() - return self.contract.claimStatus.call(claim_id) - - def claim_uri(self, claim_id: int) -> Any: - self.assert_contract_is_instantiated() - return self.contract.claimUri.call(claim_id) - - def create_claim( - self, - token_type: int, - token_address: ChecksumAddress, - token_id: int, - amount: int, - transaction_config, - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.createClaim( - token_type, token_address, token_id, amount, transaction_config - ) - - def get_claim(self, claim_id: int) -> Any: - self.assert_contract_is_instantiated() - return self.contract.getClaim.call(claim_id) - - def get_claim_status(self, claim_id: int, claimant: ChecksumAddress) -> Any: - self.assert_contract_is_instantiated() - return self.contract.getClaimStatus.call(claim_id, claimant) - - def get_signer_for_claim(self, claim_id: int) -> Any: - self.assert_contract_is_instantiated() - return self.contract.getSignerForClaim.call(claim_id) - - def num_claims(self) -> Any: - self.assert_contract_is_instantiated() - return self.contract.numClaims.call() - - def on_erc1155_batch_received( - self, - arg1: ChecksumAddress, - arg2: ChecksumAddress, - arg3: List, - arg4: List, - arg5: bytes, - transaction_config, - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.onERC1155BatchReceived( - arg1, arg2, arg3, arg4, arg5, transaction_config - ) - - def on_erc1155_received( - self, - arg1: ChecksumAddress, - arg2: ChecksumAddress, - arg3: int, - arg4: int, - arg5: bytes, - transaction_config, - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.onERC1155Received( - arg1, arg2, arg3, arg4, arg5, transaction_config - ) - - def on_erc721_received( - self, - operator: ChecksumAddress, - from_: ChecksumAddress, - token_id: int, - data: bytes, - transaction_config, - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.onERC721Received( - operator, from_, token_id, data, transaction_config - ) - - def owner(self) -> Any: - self.assert_contract_is_instantiated() - return self.contract.owner.call() - - def paused(self) -> Any: - self.assert_contract_is_instantiated() - return self.contract.paused.call() - - def renounce_ownership(self, transaction_config) -> Any: - self.assert_contract_is_instantiated() - return self.contract.renounceOwnership(transaction_config) - - def set_claim_status(self, claim_id: int, status: bool, transaction_config) -> Any: - self.assert_contract_is_instantiated() - return self.contract.setClaimStatus(claim_id, status, transaction_config) - - def set_claim_uri(self, claim_id: int, uri: str, transaction_config) -> Any: - self.assert_contract_is_instantiated() - return self.contract.setClaimUri(claim_id, uri, transaction_config) - - def set_signer_for_claim( - self, claim_id: int, signer: ChecksumAddress, transaction_config - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.setSignerForClaim(claim_id, signer, transaction_config) - - def supports_interface(self, interface_id: bytes) -> Any: - self.assert_contract_is_instantiated() - return self.contract.supportsInterface.call(interface_id) - - def surrender_pool_control( - self, - pool_id: int, - terminus_address: ChecksumAddress, - new_pool_controller: ChecksumAddress, - transaction_config, - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.surrenderPoolControl( - pool_id, terminus_address, new_pool_controller, transaction_config - ) - - def transfer_ownership(self, new_owner: ChecksumAddress, transaction_config) -> Any: - self.assert_contract_is_instantiated() - return self.contract.transferOwnership(new_owner, transaction_config) - - def withdraw_erc1155( - self, - token_address: ChecksumAddress, - token_id: int, - amount: int, - transaction_config, - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.withdrawERC1155( - token_address, token_id, amount, transaction_config - ) - - def withdraw_erc20( - self, token_address: ChecksumAddress, amount: int, transaction_config - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.withdrawERC20(token_address, amount, transaction_config) - - def withdraw_erc721( - self, token_address: ChecksumAddress, token_id: int, transaction_config - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.withdrawERC721(token_address, token_id, transaction_config) - - -def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]: - signer = network.accounts.load(args.sender, args.password) - transaction_config: Dict[str, Any] = {"from": signer} - if args.gas_price is not None: - transaction_config["gas_price"] = args.gas_price - if args.max_fee_per_gas is not None: - transaction_config["max_fee"] = args.max_fee_per_gas - if args.max_priority_fee_per_gas is not None: - transaction_config["priority_fee"] = args.max_priority_fee_per_gas - if args.confirmations is not None: - transaction_config["required_confs"] = args.confirmations - if args.nonce is not None: - transaction_config["nonce"] = args.nonce - return transaction_config - - -def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> None: - parser.add_argument( - "--network", required=True, help="Name of brownie network to connect to" - ) - parser.add_argument( - "--address", required=False, help="Address of deployed contract to connect to" - ) - if not transact: - return - parser.add_argument( - "--sender", required=True, help="Path to keystore file for transaction sender" - ) - parser.add_argument( - "--password", - required=False, - help="Password to keystore file (if you do not provide it, you will be prompted for it)", - ) - parser.add_argument( - "--gas-price", default=None, help="Gas price at which to submit transaction" - ) - parser.add_argument( - "--max-fee-per-gas", - default=None, - help="Max fee per gas for EIP1559 transactions", - ) - parser.add_argument( - "--max-priority-fee-per-gas", - default=None, - help="Max priority fee per gas for EIP1559 transactions", - ) - parser.add_argument( - "--confirmations", - type=int, - default=None, - help="Number of confirmations to await before considering a transaction completed", - ) - parser.add_argument( - "--nonce", type=int, default=None, help="Nonce for the transaction (optional)" - ) - parser.add_argument( - "--value", default=None, help="Value of the transaction in wei(optional)" - ) - - -def handle_deploy(args: argparse.Namespace) -> None: - network.connect(args.network) - transaction_config = get_transaction_config(args) - contract = Dropper(None) - result = contract.deploy(transaction_config=transaction_config) - print(result) - - -def handle_verify_contract(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.verify_contract() - print(result) - - -def handle_erc1155_type(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.erc1155_type() - print(result) - - -def handle_erc20_type(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.erc20_type() - print(result) - - -def handle_erc721_type(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.erc721_type() - print(result) - - -def handle_terminus_mintable_type(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.terminus_mintable_type() - print(result) - - -def handle_claim(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.claim( - claim_id=args.claim_id, - block_deadline=args.block_deadline, - amount=args.amount, - signature=args.signature, - transaction_config=transaction_config, - ) - print(result) - - -def handle_claim_message_hash(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.claim_message_hash( - claim_id=args.claim_id, - claimant=args.claimant, - block_deadline=args.block_deadline, - amount=args.amount, - ) - print(result) - - -def handle_claim_status(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.claim_status(claim_id=args.claim_id) - print(result) - - -def handle_claim_uri(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.claim_uri(claim_id=args.claim_id) - print(result) - - -def handle_create_claim(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.create_claim( - token_type=args.token_type, - token_address=args.token_address, - token_id=args.token_id, - amount=args.amount, - transaction_config=transaction_config, - ) - print(result) - - -def handle_get_claim(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.get_claim(claim_id=args.claim_id) - print(result) - - -def handle_get_claim_status(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.get_claim_status(claim_id=args.claim_id, claimant=args.claimant) - print(result) - - -def handle_get_signer_for_claim(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.get_signer_for_claim(claim_id=args.claim_id) - print(result) - - -def handle_num_claims(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.num_claims() - print(result) - - -def handle_on_erc1155_batch_received(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.on_erc1155_batch_received( - arg1=args.arg1, - arg2=args.arg2, - arg3=args.arg3, - arg4=args.arg4, - arg5=args.arg5, - transaction_config=transaction_config, - ) - print(result) - - -def handle_on_erc1155_received(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.on_erc1155_received( - arg1=args.arg1, - arg2=args.arg2, - arg3=args.arg3, - arg4=args.arg4, - arg5=args.arg5, - transaction_config=transaction_config, - ) - print(result) - - -def handle_on_erc721_received(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.on_erc721_received( - operator=args.operator, - from_=args.from_arg, - token_id=args.token_id, - data=args.data, - transaction_config=transaction_config, - ) - print(result) - - -def handle_owner(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.owner() - print(result) - - -def handle_paused(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.paused() - print(result) - - -def handle_renounce_ownership(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.renounce_ownership(transaction_config=transaction_config) - print(result) - - -def handle_set_claim_status(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.set_claim_status( - claim_id=args.claim_id, - status=args.status, - transaction_config=transaction_config, - ) - print(result) - - -def handle_set_claim_uri(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.set_claim_uri( - claim_id=args.claim_id, uri=args.uri, transaction_config=transaction_config - ) - print(result) - - -def handle_set_signer_for_claim(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.set_signer_for_claim( - claim_id=args.claim_id, - signer=args.signer_arg, - transaction_config=transaction_config, - ) - print(result) - - -def handle_supports_interface(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - result = contract.supports_interface(interface_id=args.interface_id) - print(result) - - -def handle_surrender_pool_control(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.surrender_pool_control( - pool_id=args.pool_id, - terminus_address=args.terminus_address, - new_pool_controller=args.new_pool_controller, - transaction_config=transaction_config, - ) - print(result) - - -def handle_transfer_ownership(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.transfer_ownership( - new_owner=args.new_owner, transaction_config=transaction_config - ) - print(result) - - -def handle_withdraw_erc1155(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.withdraw_erc1155( - token_address=args.token_address, - token_id=args.token_id, - amount=args.amount, - transaction_config=transaction_config, - ) - print(result) - - -def handle_withdraw_erc20(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.withdraw_erc20( - token_address=args.token_address, - amount=args.amount, - transaction_config=transaction_config, - ) - print(result) - - -def handle_withdraw_erc721(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = Dropper(args.address) - transaction_config = get_transaction_config(args) - result = contract.withdraw_erc721( - token_address=args.token_address, - token_id=args.token_id, - transaction_config=transaction_config, - ) - print(result) - - -def generate_cli() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description="CLI for Dropper") - parser.set_defaults(func=lambda _: parser.print_help()) - subcommands = parser.add_subparsers() - - deploy_parser = subcommands.add_parser("deploy") - add_default_arguments(deploy_parser, True) - deploy_parser.set_defaults(func=handle_deploy) - - verify_contract_parser = subcommands.add_parser("verify-contract") - add_default_arguments(verify_contract_parser, False) - verify_contract_parser.set_defaults(func=handle_verify_contract) - - erc1155_type_parser = subcommands.add_parser("erc1155-type") - add_default_arguments(erc1155_type_parser, False) - erc1155_type_parser.set_defaults(func=handle_erc1155_type) - - erc20_type_parser = subcommands.add_parser("erc20-type") - add_default_arguments(erc20_type_parser, False) - erc20_type_parser.set_defaults(func=handle_erc20_type) - - erc721_type_parser = subcommands.add_parser("erc721-type") - add_default_arguments(erc721_type_parser, False) - erc721_type_parser.set_defaults(func=handle_erc721_type) - - terminus_mintable_type_parser = subcommands.add_parser("terminus-mintable-type") - add_default_arguments(terminus_mintable_type_parser, False) - terminus_mintable_type_parser.set_defaults(func=handle_terminus_mintable_type) - - claim_parser = subcommands.add_parser("claim") - add_default_arguments(claim_parser, True) - claim_parser.add_argument( - "--claim-id", required=True, help="Type: uint256", type=int - ) - claim_parser.add_argument( - "--block-deadline", required=True, help="Type: uint256", type=int - ) - claim_parser.add_argument("--amount", required=True, help="Type: uint256", type=int) - claim_parser.add_argument( - "--signature", required=True, help="Type: bytes", type=bytes_argument_type - ) - claim_parser.set_defaults(func=handle_claim) - - claim_message_hash_parser = subcommands.add_parser("claim-message-hash") - add_default_arguments(claim_message_hash_parser, False) - claim_message_hash_parser.add_argument( - "--claim-id", required=True, help="Type: uint256", type=int - ) - claim_message_hash_parser.add_argument( - "--claimant", required=True, help="Type: address" - ) - claim_message_hash_parser.add_argument( - "--block-deadline", required=True, help="Type: uint256", type=int - ) - claim_message_hash_parser.add_argument( - "--amount", required=True, help="Type: uint256", type=int - ) - claim_message_hash_parser.set_defaults(func=handle_claim_message_hash) - - claim_status_parser = subcommands.add_parser("claim-status") - add_default_arguments(claim_status_parser, False) - claim_status_parser.add_argument( - "--claim-id", required=True, help="Type: uint256", type=int - ) - claim_status_parser.set_defaults(func=handle_claim_status) - - claim_uri_parser = subcommands.add_parser("claim-uri") - add_default_arguments(claim_uri_parser, False) - claim_uri_parser.add_argument( - "--claim-id", required=True, help="Type: uint256", type=int - ) - claim_uri_parser.set_defaults(func=handle_claim_uri) - - create_claim_parser = subcommands.add_parser("create-claim") - add_default_arguments(create_claim_parser, True) - create_claim_parser.add_argument( - "--token-type", required=True, help="Type: uint256", type=int - ) - create_claim_parser.add_argument( - "--token-address", required=True, help="Type: address" - ) - create_claim_parser.add_argument( - "--token-id", required=True, help="Type: uint256", type=int - ) - create_claim_parser.add_argument( - "--amount", required=True, help="Type: uint256", type=int - ) - create_claim_parser.set_defaults(func=handle_create_claim) - - get_claim_parser = subcommands.add_parser("get-claim") - add_default_arguments(get_claim_parser, False) - get_claim_parser.add_argument( - "--claim-id", required=True, help="Type: uint256", type=int - ) - get_claim_parser.set_defaults(func=handle_get_claim) - - get_claim_status_parser = subcommands.add_parser("get-claim-status") - add_default_arguments(get_claim_status_parser, False) - get_claim_status_parser.add_argument( - "--claim-id", required=True, help="Type: uint256", type=int - ) - get_claim_status_parser.add_argument( - "--claimant", required=True, help="Type: address" - ) - get_claim_status_parser.set_defaults(func=handle_get_claim_status) - - get_signer_for_claim_parser = subcommands.add_parser("get-signer-for-claim") - add_default_arguments(get_signer_for_claim_parser, False) - get_signer_for_claim_parser.add_argument( - "--claim-id", required=True, help="Type: uint256", type=int - ) - get_signer_for_claim_parser.set_defaults(func=handle_get_signer_for_claim) - - num_claims_parser = subcommands.add_parser("num-claims") - add_default_arguments(num_claims_parser, False) - num_claims_parser.set_defaults(func=handle_num_claims) - - on_erc1155_batch_received_parser = subcommands.add_parser( - "on-erc1155-batch-received" - ) - add_default_arguments(on_erc1155_batch_received_parser, True) - on_erc1155_batch_received_parser.add_argument( - "--arg1", required=True, help="Type: address" - ) - on_erc1155_batch_received_parser.add_argument( - "--arg2", required=True, help="Type: address" - ) - on_erc1155_batch_received_parser.add_argument( - "--arg3", required=True, help="Type: uint256[]", nargs="+" - ) - on_erc1155_batch_received_parser.add_argument( - "--arg4", required=True, help="Type: uint256[]", nargs="+" - ) - on_erc1155_batch_received_parser.add_argument( - "--arg5", required=True, help="Type: bytes", type=bytes_argument_type - ) - on_erc1155_batch_received_parser.set_defaults(func=handle_on_erc1155_batch_received) - - on_erc1155_received_parser = subcommands.add_parser("on-erc1155-received") - add_default_arguments(on_erc1155_received_parser, True) - on_erc1155_received_parser.add_argument( - "--arg1", required=True, help="Type: address" - ) - on_erc1155_received_parser.add_argument( - "--arg2", required=True, help="Type: address" - ) - on_erc1155_received_parser.add_argument( - "--arg3", required=True, help="Type: uint256", type=int - ) - on_erc1155_received_parser.add_argument( - "--arg4", required=True, help="Type: uint256", type=int - ) - on_erc1155_received_parser.add_argument( - "--arg5", required=True, help="Type: bytes", type=bytes_argument_type - ) - on_erc1155_received_parser.set_defaults(func=handle_on_erc1155_received) - - on_erc721_received_parser = subcommands.add_parser("on-erc721-received") - add_default_arguments(on_erc721_received_parser, True) - on_erc721_received_parser.add_argument( - "--operator", required=True, help="Type: address" - ) - on_erc721_received_parser.add_argument( - "--from-arg", required=True, help="Type: address" - ) - on_erc721_received_parser.add_argument( - "--token-id", required=True, help="Type: uint256", type=int - ) - on_erc721_received_parser.add_argument( - "--data", required=True, help="Type: bytes", type=bytes_argument_type - ) - on_erc721_received_parser.set_defaults(func=handle_on_erc721_received) - - owner_parser = subcommands.add_parser("owner") - add_default_arguments(owner_parser, False) - owner_parser.set_defaults(func=handle_owner) - - paused_parser = subcommands.add_parser("paused") - add_default_arguments(paused_parser, False) - paused_parser.set_defaults(func=handle_paused) - - renounce_ownership_parser = subcommands.add_parser("renounce-ownership") - add_default_arguments(renounce_ownership_parser, True) - renounce_ownership_parser.set_defaults(func=handle_renounce_ownership) - - set_claim_status_parser = subcommands.add_parser("set-claim-status") - add_default_arguments(set_claim_status_parser, True) - set_claim_status_parser.add_argument( - "--claim-id", required=True, help="Type: uint256", type=int - ) - set_claim_status_parser.add_argument( - "--status", required=True, help="Type: bool", type=boolean_argument_type - ) - set_claim_status_parser.set_defaults(func=handle_set_claim_status) - - set_claim_uri_parser = subcommands.add_parser("set-claim-uri") - add_default_arguments(set_claim_uri_parser, True) - set_claim_uri_parser.add_argument( - "--claim-id", required=True, help="Type: uint256", type=int - ) - set_claim_uri_parser.add_argument( - "--uri", required=True, help="Type: string", type=str - ) - set_claim_uri_parser.set_defaults(func=handle_set_claim_uri) - - set_signer_for_claim_parser = subcommands.add_parser("set-signer-for-claim") - add_default_arguments(set_signer_for_claim_parser, True) - set_signer_for_claim_parser.add_argument( - "--claim-id", required=True, help="Type: uint256", type=int - ) - set_signer_for_claim_parser.add_argument( - "--signer-arg", required=True, help="Type: address" - ) - set_signer_for_claim_parser.set_defaults(func=handle_set_signer_for_claim) - - supports_interface_parser = subcommands.add_parser("supports-interface") - add_default_arguments(supports_interface_parser, False) - supports_interface_parser.add_argument( - "--interface-id", required=True, help="Type: bytes4", type=bytes_argument_type - ) - supports_interface_parser.set_defaults(func=handle_supports_interface) - - surrender_pool_control_parser = subcommands.add_parser("surrender-pool-control") - add_default_arguments(surrender_pool_control_parser, True) - surrender_pool_control_parser.add_argument( - "--pool-id", required=True, help="Type: uint256", type=int - ) - surrender_pool_control_parser.add_argument( - "--terminus-address", required=True, help="Type: address" - ) - surrender_pool_control_parser.add_argument( - "--new-pool-controller", required=True, help="Type: address" - ) - surrender_pool_control_parser.set_defaults(func=handle_surrender_pool_control) - - transfer_ownership_parser = subcommands.add_parser("transfer-ownership") - add_default_arguments(transfer_ownership_parser, True) - transfer_ownership_parser.add_argument( - "--new-owner", required=True, help="Type: address" - ) - transfer_ownership_parser.set_defaults(func=handle_transfer_ownership) - - withdraw_erc1155_parser = subcommands.add_parser("withdraw-erc1155") - add_default_arguments(withdraw_erc1155_parser, True) - withdraw_erc1155_parser.add_argument( - "--token-address", required=True, help="Type: address" - ) - withdraw_erc1155_parser.add_argument( - "--token-id", required=True, help="Type: uint256", type=int - ) - withdraw_erc1155_parser.add_argument( - "--amount", required=True, help="Type: uint256", type=int - ) - withdraw_erc1155_parser.set_defaults(func=handle_withdraw_erc1155) - - withdraw_erc20_parser = subcommands.add_parser("withdraw-erc20") - add_default_arguments(withdraw_erc20_parser, True) - withdraw_erc20_parser.add_argument( - "--token-address", required=True, help="Type: address" - ) - withdraw_erc20_parser.add_argument( - "--amount", required=True, help="Type: uint256", type=int - ) - withdraw_erc20_parser.set_defaults(func=handle_withdraw_erc20) - - withdraw_erc721_parser = subcommands.add_parser("withdraw-erc721") - add_default_arguments(withdraw_erc721_parser, True) - withdraw_erc721_parser.add_argument( - "--token-address", required=True, help="Type: address" - ) - withdraw_erc721_parser.add_argument( - "--token-id", required=True, help="Type: uint256", type=int - ) - withdraw_erc721_parser.set_defaults(func=handle_withdraw_erc721) - - return parser - - -def main() -> None: - parser = generate_cli() - args = parser.parse_args() - args.func(args) - - -if __name__ == "__main__": - main() diff --git a/cli/enginecli/DropperV2Facet.py b/cli/enginecli/DropperFacet.py similarity index 91% rename from cli/enginecli/DropperV2Facet.py rename to cli/enginecli/DropperFacet.py index 35a05225..9e79751d 100644 --- a/cli/enginecli/DropperV2Facet.py +++ b/cli/enginecli/DropperFacet.py @@ -69,12 +69,12 @@ def contract_from_build(abi_name: str) -> ContractContainer: return ContractContainer(PROJECT, build) -class DropperV2Facet: +class DropperFacet: def __init__(self, contract_address: Optional[ChecksumAddress]): - self.contract_name = "DropperV2Facet" + self.contract_name = "DropperFacet" self.address = contract_address self.contract = None - self.abi = get_abi_json("DropperV2Facet") + self.abi = get_abi_json("DropperFacet") if self.address is not None: self.contract: Optional[Contract] = Contract.from_abi( self.contract_name, self.address, self.abi @@ -160,6 +160,12 @@ def drop_uri( self.assert_contract_is_instantiated() return self.contract.dropUri.call(drop_id, block_identifier=block_number) + def dropper_version( + self, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.dropperVersion.call(block_identifier=block_number) + def get_amount_claimed( self, claimant: ChecksumAddress, @@ -185,6 +191,17 @@ def get_signer_for_drop( drop_id, block_identifier=block_number ) + def init( + self, + terminus_admin_contract_address: ChecksumAddress, + terminus_admin_pool_id: int, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.init( + terminus_admin_contract_address, terminus_admin_pool_id, transaction_config + ) + def max_claimable( self, drop_id: int, block_number: Optional[Union[str, int]] = "latest" ) -> Any: @@ -365,7 +382,7 @@ def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> No def handle_deploy(args: argparse.Namespace) -> None: network.connect(args.network) transaction_config = get_transaction_config(args) - contract = DropperV2Facet(None) + contract = DropperFacet(None) result = contract.deploy(transaction_config=transaction_config) print(result) if args.verbose: @@ -374,14 +391,14 @@ def handle_deploy(args: argparse.Namespace) -> None: def handle_verify_contract(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) result = contract.verify_contract() print(result) def handle_claim(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) transaction_config = get_transaction_config(args) result = contract.claim( request_id=args.request_id, @@ -398,7 +415,7 @@ def handle_claim(args: argparse.Namespace) -> None: def handle_claim_message_hash(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) result = contract.claim_message_hash( request_id=args.request_id, drop_id=args.drop_id, @@ -412,7 +429,7 @@ def handle_claim_message_hash(args: argparse.Namespace) -> None: def handle_create_drop(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) transaction_config = get_transaction_config(args) result = contract.create_drop( token_type=args.token_type, @@ -429,21 +446,28 @@ def handle_create_drop(args: argparse.Namespace) -> None: def handle_drop_status(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) result = contract.drop_status(drop_id=args.drop_id, block_number=args.block_number) print(result) def handle_drop_uri(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) result = contract.drop_uri(drop_id=args.drop_id, block_number=args.block_number) print(result) +def handle_dropper_version(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DropperFacet(args.address) + result = contract.dropper_version(block_number=args.block_number) + print(result) + + def handle_get_amount_claimed(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) result = contract.get_amount_claimed( claimant=args.claimant, drop_id=args.drop_id, block_number=args.block_number ) @@ -452,23 +476,37 @@ def handle_get_amount_claimed(args: argparse.Namespace) -> None: def handle_get_drop(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) result = contract.get_drop(drop_id=args.drop_id, block_number=args.block_number) print(result) def handle_get_signer_for_drop(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) result = contract.get_signer_for_drop( drop_id=args.drop_id, block_number=args.block_number ) print(result) +def handle_init(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DropperFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.init( + terminus_admin_contract_address=args.terminus_admin_contract_address, + terminus_admin_pool_id=args.terminus_admin_pool_id, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + def handle_max_claimable(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) result = contract.max_claimable( drop_id=args.drop_id, block_number=args.block_number ) @@ -477,14 +515,14 @@ def handle_max_claimable(args: argparse.Namespace) -> None: def handle_num_drops(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) result = contract.num_drops(block_number=args.block_number) print(result) def handle_on_erc1155_batch_received(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) transaction_config = get_transaction_config(args) result = contract.on_erc1155_batch_received( arg1=args.arg1, @@ -501,7 +539,7 @@ def handle_on_erc1155_batch_received(args: argparse.Namespace) -> None: def handle_on_erc1155_received(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) transaction_config = get_transaction_config(args) result = contract.on_erc1155_received( arg1=args.arg1, @@ -518,7 +556,7 @@ def handle_on_erc1155_received(args: argparse.Namespace) -> None: def handle_on_erc721_received(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) transaction_config = get_transaction_config(args) result = contract.on_erc721_received( operator=args.operator, @@ -534,7 +572,7 @@ def handle_on_erc721_received(args: argparse.Namespace) -> None: def handle_set_drop_status(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) transaction_config = get_transaction_config(args) result = contract.set_drop_status( drop_id=args.drop_id, status=args.status, transaction_config=transaction_config @@ -546,7 +584,7 @@ def handle_set_drop_status(args: argparse.Namespace) -> None: def handle_set_drop_uri(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) transaction_config = get_transaction_config(args) result = contract.set_drop_uri( drop_id=args.drop_id, uri=args.uri, transaction_config=transaction_config @@ -558,7 +596,7 @@ def handle_set_drop_uri(args: argparse.Namespace) -> None: def handle_set_signer_for_drop(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) transaction_config = get_transaction_config(args) result = contract.set_signer_for_drop( drop_id=args.drop_id, @@ -572,7 +610,7 @@ def handle_set_signer_for_drop(args: argparse.Namespace) -> None: def handle_supports_interface(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) result = contract.supports_interface( interface_id=args.interface_id, block_number=args.block_number ) @@ -581,7 +619,7 @@ def handle_supports_interface(args: argparse.Namespace) -> None: def handle_surrender_pool_control(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) transaction_config = get_transaction_config(args) result = contract.surrender_pool_control( pool_id=args.pool_id, @@ -596,7 +634,7 @@ def handle_surrender_pool_control(args: argparse.Namespace) -> None: def handle_withdraw_erc1155(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) transaction_config = get_transaction_config(args) result = contract.withdraw_erc1155( token_address=args.token_address, @@ -611,7 +649,7 @@ def handle_withdraw_erc1155(args: argparse.Namespace) -> None: def handle_withdraw_erc20(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) transaction_config = get_transaction_config(args) result = contract.withdraw_erc20( token_address=args.token_address, @@ -625,7 +663,7 @@ def handle_withdraw_erc20(args: argparse.Namespace) -> None: def handle_withdraw_erc721(args: argparse.Namespace) -> None: network.connect(args.network) - contract = DropperV2Facet(args.address) + contract = DropperFacet(args.address) transaction_config = get_transaction_config(args) result = contract.withdraw_erc721( token_address=args.token_address, @@ -638,7 +676,7 @@ def handle_withdraw_erc721(args: argparse.Namespace) -> None: def generate_cli() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description="CLI for DropperV2Facet") + parser = argparse.ArgumentParser(description="CLI for DropperFacet") parser.set_defaults(func=lambda _: parser.print_help()) subcommands = parser.add_subparsers() @@ -719,6 +757,10 @@ def generate_cli() -> argparse.ArgumentParser: ) drop_uri_parser.set_defaults(func=handle_drop_uri) + dropper_version_parser = subcommands.add_parser("dropper-version") + add_default_arguments(dropper_version_parser, False) + dropper_version_parser.set_defaults(func=handle_dropper_version) + get_amount_claimed_parser = subcommands.add_parser("get-amount-claimed") add_default_arguments(get_amount_claimed_parser, False) get_amount_claimed_parser.add_argument( @@ -743,6 +785,16 @@ def generate_cli() -> argparse.ArgumentParser: ) get_signer_for_drop_parser.set_defaults(func=handle_get_signer_for_drop) + init_parser = subcommands.add_parser("init") + add_default_arguments(init_parser, True) + init_parser.add_argument( + "--terminus-admin-contract-address", required=True, help="Type: address" + ) + init_parser.add_argument( + "--terminus-admin-pool-id", required=True, help="Type: uint256", type=int + ) + init_parser.set_defaults(func=handle_init) + max_claimable_parser = subcommands.add_parser("max-claimable") add_default_arguments(max_claimable_parser, False) max_claimable_parser.add_argument( diff --git a/cli/enginecli/cli.py b/cli/enginecli/cli.py index ccf9d25d..390754b1 100644 --- a/cli/enginecli/cli.py +++ b/cli/enginecli/cli.py @@ -1,7 +1,7 @@ import argparse import logging -from . import core, drop, Dropper, Lootbox, MockErc20, MockTerminus +from . import core, drop, DropperFacet, Lootbox, MockErc20, MockTerminus logging.basicConfig(level=logging.INFO) @@ -16,14 +16,14 @@ def main() -> None: parser.set_defaults(func=lambda _: parser.print_help()) subparsers = parser.add_subparsers() - lootbox_parser = Lootbox.generate_cli() - subparsers.add_parser("lootbox", parents=[lootbox_parser], add_help=False) + core_parser = core.generate_cli() + subparsers.add_parser("core", parents=[core_parser], add_help=False) - dropper_parser = Dropper.generate_cli() + dropper_parser = DropperFacet.generate_cli() subparsers.add_parser("dropper", parents=[dropper_parser], add_help=False) - core_parser = core.generate_cli() - subparsers.add_parser("core", parents=[core_parser], add_help=False) + lootbox_parser = Lootbox.generate_cli() + subparsers.add_parser("lootbox", parents=[lootbox_parser], add_help=False) erc20_parser = MockErc20.generate_cli() subparsers.add_parser("mock-erc20", parents=[erc20_parser], add_help=False) diff --git a/cli/enginecli/core.py b/cli/enginecli/core.py index c3a205b1..ccfa94ec 100644 --- a/cli/enginecli/core.py +++ b/cli/enginecli/core.py @@ -1,5 +1,5 @@ import argparse -from ast import arg +from enum import Enum import json import os import sys @@ -17,6 +17,7 @@ Diamond, DiamondCutFacet, DiamondLoupeFacet, + DropperFacet, OwnershipFacet, ReentrancyExploitable, ) @@ -24,10 +25,23 @@ FACETS: Dict[str, Any] = { "DiamondCutFacet": DiamondCutFacet, "DiamondLoupeFacet": DiamondLoupeFacet, + "DropperFacet": DropperFacet, "OwnershipFacet": OwnershipFacet, "ReentrancyExploitable": ReentrancyExploitable, } +FACET_INIT_CALLDATA: Dict[str, str] = { + "DropperFacet": lambda address, *args: DropperFacet.DropperFacet( + address + ).contract.init.encode_input(*args) +} + +DIAMOND_FACET_PRECEDENCE: List[str] = [ + "DiamondCutFacet", + "OwnershipFacet", + "DiamondLoupeFacet", +] + FACET_PRECEDENCE: List[str] = [ "DiamondCutFacet", "OwnershipFacet", @@ -35,6 +49,26 @@ "ReentrancyExploitable", ] + +class EngineFeatures(Enum): + DROPPER = "dropper" + + +def feature_from_facet_name(facet_name: str) -> Optional[EngineFeatures]: + try: + return EngineFeatures(facet_name) + except ValueError: + return None + + +FEATURE_FACETS: Dict[EngineFeatures, List[str]] = { + EngineFeatures.DROPPER: ["DropperFacet"] +} + +FEATURE_IGNORES: Dict[EngineFeatures, List[str]] = { + EngineFeatures.DROPPER: {"methods": ["init"], "selectors": []} +} + FACET_ACTIONS: Dict[str, int] = {"add": 0, "replace": 1, "remove": 2} ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" @@ -51,6 +85,8 @@ def facet_cut( ignore_selectors: Optional[List[str]] = None, methods: Optional[List[str]] = None, selectors: Optional[List[str]] = None, + feature: Optional[EngineFeatures] = None, + initializer_args: Optional[List[Any]] = None, ) -> Any: """ Cuts the given facet onto the given Diamond contract. @@ -65,6 +101,10 @@ def facet_cut( action in FACET_ACTIONS ), f"Invalid cut action: {action}. Choices: {','.join(FACET_ACTIONS)}." + facet_precedence = FACET_PRECEDENCE + if feature is not None: + facet_precedence = DIAMOND_FACET_PRECEDENCE + FEATURE_FACETS[feature] + if ignore_methods is None: ignore_methods = [] if ignore_selectors is None: @@ -74,15 +114,31 @@ def facet_cut( if selectors is None: selectors = [] - project_dir = os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) + project_dir = os.path.abspath( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + ) abis = abi.project_abis(project_dir) reserved_selectors: Set[str] = set() - for facet in FACET_PRECEDENCE: + for facet in facet_precedence: + facet_abi = abis.get(facet, []) if facet == facet_name: + # Add feature ignores to reserved_selectors then break out of facet iteration + facet_feature = feature_from_facet_name(facet_name) + if facet_feature is not None: + feature_ignores = FEATURE_IGNORES[facet_feature] + for item in facet_abi: + if ( + item["type"] == "function" + and item["name"] in feature_ignores["methods"] + ): + reserved_selectors.add(abi.encode_function_signature(item)) + + for selector in feature_ignores["selectors"]: + reserved_selectors.add(selector) + break - facet_abi = abis.get(facet, []) for item in facet_abi: if item["type"] == "function": reserved_selectors.add(abi.encode_function_signature(item)) @@ -122,6 +178,12 @@ def facet_cut( diamond = DiamondCutFacet.DiamondCutFacet(diamond_address) calldata = b"" + if FACET_INIT_CALLDATA.get(facet_name) is not None: + if initializer_args is None: + initializer_args = [] + calldata = FACET_INIT_CALLDATA[facet_name]( + initializer_address, *initializer_args + ) transaction = diamond.diamond_cut( [diamond_cut_action], initializer_address, calldata, transaction_config ) @@ -136,7 +198,7 @@ def diamond_gogogo( Returns addresses of all the deployed contracts with the contract names as keys. """ - result: Dict[str, Any] = {} + result: Dict[str, Any] = {"contracts": {}, "attached": []} try: diamond_cut_facet = DiamondCutFacet.DiamondCutFacet(None) @@ -145,7 +207,7 @@ def diamond_gogogo( print(e) result["error"] = "Failed to deploy DiamondCutFacet" return result - result["DiamondCutFacet"] = diamond_cut_facet.address + result["contracts"]["DiamondCutFacet"] = diamond_cut_facet.address try: diamond = Diamond.Diamond(None) @@ -154,7 +216,7 @@ def diamond_gogogo( print(e) result["error"] = "Failed to deploy Diamond" return result - result["Diamond"] = diamond.address + result["contracts"]["Diamond"] = diamond.address try: diamond_loupe_facet = DiamondLoupeFacet.DiamondLoupeFacet(None) @@ -163,7 +225,7 @@ def diamond_gogogo( print(e) result["error"] = "Failed to deploy DiamondLoupeFacet" return result - result["DiamondLoupeFacet"] = diamond_loupe_facet.address + result["contracts"]["DiamondLoupeFacet"] = diamond_loupe_facet.address try: ownership_facet = OwnershipFacet.OwnershipFacet(None) @@ -172,9 +234,7 @@ def diamond_gogogo( print(e) result["error"] = "Failed to deploy OwnershipFacet" return result - result["OwnershipFacet"] = ownership_facet.address - - result["attached"] = [] + result["contracts"]["OwnershipFacet"] = ownership_facet.address try: facet_cut( @@ -370,7 +430,7 @@ def create_lootboxes_from_config( return results -def gogogo( +def lootbox_gogogo( terminus_address, vrf_coordinator_address, link_token_address, @@ -426,11 +486,62 @@ def gogogo( return contracts -def handle_gogogo(args: argparse.Namespace) -> None: +def dropper_gogogo( + admin_terminus_address: str, + admin_terminus_pool_id: int, + transaction_config: Dict[str, Any], +) -> Dict[str, Any]: + deployment_info = diamond_gogogo( + owner_address=transaction_config["from"].address, + transaction_config=transaction_config, + ) + + dropper_facet = DropperFacet.DropperFacet(None) + dropper_facet.deploy(transaction_config=transaction_config) + deployment_info["contracts"]["DropperFacet"] = dropper_facet.address + + diamond_address = deployment_info["contracts"]["Diamond"] + facet_cut( + diamond_address, + "DropperFacet", + dropper_facet.address, + "add", + transaction_config, + initializer_address=dropper_facet.address, + feature=EngineFeatures.DROPPER, + initializer_args=[admin_terminus_address, admin_terminus_pool_id], + ) + deployment_info["attached"].append("DropperFacet") + + return deployment_info + + +def handle_facet_cut(args: argparse.Namespace) -> None: + network.connect(args.network) + diamond_address = args.address + action = args.action + facet_name = args.facet_name + facet_address = args.facet_address + transaction_config = Diamond.get_transaction_config(args) + facet_cut( + diamond_address, + facet_name, + facet_address, + action, + transaction_config, + initializer_address=args.initializer_address, + ignore_methods=args.ignore_methods, + ignore_selectors=args.ignore_selectors, + methods=args.methods, + selectors=args.selectors, + ) + + +def handle_lootbox_gogogo(args: argparse.Namespace) -> None: network.connect(args.network) terminus_address = args.terminus_address transaction_config = MockTerminus.get_transaction_config(args) - result = gogogo( + result = lootbox_gogogo( terminus_address, args.vrf_coordinator_address, args.link_token_address, @@ -445,6 +556,19 @@ def handle_gogogo(args: argparse.Namespace) -> None: json.dump(result, sys.stdout, indent=4) +def handle_dropper_gogogo(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = MockTerminus.get_transaction_config(args) + result = dropper_gogogo( + args.terminus_address, args.terminus_pool_id, transaction_config + ) + if args.outfile is not None: + with args.outfile: + json.dump(result, args.outfile) + + json.dump(result, sys.stdout, indent=4) + + def handle_create_lootboxes_from_config(args: argparse.Namespace) -> None: network.connect(args.network) transaction_config = MockTerminus.get_transaction_config(args) @@ -466,13 +590,90 @@ def generate_cli(): parser.set_defaults(func=lambda _: parser.print_help()) subcommands = parser.add_subparsers() - gogogo_parser = subcommands.add_parser( - "gogogo", + facet_cut_parser = subcommands.add_parser( + "facet-cut", + help="Operate on facets of a Diamond contract", + description="Operate on facets of a Diamond contract", + ) + Diamond.add_default_arguments(facet_cut_parser, transact=True) + facet_cut_parser.add_argument( + "--facet-name", + required=True, + choices=FACETS, + help="Name of facet to cut into or out of diamond", + ) + facet_cut_parser.add_argument( + "--facet-address", + required=False, + default=ZERO_ADDRESS, + help=f"Address of deployed facet (default: {ZERO_ADDRESS})", + ) + facet_cut_parser.add_argument( + "--action", + required=True, + choices=FACET_ACTIONS, + help="Diamond cut action to take on entire facet", + ) + facet_cut_parser.add_argument( + "--initializer-address", + default=ZERO_ADDRESS, + help=f"Address of contract to run as initializer after cut (default: {ZERO_ADDRESS})", + ) + facet_cut_parser.add_argument( + "--ignore-methods", + nargs="+", + help="Names of methods to ignore when cutting a facet onto or off of the diamond", + ) + facet_cut_parser.add_argument( + "--ignore-selectors", + nargs="+", + help="Method selectors to ignore when cutting a facet onto or off of the diamond", + ) + facet_cut_parser.add_argument( + "--methods", + nargs="+", + help="Names of methods to add (if set, --ignore-methods and --ignore-selectors are not used)", + ) + facet_cut_parser.add_argument( + "--selectors", + nargs="+", + help="Selectors to add (if set, --ignore-methods and --ignore-selectors are not used)", + ) + facet_cut_parser.set_defaults(func=handle_facet_cut) + + dropper_gogogo_parser = subcommands.add_parser( + "dropper-gogogo", + help="Deploy Dropper diamond contract", + description="Deploy Dropper diamond contract", + ) + Diamond.add_default_arguments(dropper_gogogo_parser, transact=True) + dropper_gogogo_parser.add_argument( + "--terminus-address", + required=True, + help="Address of Terminus contract defining access control for this Dropper contract", + ) + dropper_gogogo_parser.add_argument( + "--terminus-pool-id", + required=True, + type=int, + help="Pool ID of Terminus pool for administrators of this dropper contract", + ) + dropper_gogogo_parser.add_argument( + "-o", + "--outfile", + type=argparse.FileType("w"), + default=None, + help="(Optional) file to write deployed addresses to", + ) + dropper_gogogo_parser.set_defaults(func=handle_dropper_gogogo) + + lootbox_gogogo_parser = subcommands.add_parser( + "lootbox-gogogo", help="Deploys Lootbox contract", description="Deploys Lootbox contract", ) - gogogo_parser.add_argument( + lootbox_gogogo_parser.add_argument( "-o", "--outfile", type=argparse.FileType("w"), @@ -480,44 +681,44 @@ def generate_cli(): help="(Optional) file to write deployed addresses to", ) - gogogo_parser.add_argument( + lootbox_gogogo_parser.add_argument( "--terminus-address", type=str, required=True, help="Address of the terminus contract", ) - gogogo_parser.add_argument( + lootbox_gogogo_parser.add_argument( "--vrf-coordinator-address", type=str, required=True, help="Address of the vrf coordinator contract", ) - gogogo_parser.add_argument( + lootbox_gogogo_parser.add_argument( "--link-token-address", type=str, required=True, help="Address of the link token contract", ) - gogogo_parser.add_argument( + lootbox_gogogo_parser.add_argument( "--chainlik-vrf-fee", type=int, required=True, help="Chainlink vrf fee", ) - gogogo_parser.add_argument( + lootbox_gogogo_parser.add_argument( "--chainlik-vrf-keyhash", type=str, required=True, help="Chainlink vrf keyhash", ) - MockTerminus.add_default_arguments(gogogo_parser, transact=True) + MockTerminus.add_default_arguments(lootbox_gogogo_parser, transact=True) - gogogo_parser.set_defaults(func=handle_gogogo) + lootbox_gogogo_parser.set_defaults(func=handle_lootbox_gogogo) create_lootboxes_from_config_parser = subcommands.add_parser( "create-lootboxes-from-config", diff --git a/cli/enginecli/test_dropper.py b/cli/enginecli/test_dropper.py index 7825dbce..c4e24ea4 100644 --- a/cli/enginecli/test_dropper.py +++ b/cli/enginecli/test_dropper.py @@ -1,6 +1,4 @@ -from multiprocessing import pool import unittest -from typing import Dict, Any from brownie import accounts, network, web3 as web3_client from brownie.exceptions import VirtualMachineError @@ -10,7 +8,7 @@ from hexbytes import HexBytes from moonworm.watch import _fetch_events_chunk -from . import Dropper, MockTerminus, MockErc20, MockERC721 +from . import core, DropperFacet, MockTerminus, MockErc20, MockERC721 ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" @@ -56,6 +54,16 @@ def setUpClass(cls) -> None: cls.terminus.address, 100 * 10**18, {"from": accounts[0]} ) + cls.terminus.create_pool_v1(2**256 - 1, True, True, {"from": accounts[0]}) + cls.admin_terminus_pool_id = cls.terminus.total_pools() + cls.terminus.mint( + accounts[0].address, + cls.admin_terminus_pool_id, + 1, + b"", + {"from": accounts[0]}, + ) + cls.terminus.create_pool_v1(2**256 - 1, True, True, {"from": accounts[0]}) cls.terminus_pool_id = cls.terminus.total_pools() @@ -65,8 +73,12 @@ def setUpClass(cls) -> None: cls.mintable_terminus_pool_id = cls.terminus.total_pools() # Dropper deployment - cls.dropper = Dropper.Dropper(None) - cls.dropper.deploy({"from": accounts[0]}) + cls.deployment_info = core.dropper_gogogo( + cls.terminus.address, cls.admin_terminus_pool_id, {"from": accounts[0]} + ) + cls.dropper = DropperFacet.DropperFacet( + cls.deployment_info["contracts"]["Diamond"] + ) cls.TERMINUS_MINTABLE_TYPE = int(cls.dropper.terminus_mintable_type()) cls.terminus.set_pool_controller( diff --git a/cli/enginecli/test_lootbox.py b/cli/enginecli/test_lootbox.py index c8336a51..82e3d899 100644 --- a/cli/enginecli/test_lootbox.py +++ b/cli/enginecli/test_lootbox.py @@ -7,7 +7,7 @@ from chainlink import MockChainlinkCoordinator, MockLinkToken, mock_vrf_oracle from . import Lootbox, MockTerminus, MockErc20 -from .core import lootbox_item_to_tuple, gogogo +from .core import lootbox_item_to_tuple, lootbox_gogogo class LootboxTypes(Enum): @@ -56,7 +56,7 @@ def setUpClass(cls) -> None: ) cls.terminus.set_pool_base_price(1, {"from": accounts[0]}) - gogogo_result = gogogo( + gogogo_result = lootbox_gogogo( cls.terminus.address, cls.mock_chainlink_coordinator.address, cls.linkToken.address, diff --git a/cli/enginecli/test_random_lootbox.py b/cli/enginecli/test_random_lootbox.py index 31d5fce7..01be0436 100644 --- a/cli/enginecli/test_random_lootbox.py +++ b/cli/enginecli/test_random_lootbox.py @@ -7,7 +7,7 @@ from chainlink import MockChainlinkCoordinator, MockLinkToken, mock_vrf_oracle from . import Lootbox, MockTerminus, MockErc20 -from .core import lootbox_item_to_tuple, gogogo +from .core import lootbox_item_to_tuple, lootbox_gogogo from .test_lootbox import LootboxTestCase, LootboxTypes diff --git a/cli/enginecli/test_reentrancy_guard.py b/cli/enginecli/test_reentrancy_guard.py index a30bdb83..1c0a4963 100644 --- a/cli/enginecli/test_reentrancy_guard.py +++ b/cli/enginecli/test_reentrancy_guard.py @@ -20,7 +20,7 @@ def setUpClass(cls) -> None: accounts[0].address, {"from": accounts[0]} ) cls.reentrancy_exploitable = ReentrancyExploitable.ReentrancyExploitable( - reentrancy_exploitable_gogogo_result["Diamond"] + reentrancy_exploitable_gogogo_result["contracts"]["Diamond"] ) reentrancy_exploitable_facet = ReentrancyExploitable.ReentrancyExploitable(None) diff --git a/contracts/Dropper.sol b/contracts/Dropper.sol deleted file mode 100644 index 8edac9d0..00000000 --- a/contracts/Dropper.sol +++ /dev/null @@ -1,344 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/** - * Authors: Moonstream Engineering (engineering@moonstream.to) - * GitHub: https://github.com/bugout-dev/dao - */ - -pragma solidity ^0.8.9; - -import "@moonstream/contracts/terminus/TerminusFacet.sol"; -import "@openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin-contracts/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol"; -import "@openzeppelin-contracts/contracts/access/Ownable.sol"; -import "@openzeppelin-contracts/contracts/security/Pausable.sol"; -import "@openzeppelin-contracts/contracts/security/ReentrancyGuard.sol"; -import "@openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol"; -import "@openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol"; -import "@openzeppelin-contracts/contracts/utils/cryptography/draft-EIP712.sol"; -import "@openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol"; - -/** - * @title Moonstream Dropper - * @author Moonstream Engineering (engineering@moonstream.to) - * @notice This contract manages drops for ERC20, ERC1155, and ERC721 tokens. - */ -contract Dropper is - IERC721Receiver, - ERC1155Holder, - EIP712, - Ownable, - Pausable, - ReentrancyGuard -{ - // - [x] onERC721Received - // - [x] onERC1155Received (implemented by ERC1155Holder) - // - [x] withdrawERC20Tokens onlyOwner - // - [x] withdrawERC1155Tokens onlyOwner - // - [x] withdrawERC721Tokens onlyOwner - // - [x] claim (transfer with signature) nonReentrant - // - [x] claimMessageHash public view - // - [x] onERC1155BatchReceived nonReentrant (implemented by ERC1155Holder) - // - [x] claimStatus view method - // - [x] setSignerForClaim onlyOwner - // - [x] getSignerForClaim public view - // - [x] createClaim onlyOwner - // - [x] numClaims public view - // - [x] setClaimStatus onlyOwner - // - [x] getClaim external view - // - [x] getClaimStatus external view - // - [x] claimUri external view - // - [x] setClaimUri external onlyOwner - - uint256 public ERC20_TYPE = 20; - uint256 public ERC721_TYPE = 721; - uint256 public ERC1155_TYPE = 1155; - uint256 public TERMINUS_MINTABLE_TYPE = 1; - - struct ClaimableToken { - uint256 tokenType; - address tokenAddress; // address of the token - uint256 tokenId; - uint256 amount; - } - - uint256 private NumClaims; - mapping(uint256 => bool) IsClaimActive; - mapping(uint256 => address) ClaimSigner; - mapping(uint256 => ClaimableToken) ClaimToken; - mapping(uint256 => mapping(address => uint256)) ClaimCompleted; - mapping(uint256 => string) ClaimURI; - - event Claimed(uint256 indexed claimId, address indexed claimant); - event ClaimCreated( - uint256 claimId, - uint256 indexed tokenType, - address indexed tokenAddress, - uint256 indexed tokenId, - uint256 amount - ); - event ClaimStatusChanged(uint256 indexed claimId, bool status); - event ClaimSignerChanged(uint256 indexed claimId, address signer); - event Withdrawal( - address recipient, - uint256 indexed tokenType, - address indexed tokenAddress, - uint256 indexed tokenId, - uint256 amount - ); - - // Terminus Facet contract controller - - /** - * @dev Initializes the Dropper contract - */ - constructor() EIP712("Moonstream Dropper", "0.1.0") {} - - function onERC721Received( - address operator, - address from, - uint256 tokenId, - bytes calldata data - ) external returns (bytes4) { - return IERC721Receiver.onERC721Received.selector; - } - - function createClaim( - uint256 tokenType, - address tokenAddress, - uint256 tokenId, - uint256 amount - ) external onlyOwner returns (uint256) { - require( - tokenType == ERC20_TYPE || - tokenType == ERC721_TYPE || - tokenType == ERC1155_TYPE || - tokenType == TERMINUS_MINTABLE_TYPE, - "Dropper: createClaim -- Unknown token type" - ); - - require( - amount != 0, - "Dropper: createClaim -- Amount must be greater than 0" - ); - - NumClaims++; - - ClaimableToken memory tokenMetadata; - tokenMetadata.tokenType = tokenType; - tokenMetadata.tokenAddress = tokenAddress; - tokenMetadata.tokenId = tokenId; - tokenMetadata.amount = amount; - ClaimToken[NumClaims] = tokenMetadata; - emit ClaimCreated(NumClaims, tokenType, tokenAddress, tokenId, amount); - - IsClaimActive[NumClaims] = true; - emit ClaimStatusChanged(NumClaims, true); - - return NumClaims; - } - - function numClaims() external view returns (uint256) { - return NumClaims; - } - - function getClaim(uint256 claimId) - external - view - returns (ClaimableToken memory) - { - return ClaimToken[claimId]; - } - - function setClaimStatus(uint256 claimId, bool status) external onlyOwner { - IsClaimActive[claimId] = status; - emit ClaimStatusChanged(claimId, status); - } - - function claimStatus(uint256 claimId) external view returns (bool) { - return IsClaimActive[claimId]; - } - - function setSignerForClaim(uint256 claimId, address signer) - public - onlyOwner - { - ClaimSigner[claimId] = signer; - emit ClaimSignerChanged(claimId, signer); - } - - function getSignerForClaim(uint256 claimId) - external - view - returns (address) - { - return ClaimSigner[claimId]; - } - - function getClaimStatus(uint256 claimId, address claimant) - external - view - returns (uint256) - { - return ClaimCompleted[claimId][claimant]; - } - - function claimMessageHash( - uint256 claimId, - address claimant, - uint256 blockDeadline, - uint256 amount - ) public view returns (bytes32) { - bytes32 structHash = keccak256( - abi.encode( - keccak256( - "ClaimPayload(uint256 claimId,address claimant,uint256 blockDeadline,uint256 amount)" - ), - claimId, - claimant, - blockDeadline, - amount - ) - ); - bytes32 digest = _hashTypedDataV4(structHash); - return digest; - } - - function claim( - uint256 claimId, - uint256 blockDeadline, - uint256 amount, - bytes memory signature - ) external whenNotPaused nonReentrant { - require( - block.number <= blockDeadline, - "Dropper: claim -- Block deadline exceeded." - ); - require( - ClaimCompleted[claimId][msg.sender] == 0, - "Dropper: claim -- That claim has already been completed." - ); - - bytes32 hash = claimMessageHash( - claimId, - msg.sender, - blockDeadline, - amount - ); - require( - SignatureChecker.isValidSignatureNow( - ClaimSigner[claimId], - hash, - signature - ), - "Dropper: claim -- Invalid signer for claim." - ); - - ClaimableToken memory claimToken = ClaimToken[claimId]; - - if (amount == 0) { - amount = claimToken.amount; - } - - if (claimToken.tokenType == ERC20_TYPE) { - IERC20 erc20Contract = IERC20(claimToken.tokenAddress); - erc20Contract.transfer(msg.sender, amount); - } else if (claimToken.tokenType == ERC721_TYPE) { - IERC721 erc721Contract = IERC721(claimToken.tokenAddress); - erc721Contract.safeTransferFrom( - address(this), - msg.sender, - claimToken.tokenId, - "" - ); - } else if (claimToken.tokenType == ERC1155_TYPE) { - IERC1155 erc1155Contract = IERC1155(claimToken.tokenAddress); - erc1155Contract.safeTransferFrom( - address(this), - msg.sender, - claimToken.tokenId, - amount, - "" - ); - } else if (claimToken.tokenType == TERMINUS_MINTABLE_TYPE) { - TerminusFacet terminusFacetContract = TerminusFacet( - claimToken.tokenAddress - ); - terminusFacetContract.mint( - msg.sender, - claimToken.tokenId, - amount, - "" - ); - } else { - revert("Dropper -- claim: Unknown token type in claim"); - } - - ClaimCompleted[claimId][msg.sender] = amount; - - emit Claimed(claimId, msg.sender); - } - - function withdrawERC20(address tokenAddress, uint256 amount) - public - onlyOwner - { - IERC20 erc20Contract = IERC20(tokenAddress); - erc20Contract.transfer(_msgSender(), amount); - emit Withdrawal(msg.sender, ERC20_TYPE, tokenAddress, 0, amount); - } - - function withdrawERC721(address tokenAddress, uint256 tokenId) - public - onlyOwner - { - address _owner = owner(); - IERC721 erc721Contract = IERC721(tokenAddress); - erc721Contract.safeTransferFrom(address(this), _owner, tokenId, ""); - emit Withdrawal(msg.sender, ERC721_TYPE, tokenAddress, tokenId, 1); - } - - function withdrawERC1155( - address tokenAddress, - uint256 tokenId, - uint256 amount - ) public onlyOwner { - address _owner = owner(); - IERC1155 erc1155Contract = IERC1155(tokenAddress); - erc1155Contract.safeTransferFrom( - address(this), - _owner, - tokenId, - amount, - "" - ); - emit Withdrawal( - msg.sender, - ERC1155_TYPE, - tokenAddress, - tokenId, - amount - ); - } - - function surrenderPoolControl( - uint256 poolId, - address terminusAddress, - address newPoolController - ) public onlyOwner { - TerminusFacet terminusFacetContract = TerminusFacet(terminusAddress); - terminusFacetContract.setPoolController(poolId, newPoolController); - } - - function claimUri(uint256 claimId) public view returns (string memory) { - return ClaimURI[claimId]; - } - - function setClaimUri(uint256 claimId, string memory uri) - external - onlyOwner - { - ClaimURI[claimId] = uri; - } -} diff --git a/contracts/DropperV2/DropperFacet.sol b/contracts/Dropper/DropperFacet.sol similarity index 100% rename from contracts/DropperV2/DropperFacet.sol rename to contracts/Dropper/DropperFacet.sol diff --git a/contracts/DropperV2/LibDropper.sol b/contracts/Dropper/LibDropper.sol similarity index 100% rename from contracts/DropperV2/LibDropper.sol rename to contracts/Dropper/LibDropper.sol From 2d282371b8736e24c45f2633194a0c28ce22c38f Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Fri, 12 Aug 2022 13:59:55 -0700 Subject: [PATCH 06/31] Added view methods for token types --- cli/enginecli/DropperFacet.py | 90 ++++++++++++++++++++++++++++++ contracts/Dropper/DropperFacet.sol | 20 +++++++ 2 files changed, 110 insertions(+) diff --git a/cli/enginecli/DropperFacet.py b/cli/enginecli/DropperFacet.py index 9e79751d..1749f1ba 100644 --- a/cli/enginecli/DropperFacet.py +++ b/cli/enginecli/DropperFacet.py @@ -166,6 +166,22 @@ def dropper_version( self.assert_contract_is_instantiated() return self.contract.dropperVersion.call(block_identifier=block_number) + def erc1155_type(self, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.erc1155_type(transaction_config) + + def erc20_type(self, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.erc20_type(transaction_config) + + def erc721_mintable_type(self, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.erc721_mintable_type(transaction_config) + + def erc721_type(self, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.erc721_type(transaction_config) + def get_amount_claimed( self, claimant: ChecksumAddress, @@ -287,6 +303,10 @@ def surrender_pool_control( pool_id, terminus_address, new_pool_controller, transaction_config ) + def terminus_mintable_type(self, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.terminus_mintable_type(transaction_config) + def withdraw_erc1155( self, token_address: ChecksumAddress, @@ -465,6 +485,46 @@ def handle_dropper_version(args: argparse.Namespace) -> None: print(result) +def handle_erc1155_type(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DropperFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.erc1155_type(transaction_config=transaction_config) + print(result) + if args.verbose: + print(result.info()) + + +def handle_erc20_type(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DropperFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.erc20_type(transaction_config=transaction_config) + print(result) + if args.verbose: + print(result.info()) + + +def handle_erc721_mintable_type(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DropperFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.erc721_mintable_type(transaction_config=transaction_config) + print(result) + if args.verbose: + print(result.info()) + + +def handle_erc721_type(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DropperFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.erc721_type(transaction_config=transaction_config) + print(result) + if args.verbose: + print(result.info()) + + def handle_get_amount_claimed(args: argparse.Namespace) -> None: network.connect(args.network) contract = DropperFacet(args.address) @@ -632,6 +692,16 @@ def handle_surrender_pool_control(args: argparse.Namespace) -> None: print(result.info()) +def handle_terminus_mintable_type(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DropperFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.terminus_mintable_type(transaction_config=transaction_config) + print(result) + if args.verbose: + print(result.info()) + + def handle_withdraw_erc1155(args: argparse.Namespace) -> None: network.connect(args.network) contract = DropperFacet(args.address) @@ -761,6 +831,22 @@ def generate_cli() -> argparse.ArgumentParser: add_default_arguments(dropper_version_parser, False) dropper_version_parser.set_defaults(func=handle_dropper_version) + erc1155_type_parser = subcommands.add_parser("erc1155-type") + add_default_arguments(erc1155_type_parser, True) + erc1155_type_parser.set_defaults(func=handle_erc1155_type) + + erc20_type_parser = subcommands.add_parser("erc20-type") + add_default_arguments(erc20_type_parser, True) + erc20_type_parser.set_defaults(func=handle_erc20_type) + + erc721_mintable_type_parser = subcommands.add_parser("erc721-mintable-type") + add_default_arguments(erc721_mintable_type_parser, True) + erc721_mintable_type_parser.set_defaults(func=handle_erc721_mintable_type) + + erc721_type_parser = subcommands.add_parser("erc721-type") + add_default_arguments(erc721_type_parser, True) + erc721_type_parser.set_defaults(func=handle_erc721_type) + get_amount_claimed_parser = subcommands.add_parser("get-amount-claimed") add_default_arguments(get_amount_claimed_parser, False) get_amount_claimed_parser.add_argument( @@ -912,6 +998,10 @@ def generate_cli() -> argparse.ArgumentParser: ) surrender_pool_control_parser.set_defaults(func=handle_surrender_pool_control) + terminus_mintable_type_parser = subcommands.add_parser("terminus-mintable-type") + add_default_arguments(terminus_mintable_type_parser, True) + terminus_mintable_type_parser.set_defaults(func=handle_terminus_mintable_type) + withdraw_erc1155_parser = subcommands.add_parser("withdraw-erc1155") add_default_arguments(withdraw_erc1155_parser, True) withdraw_erc1155_parser.add_argument( diff --git a/contracts/Dropper/DropperFacet.sol b/contracts/Dropper/DropperFacet.sol index be5744a1..8e7de515 100644 --- a/contracts/Dropper/DropperFacet.sol +++ b/contracts/Dropper/DropperFacet.sol @@ -55,6 +55,26 @@ contract DropperFacet is uint256 amount ); + function erc20_type() external pure returns (uint256) { + return ERC20_TYPE; + } + + function erc721_type() external pure returns (uint256) { + return ERC721_TYPE; + } + + function erc1155_type() external pure returns (uint256) { + return ERC1155_TYPE; + } + + function terminus_mintable_type() external pure returns (uint256) { + return TERMINUS_MINTABLE_TYPE; + } + + function erc721_mintable_type() external pure returns (uint256) { + return ERC721_MINTABLE_TYPE; + } + modifier onlyTerminusAdmin() { LibDropper.DropperStorage storage ds = LibDropper.dropperStorage(); require( From 903400ae5486e68e129f757789452282df41dd6d Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Fri, 12 Aug 2022 14:02:16 -0700 Subject: [PATCH 07/31] pure -> view for type methods on DropperFacet.sol --- cli/enginecli/DropperFacet.py | 59 ++++++++++++------------------ contracts/Dropper/DropperFacet.sol | 12 +++--- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/cli/enginecli/DropperFacet.py b/cli/enginecli/DropperFacet.py index 1749f1ba..b71de861 100644 --- a/cli/enginecli/DropperFacet.py +++ b/cli/enginecli/DropperFacet.py @@ -166,21 +166,23 @@ def dropper_version( self.assert_contract_is_instantiated() return self.contract.dropperVersion.call(block_identifier=block_number) - def erc1155_type(self, transaction_config) -> Any: + def erc1155_type(self, block_number: Optional[Union[str, int]] = "latest") -> Any: self.assert_contract_is_instantiated() - return self.contract.erc1155_type(transaction_config) + return self.contract.erc1155_type.call(block_identifier=block_number) - def erc20_type(self, transaction_config) -> Any: + def erc20_type(self, block_number: Optional[Union[str, int]] = "latest") -> Any: self.assert_contract_is_instantiated() - return self.contract.erc20_type(transaction_config) + return self.contract.erc20_type.call(block_identifier=block_number) - def erc721_mintable_type(self, transaction_config) -> Any: + def erc721_mintable_type( + self, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: self.assert_contract_is_instantiated() - return self.contract.erc721_mintable_type(transaction_config) + return self.contract.erc721_mintable_type.call(block_identifier=block_number) - def erc721_type(self, transaction_config) -> Any: + def erc721_type(self, block_number: Optional[Union[str, int]] = "latest") -> Any: self.assert_contract_is_instantiated() - return self.contract.erc721_type(transaction_config) + return self.contract.erc721_type.call(block_identifier=block_number) def get_amount_claimed( self, @@ -303,9 +305,11 @@ def surrender_pool_control( pool_id, terminus_address, new_pool_controller, transaction_config ) - def terminus_mintable_type(self, transaction_config) -> Any: + def terminus_mintable_type( + self, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: self.assert_contract_is_instantiated() - return self.contract.terminus_mintable_type(transaction_config) + return self.contract.terminus_mintable_type.call(block_identifier=block_number) def withdraw_erc1155( self, @@ -488,41 +492,29 @@ def handle_dropper_version(args: argparse.Namespace) -> None: def handle_erc1155_type(args: argparse.Namespace) -> None: network.connect(args.network) contract = DropperFacet(args.address) - transaction_config = get_transaction_config(args) - result = contract.erc1155_type(transaction_config=transaction_config) + result = contract.erc1155_type(block_number=args.block_number) print(result) - if args.verbose: - print(result.info()) def handle_erc20_type(args: argparse.Namespace) -> None: network.connect(args.network) contract = DropperFacet(args.address) - transaction_config = get_transaction_config(args) - result = contract.erc20_type(transaction_config=transaction_config) + result = contract.erc20_type(block_number=args.block_number) print(result) - if args.verbose: - print(result.info()) def handle_erc721_mintable_type(args: argparse.Namespace) -> None: network.connect(args.network) contract = DropperFacet(args.address) - transaction_config = get_transaction_config(args) - result = contract.erc721_mintable_type(transaction_config=transaction_config) + result = contract.erc721_mintable_type(block_number=args.block_number) print(result) - if args.verbose: - print(result.info()) def handle_erc721_type(args: argparse.Namespace) -> None: network.connect(args.network) contract = DropperFacet(args.address) - transaction_config = get_transaction_config(args) - result = contract.erc721_type(transaction_config=transaction_config) + result = contract.erc721_type(block_number=args.block_number) print(result) - if args.verbose: - print(result.info()) def handle_get_amount_claimed(args: argparse.Namespace) -> None: @@ -695,11 +687,8 @@ def handle_surrender_pool_control(args: argparse.Namespace) -> None: def handle_terminus_mintable_type(args: argparse.Namespace) -> None: network.connect(args.network) contract = DropperFacet(args.address) - transaction_config = get_transaction_config(args) - result = contract.terminus_mintable_type(transaction_config=transaction_config) + result = contract.terminus_mintable_type(block_number=args.block_number) print(result) - if args.verbose: - print(result.info()) def handle_withdraw_erc1155(args: argparse.Namespace) -> None: @@ -832,19 +821,19 @@ def generate_cli() -> argparse.ArgumentParser: dropper_version_parser.set_defaults(func=handle_dropper_version) erc1155_type_parser = subcommands.add_parser("erc1155-type") - add_default_arguments(erc1155_type_parser, True) + add_default_arguments(erc1155_type_parser, False) erc1155_type_parser.set_defaults(func=handle_erc1155_type) erc20_type_parser = subcommands.add_parser("erc20-type") - add_default_arguments(erc20_type_parser, True) + add_default_arguments(erc20_type_parser, False) erc20_type_parser.set_defaults(func=handle_erc20_type) erc721_mintable_type_parser = subcommands.add_parser("erc721-mintable-type") - add_default_arguments(erc721_mintable_type_parser, True) + add_default_arguments(erc721_mintable_type_parser, False) erc721_mintable_type_parser.set_defaults(func=handle_erc721_mintable_type) erc721_type_parser = subcommands.add_parser("erc721-type") - add_default_arguments(erc721_type_parser, True) + add_default_arguments(erc721_type_parser, False) erc721_type_parser.set_defaults(func=handle_erc721_type) get_amount_claimed_parser = subcommands.add_parser("get-amount-claimed") @@ -999,7 +988,7 @@ def generate_cli() -> argparse.ArgumentParser: surrender_pool_control_parser.set_defaults(func=handle_surrender_pool_control) terminus_mintable_type_parser = subcommands.add_parser("terminus-mintable-type") - add_default_arguments(terminus_mintable_type_parser, True) + add_default_arguments(terminus_mintable_type_parser, False) terminus_mintable_type_parser.set_defaults(func=handle_terminus_mintable_type) withdraw_erc1155_parser = subcommands.add_parser("withdraw-erc1155") diff --git a/contracts/Dropper/DropperFacet.sol b/contracts/Dropper/DropperFacet.sol index 8e7de515..4a2650c5 100644 --- a/contracts/Dropper/DropperFacet.sol +++ b/contracts/Dropper/DropperFacet.sol @@ -55,23 +55,25 @@ contract DropperFacet is uint256 amount ); - function erc20_type() external pure returns (uint256) { + // TODO(zomglings): Could be pure. Listing as view right now because ABI does not process + // correctly through moonworm generate-brownie. + function erc20_type() external view returns (uint256) { return ERC20_TYPE; } - function erc721_type() external pure returns (uint256) { + function erc721_type() external view returns (uint256) { return ERC721_TYPE; } - function erc1155_type() external pure returns (uint256) { + function erc1155_type() external view returns (uint256) { return ERC1155_TYPE; } - function terminus_mintable_type() external pure returns (uint256) { + function terminus_mintable_type() external view returns (uint256) { return TERMINUS_MINTABLE_TYPE; } - function erc721_mintable_type() external pure returns (uint256) { + function erc721_mintable_type() external view returns (uint256) { return ERC721_MINTABLE_TYPE; } From 0a61d18d997015f067f848b419067a9a9adb090e Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Fri, 12 Aug 2022 14:12:22 -0700 Subject: [PATCH 08/31] Made DropperClaimTests -> DropperDropTests work --- cli/enginecli/test_dropper.py | 218 +++++++++++++++++----------------- 1 file changed, 109 insertions(+), 109 deletions(-) diff --git a/cli/enginecli/test_dropper.py b/cli/enginecli/test_dropper.py index c4e24ea4..eac863fd 100644 --- a/cli/enginecli/test_dropper.py +++ b/cli/enginecli/test_dropper.py @@ -12,6 +12,8 @@ ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" +MAX_UINT = 2**256 - 1 + def sign_message(message_hash, signer): eth_private_key = eth_keys.keys.PrivateKey(HexBytes(signer.private_key)) @@ -89,126 +91,124 @@ def setUpClass(cls) -> None: cls.signer_0 = accounts.add() cls.signer_1 = accounts.add() - def create_claim_and_return_claim_id(self, *args, **kwargs) -> int: - tx_receipt = self.dropper.create_claim(*args, **kwargs) - claim_created_event_abi = None + def create_drop_and_return_drop_id(self, *args, **kwargs) -> int: + tx_receipt = self.dropper.create_drop(*args, **kwargs) + drop_created_event_abi = None for item in self.dropper.abi: - if item["type"] == "event" and item["name"] == "ClaimCreated": - claim_created_event_abi = item - self.assertIsNotNone(claim_created_event_abi) + if item["type"] == "event" and item["name"] == "DropCreated": + drop_created_event_abi = item + self.assertIsNotNone(drop_created_event_abi) events = _fetch_events_chunk( web3_client, - claim_created_event_abi, + drop_created_event_abi, from_block=tx_receipt.block_number, to_block=tx_receipt.block_number, ) self.assertEqual(len(events), 1) - return events[0]["args"]["claimId"] + return events[0]["args"]["dropId"] -class DropperClaimTests(DropperTestCase): - def test_claim_creation(self): - num_claims_0 = self.dropper.num_claims() - claim_id = self.create_claim_and_return_claim_id( - 20, self.erc20_contract.address, 0, 1, {"from": accounts[0]} +class DropperDropTests(DropperTestCase): + def test_drop_creation(self): + num_drops_0 = self.dropper.num_drops() + drop_id = self.create_drop_and_return_drop_id( + 20, self.erc20_contract.address, 0, 1, MAX_UINT, {"from": accounts[0]} ) - num_claims_1 = self.dropper.num_claims() - self.assertEqual(num_claims_1, num_claims_0 + 1) - self.assertEqual(claim_id, num_claims_1) + num_drops_1 = self.dropper.num_drops() + self.assertEqual(num_drops_1, num_drops_0 + 1) + self.assertEqual(drop_id, num_drops_1) - claim_info = self.dropper.get_claim(claim_id) - self.assertEqual(claim_info, (20, self.erc20_contract.address, 0, 1)) + drop_info = self.dropper.get_drop(drop_id) + self.assertEqual(drop_info, (20, self.erc20_contract.address, 0, 1)) - def test_claim_creation_fails_if_unknown_token_type(self): + def test_drop_creation_fails_if_unknown_token_type(self): UNKNOWN_TOKEN_TYPE = 43 - num_claims_0 = self.dropper.num_claims() + num_drops_0 = self.dropper.num_drops() with self.assertRaises(VirtualMachineError): - self.dropper.create_claim( - 43, self.erc20_contract.address, 0, 1, {"from": accounts[0]} + self.dropper.create_drop( + 43, self.erc20_contract.address, 0, 1, MAX_UINT, {"from": accounts[0]} ) - num_claims_1 = self.dropper.num_claims() - self.assertEqual(num_claims_1, num_claims_0) + num_drops_1 = self.dropper.num_drops() + self.assertEqual(num_drops_1, num_drops_0) - def test_claim_creation_fails_from_non_owner(self): - num_claims_0 = self.dropper.num_claims() + def test_drop_creation_fails_from_non_owner(self): + num_drops_0 = self.dropper.num_drops() with self.assertRaises(VirtualMachineError): - self.dropper.create_claim( - 20, self.erc20_contract.address, 0, 1, {"from": accounts[1]} + self.dropper.create_drop( + 20, self.erc20_contract.address, 0, 1, MAX_UINT, {"from": accounts[1]} ) - num_claims_1 = self.dropper.num_claims() - self.assertEqual(num_claims_1, num_claims_0) + num_drops_1 = self.dropper.num_drops() + self.assertEqual(num_drops_1, num_drops_0) - def test_claim_status_for_new_claim(self): - claim_id = self.create_claim_and_return_claim_id( - 20, self.erc20_contract.address, 0, 1, {"from": accounts[0]} + def test_drop_status_for_new_drop(self): + drop_id = self.create_drop_and_return_drop_id( + 20, self.erc20_contract.address, 0, 1, MAX_UINT, {"from": accounts[0]} ) - self.assertTrue(self.dropper.claim_status(claim_id)) + self.assertTrue(self.dropper.drop_status(drop_id)) - def test_claim_status_can_be_changed_by_owner(self): - claim_id = self.create_claim_and_return_claim_id( - 20, self.erc20_contract.address, 0, 1, {"from": accounts[0]} + def test_drop_status_can_be_changed_by_owner(self): + drop_id = self.create_drop_and_return_drop_id( + 20, self.erc20_contract.address, 0, 1, MAX_UINT, {"from": accounts[0]} ) - self.assertTrue(self.dropper.claim_status(claim_id)) + self.assertTrue(self.dropper.drop_status(drop_id)) - self.dropper.set_claim_status(claim_id, False, {"from": accounts[0]}) - self.assertFalse(self.dropper.claim_status(claim_id)) + self.dropper.set_drop_status(drop_id, False, {"from": accounts[0]}) + self.assertFalse(self.dropper.drop_status(drop_id)) - def test_claim_status_cannot_be_changed_by_non_owner(self): - claim_id = self.create_claim_and_return_claim_id( - 20, self.erc20_contract.address, 0, 1, {"from": accounts[0]} + def test_drop_status_cannot_be_changed_by_non_owner(self): + drop_id = self.create_drop_and_return_drop_id( + 20, self.erc20_contract.address, 0, 1, MAX_UINT, {"from": accounts[0]} ) - self.assertTrue(self.dropper.claim_status(claim_id)) + self.assertTrue(self.dropper.drop_status(drop_id)) with self.assertRaises(VirtualMachineError): - self.dropper.set_claim_status(claim_id, False, {"from": accounts[1]}) + self.dropper.set_drop_status(drop_id, False, {"from": accounts[1]}) - self.assertTrue(self.dropper.claim_status(claim_id)) + self.assertTrue(self.dropper.drop_status(drop_id)) def test_owner_can_set_signer(self): - claim_id = self.create_claim_and_return_claim_id( - 20, self.erc20_contract.address, 0, 1, {"from": accounts[0]} + drop_id = self.create_drop_and_return_drop_id( + 20, self.erc20_contract.address, 0, 1, MAX_UINT, {"from": accounts[0]} ) - self.assertEqual(self.dropper.get_signer_for_claim(claim_id), ZERO_ADDRESS) + self.assertEqual(self.dropper.get_signer_for_drop(drop_id), ZERO_ADDRESS) - self.dropper.set_signer_for_claim( - claim_id, self.signer_0.address, {"from": accounts[0]} + self.dropper.set_signer_for_drop( + drop_id, self.signer_0.address, {"from": accounts[0]} ) self.assertEqual( - self.dropper.get_signer_for_claim(claim_id), self.signer_0.address + self.dropper.get_signer_for_drop(drop_id), self.signer_0.address ) def test_non_owner_cannot_set_signer(self): - claim_id = self.create_claim_and_return_claim_id( - 20, self.erc20_contract.address, 0, 1, {"from": accounts[0]} + drop_id = self.create_drop_and_return_drop_id( + 20, self.erc20_contract.address, 0, 1, MAX_UINT, {"from": accounts[0]} ) - self.assertEqual(self.dropper.get_signer_for_claim(claim_id), ZERO_ADDRESS) + self.assertEqual(self.dropper.get_signer_for_drop(drop_id), ZERO_ADDRESS) with self.assertRaises(VirtualMachineError): - self.dropper.set_signer_for_claim( - claim_id, self.signer_0.address, {"from": accounts[1]} + self.dropper.set_signer_for_drop( + drop_id, self.signer_0.address, {"from": accounts[1]} ) - self.assertEqual(self.dropper.get_signer_for_claim(claim_id), ZERO_ADDRESS) + self.assertEqual(self.dropper.get_signer_for_drop(drop_id), ZERO_ADDRESS) - def test_owner_can_set_claim_uri(self): - claim_id = self.create_claim_and_return_claim_id( - 20, self.erc20_contract.address, 0, 1, {"from": accounts[0]} - ) - self.assertEqual(self.dropper.claim_uri(claim_id), "") - self.dropper.set_claim_uri( - claim_id, "https://example.com", {"from": accounts[0]} + def test_owner_can_set_drop_uri(self): + drop_id = self.create_drop_and_return_drop_id( + 20, self.erc20_contract.address, 0, 1, MAX_UINT, {"from": accounts[0]} ) - self.assertEqual(self.dropper.claim_uri(claim_id), "https://example.com") + self.assertEqual(self.dropper.drop_uri(drop_id), "") + self.dropper.set_drop_uri(drop_id, "https://example.com", {"from": accounts[0]}) + self.assertEqual(self.dropper.drop_uri(drop_id), "https://example.com") - def test_non_owner_cannot_set_claim_uri(self): - claim_id = self.create_claim_and_return_claim_id( - 20, self.erc20_contract.address, 0, 1, {"from": accounts[0]} + def test_non_owner_cannot_set_drop_uri(self): + drop_id = self.create_drop_and_return_drop_id( + 20, self.erc20_contract.address, 0, 1, MAX_UINT, {"from": accounts[0]} ) - self.assertEqual(self.dropper.claim_uri(claim_id), "") + self.assertEqual(self.dropper.drop_uri(drop_id), "") with self.assertRaises(VirtualMachineError): - self.dropper.set_claim_uri( - claim_id, "https://example.com", {"from": accounts[1]} + self.dropper.set_drop_uri( + drop_id, "https://example.com", {"from": accounts[1]} ) - self.assertEqual(self.dropper.claim_uri(claim_id), "") + self.assertEqual(self.dropper.drop_uri(drop_id), "") class DropperWithdrawalTests(DropperTestCase): @@ -359,7 +359,7 @@ class DropperClaimERC20Tests(DropperTestCase): def test_claim_erc20(self): reward = 3 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -390,7 +390,7 @@ def test_claim_erc20_with_custom_amount(self): default_reward = 3 custom_reward = 89 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, default_reward, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -424,7 +424,7 @@ def test_claim_erc20_with_custom_amount(self): def test_claim_erc20_fails_if_block_deadline_exceeded(self): reward = 5 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -456,7 +456,7 @@ def test_claim_erc20_fails_if_block_deadline_exceeded(self): def test_claim_erc20_fails_if_incorrect_amount(self): reward = 5 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -492,7 +492,7 @@ def test_claim_erc20_fails_if_incorrect_amount(self): def test_claim_erc20_fails_if_wrong_claimant(self): reward = 6 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -527,7 +527,7 @@ def test_claim_erc20_fails_if_wrong_claimant(self): def test_claim_erc20_fails_if_wrong_signer(self): reward = 7 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -559,7 +559,7 @@ def test_claim_erc20_fails_if_wrong_signer(self): def test_claim_erc20_fails_on_repeated_attempts_with_same_signed_message(self): reward = 9 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -602,7 +602,7 @@ def test_claim_erc20_fails_on_repeated_attempts_with_different_signed_messages( ): reward = 9 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -656,7 +656,7 @@ def test_claim_erc20_fails_when_insufficient_balance(self): self.erc20_contract.balance_of(self.dropper.address), {"from": accounts[0]}, ) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -690,7 +690,7 @@ class DropperClaimERC721Tests(DropperTestCase): def test_claim_erc721(self): token_id = 103 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -716,7 +716,7 @@ def test_claim_erc721_with_custom_amount(self): token_id = 1003 custom_amount = 79 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -745,7 +745,7 @@ def test_claim_erc721_with_custom_amount(self): def test_claim_erc721_fails_if_block_deadline_exceeded(self): token_id = 105 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -772,7 +772,7 @@ def test_claim_erc721_fails_if_block_deadline_exceeded(self): def test_claim_erc721_fails_if_incorrect_amount(self): token_id = 1005 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -799,7 +799,7 @@ def test_claim_erc721_fails_if_incorrect_amount(self): def test_claim_erc721_fails_if_wrong_claimant(self): token_id = 106 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -826,7 +826,7 @@ def test_claim_erc721_fails_if_wrong_claimant(self): def test_claim_erc721_fails_if_wrong_signer(self): token_id = 107 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -853,7 +853,7 @@ def test_claim_erc721_fails_if_wrong_signer(self): def test_claim_erc721_fails_on_repeated_attempts_with_same_signed_message(self): token_id = 109 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -897,7 +897,7 @@ def test_claim_erc721_fails_on_repeated_attempts_with_different_signed_messages( ): token_id = 110 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -944,7 +944,7 @@ def test_claim_erc721_fails_if_dropper_not_owner(self): token_id = 111 self.nft_contract.mint(accounts[0].address, token_id, {"from": accounts[0]}) self.assertNotEqual(self.nft_contract.owner_of(token_id), self.dropper.address) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( @@ -978,7 +978,7 @@ def test_claim_erc1155(self): "", {"from": accounts[0]}, ) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 1155, self.terminus.address, self.terminus_pool_id, @@ -1022,7 +1022,7 @@ def test_claim_erc1155_with_custom_amount(self): "", {"from": accounts[0]}, ) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 1155, self.terminus.address, self.terminus_pool_id, @@ -1069,7 +1069,7 @@ def test_claim_erc1155_fails_if_block_deadline_exceeded(self): "", {"from": accounts[0]}, ) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 1155, self.terminus.address, self.terminus_pool_id, @@ -1113,7 +1113,7 @@ def test_claim_erc1155_fails_if_incorrect_amount(self): "", {"from": accounts[0]}, ) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 1155, self.terminus.address, self.terminus_pool_id, @@ -1157,7 +1157,7 @@ def test_claim_erc1155_fails_if_wrong_claimant(self): "", {"from": accounts[0]}, ) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 1155, self.terminus.address, self.terminus_pool_id, @@ -1208,7 +1208,7 @@ def test_claim_erc1155_fails_if_wrong_signer(self): "", {"from": accounts[0]}, ) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 1155, self.terminus.address, self.terminus_pool_id, @@ -1252,7 +1252,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_same_signed_message(self) "", {"from": accounts[0]}, ) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 1155, self.terminus.address, self.terminus_pool_id, @@ -1309,7 +1309,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_different_signed_messages "", {"from": accounts[0]}, ) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 1155, self.terminus.address, self.terminus_pool_id, @@ -1370,7 +1370,7 @@ def test_claim_erc1155_fails_when_insufficient_balance(self): self.terminus.balance_of(self.dropper.address, self.terminus_pool_id), {"from": accounts[0]}, ) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( 1155, self.terminus.address, self.terminus_pool_id, @@ -1409,7 +1409,7 @@ def test_claim_erc1155_fails_when_insufficient_balance(self): class DropperClaimERC1155MintableTests(DropperTestCase): def test_claim_erc1155(self): reward = 3 - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( self.TERMINUS_MINTABLE_TYPE, self.terminus.address, self.mintable_terminus_pool_id, @@ -1453,7 +1453,7 @@ def test_claim_erc1155(self): def test_claim_erc1155_with_custom_amount(self): default_reward = 3 custom_reward = default_reward + 1 - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( self.TERMINUS_MINTABLE_TYPE, self.terminus.address, self.mintable_terminus_pool_id, @@ -1511,7 +1511,7 @@ def test_claim_erc1155_fails_if_dropper_does_not_have_pool_control(self): {"from": accounts[0]}, ) - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( self.TERMINUS_MINTABLE_TYPE, self.terminus.address, terminus_pool_which_is_owned_by_dropper_id, @@ -1595,7 +1595,7 @@ def test_claim_erc1155_fails_if_dropper_does_not_have_pool_control(self): def test_claim_erc1155_fails_if_block_deadline_exceeded(self): reward = 5 - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( self.TERMINUS_MINTABLE_TYPE, self.terminus.address, self.mintable_terminus_pool_id, @@ -1641,7 +1641,7 @@ def test_claim_erc1155_fails_if_block_deadline_exceeded(self): def test_claim_erc1155_fails_if_incorrect_amount(self): reward = 5 - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( self.TERMINUS_MINTABLE_TYPE, self.terminus.address, self.mintable_terminus_pool_id, @@ -1688,7 +1688,7 @@ def test_claim_erc1155_fails_if_incorrect_amount(self): def test_claim_erc1155_fails_if_wrong_claimant(self): reward = 6 - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( self.TERMINUS_MINTABLE_TYPE, self.terminus.address, self.mintable_terminus_pool_id, @@ -1739,7 +1739,7 @@ def test_claim_erc1155_fails_if_wrong_claimant(self): def test_claim_erc1155_fails_if_wrong_signer(self): reward = 7 - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( self.TERMINUS_MINTABLE_TYPE, self.terminus.address, self.mintable_terminus_pool_id, @@ -1783,7 +1783,7 @@ def test_claim_erc1155_fails_if_wrong_signer(self): def test_claim_erc1155_fails_on_repeated_attempts_with_same_signed_message(self): reward = 9 - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( self.TERMINUS_MINTABLE_TYPE, self.terminus.address, self.mintable_terminus_pool_id, @@ -1844,7 +1844,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_different_signed_messages self, ): reward = 9 - claim_id = self.create_claim_and_return_claim_id( + claim_id = self.create_drop_and_return_drop_id( self.TERMINUS_MINTABLE_TYPE, self.terminus.address, self.mintable_terminus_pool_id, From 237d5a375b84bbb98f973ab952d97b2a0131a2a3 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Mon, 15 Aug 2022 12:01:04 -0700 Subject: [PATCH 09/31] [WIP] Fixing dropper unittests After moving to EIP2535 upgradable Dropper implementation (and foolishly also implementing some additonal changes from the 0.2.0 design document). --- cli/enginecli/test_dropper.py | 61 ++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/cli/enginecli/test_dropper.py b/cli/enginecli/test_dropper.py index eac863fd..1af108c8 100644 --- a/cli/enginecli/test_dropper.py +++ b/cli/enginecli/test_dropper.py @@ -108,7 +108,7 @@ def create_drop_and_return_drop_id(self, *args, **kwargs) -> int: return events[0]["args"]["dropId"] -class DropperDropTests(DropperTestCase): +class DropperDropSetupTests(DropperTestCase): def test_drop_creation(self): num_drops_0 = self.dropper.num_drops() drop_id = self.create_drop_and_return_drop_id( @@ -360,7 +360,7 @@ def test_claim_erc20(self): reward = 3 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} + 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -391,7 +391,12 @@ def test_claim_erc20_with_custom_amount(self): custom_reward = 89 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 20, self.erc20_contract.address, 0, default_reward, {"from": accounts[0]} + 20, + self.erc20_contract.address, + 0, + default_reward, + MAX_UINT, + {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -425,7 +430,7 @@ def test_claim_erc20_fails_if_block_deadline_exceeded(self): reward = 5 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} + 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -457,7 +462,7 @@ def test_claim_erc20_fails_if_incorrect_amount(self): reward = 5 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} + 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -493,7 +498,7 @@ def test_claim_erc20_fails_if_wrong_claimant(self): reward = 6 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} + 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -528,7 +533,7 @@ def test_claim_erc20_fails_if_wrong_signer(self): reward = 7 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} + 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -560,7 +565,7 @@ def test_claim_erc20_fails_on_repeated_attempts_with_same_signed_message(self): reward = 9 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} + 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -603,7 +608,7 @@ def test_claim_erc20_fails_on_repeated_attempts_with_different_signed_messages( reward = 9 self.erc20_contract.mint(self.dropper.address, 100, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} + 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -657,7 +662,7 @@ def test_claim_erc20_fails_when_insufficient_balance(self): {"from": accounts[0]}, ) claim_id = self.create_drop_and_return_drop_id( - 20, self.erc20_contract.address, 0, reward, {"from": accounts[0]} + 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -691,7 +696,7 @@ def test_claim_erc721(self): token_id = 103 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} + 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -717,7 +722,7 @@ def test_claim_erc721_with_custom_amount(self): custom_amount = 79 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} + 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -746,7 +751,7 @@ def test_claim_erc721_fails_if_block_deadline_exceeded(self): token_id = 105 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} + 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -773,7 +778,7 @@ def test_claim_erc721_fails_if_incorrect_amount(self): token_id = 1005 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} + 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -800,7 +805,7 @@ def test_claim_erc721_fails_if_wrong_claimant(self): token_id = 106 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} + 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -827,7 +832,7 @@ def test_claim_erc721_fails_if_wrong_signer(self): token_id = 107 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} + 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -854,7 +859,7 @@ def test_claim_erc721_fails_on_repeated_attempts_with_same_signed_message(self): token_id = 109 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} + 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -898,7 +903,7 @@ def test_claim_erc721_fails_on_repeated_attempts_with_different_signed_messages( token_id = 110 self.nft_contract.mint(self.dropper.address, token_id, {"from": accounts[0]}) claim_id = self.create_drop_and_return_drop_id( - 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} + 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -945,7 +950,7 @@ def test_claim_erc721_fails_if_dropper_not_owner(self): self.nft_contract.mint(accounts[0].address, token_id, {"from": accounts[0]}) self.assertNotEqual(self.nft_contract.owner_of(token_id), self.dropper.address) claim_id = self.create_drop_and_return_drop_id( - 721, self.nft_contract.address, token_id, 1, {"from": accounts[0]} + 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) self.dropper.set_signer_for_claim( claim_id, self.signer_0.address, {"from": accounts[0]} @@ -983,6 +988,7 @@ def test_claim_erc1155(self): self.terminus.address, self.terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1027,6 +1033,7 @@ def test_claim_erc1155_with_custom_amount(self): self.terminus.address, self.terminus_pool_id, default_reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1074,6 +1081,7 @@ def test_claim_erc1155_fails_if_block_deadline_exceeded(self): self.terminus.address, self.terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1118,6 +1126,7 @@ def test_claim_erc1155_fails_if_incorrect_amount(self): self.terminus.address, self.terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1162,6 +1171,7 @@ def test_claim_erc1155_fails_if_wrong_claimant(self): self.terminus.address, self.terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1213,6 +1223,7 @@ def test_claim_erc1155_fails_if_wrong_signer(self): self.terminus.address, self.terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1257,6 +1268,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_same_signed_message(self) self.terminus.address, self.terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1314,6 +1326,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_different_signed_messages self.terminus.address, self.terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1375,6 +1388,7 @@ def test_claim_erc1155_fails_when_insufficient_balance(self): self.terminus.address, self.terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1414,6 +1428,7 @@ def test_claim_erc1155(self): self.terminus.address, self.mintable_terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1458,6 +1473,7 @@ def test_claim_erc1155_with_custom_amount(self): self.terminus.address, self.mintable_terminus_pool_id, default_reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1516,6 +1532,7 @@ def test_claim_erc1155_fails_if_dropper_does_not_have_pool_control(self): self.terminus.address, terminus_pool_which_is_owned_by_dropper_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1600,6 +1617,7 @@ def test_claim_erc1155_fails_if_block_deadline_exceeded(self): self.terminus.address, self.mintable_terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) @@ -1646,6 +1664,7 @@ def test_claim_erc1155_fails_if_incorrect_amount(self): self.terminus.address, self.mintable_terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) @@ -1693,6 +1712,7 @@ def test_claim_erc1155_fails_if_wrong_claimant(self): self.terminus.address, self.mintable_terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1744,6 +1764,7 @@ def test_claim_erc1155_fails_if_wrong_signer(self): self.terminus.address, self.mintable_terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1788,6 +1809,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_same_signed_message(self) self.terminus.address, self.mintable_terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( @@ -1849,6 +1871,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_different_signed_messages self.terminus.address, self.mintable_terminus_pool_id, reward, + MAX_UINT, {"from": accounts[0]}, ) self.dropper.set_signer_for_claim( From 3d09bb0784506b0ea861f8242b93f5ea9a767b37 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 31 Jan 2023 20:51:24 -0800 Subject: [PATCH 10/31] Overmerge of main, with fixes to DropperFacet and tests --- .github/workflows/chainlink.yml | 39 + .github/workflows/crafting.yml | 42 + .github/workflows/{test.yml => dropper.yml} | 13 +- .github/workflows/gofp.yml | 42 + .github/workflows/lootbox.yml | 46 + .github/workflows/reentrancy-guard.yml | 42 + .gitignore | 5 + .../782ac8fe23c8_add_resource_id_column.py | 38 + api/engineapi/actions.py | 259 +- api/engineapi/cli.py | 186 +- api/engineapi/data.py | 19 + api/engineapi/middleware.py | 37 +- api/engineapi/models.py | 1 + api/engineapi/routes/leaderboard.py | 239 +- api/engineapi/settings.py | 25 + api/regular-scores.json | 10 + api/requirements.txt | 51 +- api/setup.py | 1 + api/token-id-scores.json | 10 + apps/play/.eslintrc.json | 11 +- apps/play/components/GroupImage.js | 39 + apps/play/components/LeaderboardGroup.js | 47 + .../play/components/LeaderboardGroupHeader.js | 61 + apps/play/components/LeaderboardRank.js | 30 + apps/play/components/ShadocornRow.js | 53 + apps/play/games/GoFPABI.json | 878 +++++ apps/play/games/cu/GameBankABI.js | 5 + apps/play/games/cu/Multicall2.json | 313 ++ apps/play/games/cu/Multicall2.ts | 90 + apps/play/games/cu/StashABI.js | 5 + apps/play/games/cu/types.js | 2 + apps/play/package.json | 6 +- apps/play/pages/_app.js | 6 +- apps/play/pages/_document.js | 9 + apps/play/pages/docs.js | 6 +- apps/play/pages/drops/details.js | 8 +- apps/play/pages/drops/index.js | 28 +- apps/play/pages/entry-point.js | 2 +- .../index.tsx} | 718 ++-- .../games/CryptoUnicorns/leaderboard.tsx | 526 +++ .../play/pages/games/OpenGamingCollective.tsx | 293 ++ apps/play/pages/games/cu/dashboard.tsx | 191 + apps/play/pages/games/index.js | 25 +- apps/play/pages/index.tsx | 7 +- apps/play/pages/inventory/index.js | 7 +- apps/play/public/favicon.png | Bin 2723 -> 797 bytes apps/play/sample.env | 1 + apps/play/src/AppContext.js | 6 +- apps/play/src/EngineLayout.d.ts | 1 + apps/play/src/Theme/Button/index.js | 52 + apps/play/src/Theme/theme.js | 11 +- apps/play/src/constants.js | 10 +- apps/play/styles/sidebar.css | 12 +- brownie-config.yaml | 7 +- cli/enginecli/GOFPFacet.py | 1114 ++++++ cli/enginecli/cli.py | 18 +- cli/enginecli/core.py | 82 +- cli/enginecli/gas_profiler.py | 2 +- cli/enginecli/setup_drop.py | 98 +- cli/enginecli/test_crafting.py | 2 +- cli/enginecli/test_dropper.py | 244 +- cli/enginecli/test_gofp.py | 3490 +++++++++++++++++ cli/enginecli/test_random_lootbox.py | 7 +- cli/test.sh | 4 +- contracts/Dropper/DropperFacet.sol | 12 +- contracts/Dropper/LibDropper.sol | 2 + .../GardenOfForkingPaths.sol | 824 ++++ .../moonstream-components/src/Theme/theme.js | 9 +- .../src/components/ChainSelectorPlay.js | 145 + .../src/components/ClaimCardPlay.js | 146 + .../components/CryptoUnicorns/LootboxCard.tsx | 17 +- .../CryptoUnicorns/LootboxCardPlay.tsx | 59 + .../CryptoUnicorns/MostActiveUsers.tsx | 167 + .../components/CryptoUnicorns/RecentSales.tsx | 147 + .../CryptoUnicorns/TerminusSupply.tsx | 0 .../components/CryptoUnicorns/TotalSupply.tsx | 150 + .../src/components/DropperContractPlay.js | 61 + .../src/components/FeatureCard.js | 2 +- .../src/components/FeatureCardPlay.js | 89 + .../src/components/LandingNavbar.js | 9 +- .../src/components/LandingNavbarPlay.js | 185 + .../src/components/LineChart.tsx | 102 + .../src/components/Navbar.js | 2 +- .../src/components/NavbarPlay.js | 26 + .../src/components/PaginatorPlay.js | 153 + .../src/components/RadioFilter.tsx | 63 + .../src/components/SidebarPlay.js | 143 + .../src/components/lootbox/LootboxCard.js | 2 + .../src/components/lootbox/LootboxCardPlay.js | 106 + .../src/core/contracts/erc20.contracts.ts | 6 +- .../src/core/cu/constants.tsx | 64 + .../src/core/hooks/useERC20.ts | 24 +- .../core/providers/AnalyticsProvider/index.js | 12 +- .../core/providers/Web3Provider/context.ts | 1 + .../src/core/providers/Web3Provider/index.tsx | 10 + .../src/core/types/DashboardTypes.ts | 15 + .../src/core/utils/http.js | 4 +- .../src/layouts/EngineLayout.js | 2 +- .../src/layouts/RootLayout.js | 2 +- .../src/layoutsForPlay/EngineLayout.js | 81 + .../src/layoutsForPlay/EntryPointLayout.js | 9 + .../src/layoutsForPlay/InfoPageLayout.js | 141 + .../src/layoutsForPlay/RootLayout.js | 94 + .../src/layoutsForPlay/index.js | 19 + packages/web3auth/index.ts | 1 - sample.env | 2 + scripts/dropper-create-claim.sh | 8 + scripts/dropper-delete-claimant.sh | 8 + types/Moonstream.ts | 1 + types/contracts/GOFPFacet.ts | 338 ++ 110 files changed, 12566 insertions(+), 529 deletions(-) create mode 100644 .github/workflows/chainlink.yml create mode 100644 .github/workflows/crafting.yml rename .github/workflows/{test.yml => dropper.yml} (72%) create mode 100644 .github/workflows/gofp.yml create mode 100644 .github/workflows/lootbox.yml create mode 100644 .github/workflows/reentrancy-guard.yml create mode 100644 alembic/versions/782ac8fe23c8_add_resource_id_column.py create mode 100644 api/regular-scores.json create mode 100644 api/token-id-scores.json create mode 100644 apps/play/components/GroupImage.js create mode 100644 apps/play/components/LeaderboardGroup.js create mode 100644 apps/play/components/LeaderboardGroupHeader.js create mode 100644 apps/play/components/LeaderboardRank.js create mode 100644 apps/play/components/ShadocornRow.js create mode 100644 apps/play/games/GoFPABI.json create mode 100644 apps/play/games/cu/GameBankABI.js create mode 100644 apps/play/games/cu/Multicall2.json create mode 100644 apps/play/games/cu/Multicall2.ts create mode 100644 apps/play/games/cu/StashABI.js create mode 100644 apps/play/games/cu/types.js rename apps/play/pages/games/{cryptoUnicorns.tsx => CryptoUnicorns/index.tsx} (71%) create mode 100644 apps/play/pages/games/CryptoUnicorns/leaderboard.tsx create mode 100644 apps/play/pages/games/OpenGamingCollective.tsx create mode 100644 apps/play/pages/games/cu/dashboard.tsx create mode 100644 apps/play/src/EngineLayout.d.ts create mode 100644 cli/enginecli/GOFPFacet.py create mode 100644 cli/enginecli/test_gofp.py create mode 100644 contracts/mechanics/garden-of-forking-paths/GardenOfForkingPaths.sol create mode 100644 packages/moonstream-components/src/components/ChainSelectorPlay.js create mode 100644 packages/moonstream-components/src/components/ClaimCardPlay.js create mode 100644 packages/moonstream-components/src/components/CryptoUnicorns/LootboxCardPlay.tsx create mode 100644 packages/moonstream-components/src/components/CryptoUnicorns/MostActiveUsers.tsx create mode 100644 packages/moonstream-components/src/components/CryptoUnicorns/RecentSales.tsx rename cli/enginecli/test_dropper_api.py => packages/moonstream-components/src/components/CryptoUnicorns/TerminusSupply.tsx (100%) create mode 100644 packages/moonstream-components/src/components/CryptoUnicorns/TotalSupply.tsx create mode 100644 packages/moonstream-components/src/components/DropperContractPlay.js create mode 100644 packages/moonstream-components/src/components/FeatureCardPlay.js create mode 100644 packages/moonstream-components/src/components/LandingNavbarPlay.js create mode 100644 packages/moonstream-components/src/components/LineChart.tsx create mode 100644 packages/moonstream-components/src/components/NavbarPlay.js create mode 100644 packages/moonstream-components/src/components/PaginatorPlay.js create mode 100644 packages/moonstream-components/src/components/RadioFilter.tsx create mode 100644 packages/moonstream-components/src/components/SidebarPlay.js create mode 100644 packages/moonstream-components/src/components/lootbox/LootboxCardPlay.js create mode 100644 packages/moonstream-components/src/core/cu/constants.tsx create mode 100644 packages/moonstream-components/src/core/types/DashboardTypes.ts create mode 100644 packages/moonstream-components/src/layoutsForPlay/EngineLayout.js create mode 100644 packages/moonstream-components/src/layoutsForPlay/EntryPointLayout.js create mode 100644 packages/moonstream-components/src/layoutsForPlay/InfoPageLayout.js create mode 100644 packages/moonstream-components/src/layoutsForPlay/RootLayout.js create mode 100644 packages/moonstream-components/src/layoutsForPlay/index.js create mode 100755 scripts/dropper-create-claim.sh create mode 100755 scripts/dropper-delete-claimant.sh create mode 100644 types/contracts/GOFPFacet.ts diff --git a/.github/workflows/chainlink.yml b/.github/workflows/chainlink.yml new file mode 100644 index 00000000..fc95f0c9 --- /dev/null +++ b/.github/workflows/chainlink.yml @@ -0,0 +1,39 @@ +name: Chainlink protocol tests + +on: + pull_request: + paths: + - "cli/chainlink/**" + - "contracts/mock/MockChainlinkCoordinator.sol" + - "contracts/mock/MockLinkToken.sol" + - "contracts/mock/MockVRFUser.sol" + - ".github/workflows/chainlink.yml" + branches: + - main +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16" + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - name: Install ganache + run: npm install -g ganache-cli + - name: Upgrade pip + env: + BROWNIE_LIB: 1 + run: pip install -U pip + - name: Install additional dev dependencies + run: | + pip install black moonworm + - name: Install dependencies for CLI + working-directory: cli/ + run: | + pip install -e . + - name: Run tests + working-directory: cli/ + run: bash test.sh chainlink.test_chainlink diff --git a/.github/workflows/crafting.yml b/.github/workflows/crafting.yml new file mode 100644 index 00000000..576aa1fe --- /dev/null +++ b/.github/workflows/crafting.yml @@ -0,0 +1,42 @@ +name: Crafting system tests + +on: + pull_request: + paths: + - "contracts/crafting/**" + - "contracts/mock/**" + - "cli/enginecli/test_crafting.py" + - "cli/enginecli/CraftingFacet.py" + - "cli/enginecli/Mock*.py" + - "cli/enginecli/Diamond*" + - "cli/enginecli/OwnershipFacet.py" + - ".github/workflows/crafting.yml" + branches: + - main +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16" + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - name: Install ganache + run: npm install -g ganache-cli + - name: Upgrade pip + env: + BROWNIE_LIB: 1 + run: pip install -U pip + - name: Install additional dev dependencies + run: | + pip install black moonworm + - name: Install dependencies for CLI + working-directory: cli/ + run: | + pip install -e . + - name: Run tests + working-directory: cli/ + run: bash test.sh enginecli.test_crafting diff --git a/.github/workflows/test.yml b/.github/workflows/dropper.yml similarity index 72% rename from .github/workflows/test.yml rename to .github/workflows/dropper.yml index 64ce75f7..1fd33443 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/dropper.yml @@ -1,13 +1,16 @@ -name: Test smart contracts and backend code +name: Dropper system tests on: pull_request: paths: - - "contracts/**" - - "cli/**" + - "contracts/Dropper.sol" + - "contracts/mock/**" + - "cli/enginecli/test_dropper.py" + - "cli/enginecli/Dropper.py" + - "cli/enginecli/Mock*.py" + - ".github/workflows/dropper.yml" branches: - main - jobs: build: runs-on: ubuntu-20.04 @@ -34,4 +37,4 @@ jobs: pip install -e . - name: Run tests working-directory: cli/ - run: bash test.sh + run: bash test.sh enginecli.test_dropper diff --git a/.github/workflows/gofp.yml b/.github/workflows/gofp.yml new file mode 100644 index 00000000..3747dbb5 --- /dev/null +++ b/.github/workflows/gofp.yml @@ -0,0 +1,42 @@ +name: Garden of Forking Paths system tests + +on: + pull_request: + paths: + - "contracts/mechanics/garden-of-forking-paths/**" + - "contracts/mock/**" + - "cli/enginecli/test_gofp.py" + - "cli/enginecli/GOFPFacet.py" + - "cli/enginecli/Mock*.py" + - "cli/enginecli/Diamond*" + - "cli/enginecli/OwnershipFacet.py" + - ".github/workflows/gofp.yml" + branches: + - main +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16" + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - name: Install ganache + run: npm install -g ganache-cli + - name: Upgrade pip + env: + BROWNIE_LIB: 1 + run: pip install -U pip + - name: Install additional dev dependencies + run: | + pip install black moonworm + - name: Install dependencies for CLI + working-directory: cli/ + run: | + pip install -e . + - name: Run tests + working-directory: cli/ + run: bash test.sh enginecli.test_gofp diff --git a/.github/workflows/lootbox.yml b/.github/workflows/lootbox.yml new file mode 100644 index 00000000..4d392f35 --- /dev/null +++ b/.github/workflows/lootbox.yml @@ -0,0 +1,46 @@ +name: Lootbox system tests + +on: + pull_request: + paths: + - "contracts/Lootbox*" + - "contracts/mock/**" + - "cli/enginecli/test_lootbox.py" + - "cli/enginecli/test_random_lootbox.py" + - "cli/enginecli/Lootbox*.py" + - "cli/enginecli/Mock*.py" + - "cli/enginecli/Diamond*" + - "cli/enginecli/OwnershipFacet.py" + - ".github/workflows/lootbox.yml" + branches: + - main +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16" + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - name: Install ganache + run: npm install -g ganache-cli + - name: Upgrade pip + env: + BROWNIE_LIB: 1 + run: pip install -U pip + - name: Install additional dev dependencies + run: | + pip install black moonworm + - name: Install dependencies for CLI + working-directory: cli/ + run: | + pip install -e . + - name: Run lootbox tests + working-directory: cli/ + run: bash test.sh enginecli.test_lootbox + - name: Run random lootbox tests + working-directory: cli/ + run: bash test.sh enginecli.test_random_lootbox diff --git a/.github/workflows/reentrancy-guard.yml b/.github/workflows/reentrancy-guard.yml new file mode 100644 index 00000000..10e9f14f --- /dev/null +++ b/.github/workflows/reentrancy-guard.yml @@ -0,0 +1,42 @@ +name: Diamond Reentrancy Guard tests + +on: + pull_request: + paths: + - "contracts/test/**" + - "contracts/diamond/security/DiamondReentrancyGuard.sol" + - "cli/enginecli/test_reentrancy_guard.py" + - "cli/enginecli/ExploitContract.py" + - "cli/enginecli/ReentrancyExploitable.py" + - "cli/enginecli/Diamond*" + - "cli/enginecli/OwnershipFacet.py" + - ".github/workflows/reentrancy-guard.yml" + branches: + - main +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16" + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - name: Install ganache + run: npm install -g ganache-cli + - name: Upgrade pip + env: + BROWNIE_LIB: 1 + run: pip install -U pip + - name: Install additional dev dependencies + run: | + pip install black moonworm + - name: Install dependencies for CLI + working-directory: cli/ + run: | + pip install -e . + - name: Run tests + working-directory: cli/ + run: bash test.sh enginecli.test_crafting diff --git a/.gitignore b/.gitignore index 9e570c2c..7020cb87 100644 --- a/.gitignore +++ b/.gitignore @@ -133,6 +133,8 @@ dmypy.json .secrets/ .vscode/ .engine/ +.engineapi/ +.enginecli/ prod.env test.env @@ -144,6 +146,9 @@ alembic.dev.ini alembic.prod.ini alembic.test.ini +scratch/ +outdir/ + #Node node_modules/ diff --git a/alembic/versions/782ac8fe23c8_add_resource_id_column.py b/alembic/versions/782ac8fe23c8_add_resource_id_column.py new file mode 100644 index 00000000..ab822abe --- /dev/null +++ b/alembic/versions/782ac8fe23c8_add_resource_id_column.py @@ -0,0 +1,38 @@ +"""add resource_id column + +Revision ID: 782ac8fe23c8 +Revises: 815ae0983ef1 +Create Date: 2022-11-10 13:47:49.486491 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "782ac8fe23c8" +down_revision = "815ae0983ef1" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "leaderboards", + sa.Column("resource_id", postgresql.UUID(as_uuid=True), nullable=True), + ) + op.create_index( + op.f("ix_leaderboards_resource_id"), + "leaderboards", + ["resource_id"], + unique=False, + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_leaderboards_resource_id"), table_name="leaderboards") + op.drop_column("leaderboards", "resource_id") + # ### end Alembic commands ### diff --git a/api/engineapi/actions.py b/api/engineapi/actions.py index 25e4cbd3..079148ba 100644 --- a/api/engineapi/actions.py +++ b/api/engineapi/actions.py @@ -1,16 +1,20 @@ from datetime import datetime +from collections import Counter from typing import List, Any, Optional, Dict import uuid import logging +from bugout.data import BugoutResource from eth_typing import Address from hexbytes import HexBytes +import requests # type: ignore from sqlalchemy.dialects.postgresql import insert from sqlalchemy.orm import Session from sqlalchemy import func, text, or_ from web3 import Web3 from web3.types import ChecksumAddress +from .data import Score from .contracts import Dropper_interface, ERC20_interface, Terminus_interface from .models import ( DropperClaimant, @@ -20,7 +24,13 @@ LeaderboardScores, ) from . import signatures -from .settings import BLOCKCHAIN_WEB3_PROVIDERS +from .settings import ( + BLOCKCHAIN_WEB3_PROVIDERS, + LEADERBOARD_RESOURCE_TYPE, + MOONSTREAM_APPLICATION_ID, + MOONSTREAM_ADMIN_ACCESS_TOKEN, + bugout_client as bc, +) class AuthorizationError(Exception): @@ -35,6 +45,21 @@ class DublicateClaimantError(Exception): pass +class DuplicateLeaderboardAddressError(Exception): + def __init__(self, message, duplicates): + super(DuplicateLeaderboardAddressError, self).__init__(message) + self.message = message + self.duplicates = duplicates + + +class LeaderboardIsEmpty(Exception): + pass + + +class LeaderboardDeleteScoresError(Exception): + pass + + BATCH_SIGNATURE_PAGE_SIZE = 500 logger = logging.getLogger(__name__) @@ -701,7 +726,7 @@ def get_drops( if active: query = query.filter(DropperClaim.active == active) - query = query.order_by(DropperClaim.created_at.asc()) + query = query.order_by(DropperClaim.created_at.desc()) if limit: query = query.limit(limit) @@ -976,13 +1001,14 @@ def get_leaderboard_positions( """ query = ( db_session.query( + LeaderboardScores.id, LeaderboardScores.address, LeaderboardScores.score, LeaderboardScores.points_data, func.rank().over(order_by=LeaderboardScores.score.desc()).label("rank"), ) .filter(LeaderboardScores.leaderboard_id == leaderboard_id) - .order_by(text("rank asc")) + .order_by(text("rank asc, id asc")) ) if limit: @@ -991,7 +1017,7 @@ def get_leaderboard_positions( if offset: query = query.offset(offset) - return query.all() + return query def get_qurtiles(db_session: Session, leaderboard_id): @@ -1010,6 +1036,9 @@ def get_qurtiles(db_session: Session, leaderboard_id): current_count = db_session.query(ranked_leaderboard).count() + if current_count == 0: + raise LeaderboardIsEmpty(f"Leaderboard {leaderboard_id} is empty") + index_75 = int(current_count / 4) index_50 = int(current_count / 2) @@ -1025,6 +1054,65 @@ def get_qurtiles(db_session: Session, leaderboard_id): return q1, q2, q3 +def get_ranks(db_session: Session, leaderboard_id): + """ + Get the leaderboard rank buckets(rank, size, score) + """ + query = db_session.query( + LeaderboardScores.id, + LeaderboardScores.address, + LeaderboardScores.score, + LeaderboardScores.points_data, + func.rank().over(order_by=LeaderboardScores.score.desc()).label("rank"), + ).filter(LeaderboardScores.leaderboard_id == leaderboard_id) + + ranked_leaderboard = query.cte(name="ranked_leaderboard") + + ranks = db_session.query( + ranked_leaderboard.c.rank, + func.count(ranked_leaderboard.c.id).label("size"), + ranked_leaderboard.c.score, + ).group_by(ranked_leaderboard.c.rank, ranked_leaderboard.c.score) + return ranks + + +def get_rank( + db_session: Session, + leaderboard_id: uuid.UUID, + rank: int, + limit: Optional[int] = None, + offset: Optional[int] = None, +): + """ + Get bucket in leaderboard by rank + """ + query = ( + db_session.query( + LeaderboardScores.id, + LeaderboardScores.address, + LeaderboardScores.score, + LeaderboardScores.points_data, + func.rank().over(order_by=LeaderboardScores.score.desc()).label("rank"), + ) + .filter(LeaderboardScores.leaderboard_id == leaderboard_id) + .order_by(text("rank asc, id asc")) + ) + + ranked_leaderboard = query.cte(name="ranked_leaderboard") + + positions = db_session.query(ranked_leaderboard).filter( + ranked_leaderboard.c.rank == rank + ) + + if limit: + positions = positions.limit(limit) + + if offset: + positions = positions.offset(offset) + + return positions + + def create_leaderboard(db_session: Session, title: str, description: str): """ Create a leaderboard @@ -1066,20 +1154,48 @@ def list_leaderboards(db_session: Session, limit: int, offset: int): return query.all() -def add_scores(db_session: Session, leaderboard_id, scores): +def add_scores( + db_session: Session, + leaderboard_id: uuid.UUID, + scores: List[Score], + overwrite: bool = False, + normalize_addresses: bool = True, +): """ Add scores to the leaderboard """ leaderboard_scores = [] + normalizer_fn = Web3.toChecksumAddress + if not normalize_addresses: + normalizer_fn = lambda x: x # type: ignore + + addresses = [score.address for score in scores] + + if len(addresses) != len(set(addresses)): + + duplicates = [key for key, value in Counter(addresses).items() if value > 1] + + raise DuplicateLeaderboardAddressError("Dublicated addresses", duplicates) + + if overwrite: + db_session.query(LeaderboardScores).filter( + LeaderboardScores.leaderboard_id == leaderboard_id + ).delete() + try: + db_session.commit() + except: + db_session.rollback() + raise LeaderboardDeleteScoresError("Error deleting leaderboard scores") + for score in scores: leaderboard_scores.append( { "leaderboard_id": leaderboard_id, - "address": score["address"], - "score": score["score"], - "points_data": score["points_data"], + "address": normalizer_fn(score.address), + "score": score.score, + "points_data": score.points_data, } ) @@ -1093,7 +1209,130 @@ def add_scores(db_session: Session, leaderboard_id, scores): updated_at=datetime.now(), ), ) - db_session.execute(result_stmt) - db_session.commit() + try: + db_session.execute(result_stmt) + db_session.commit() + except: + db_session.rollback() return leaderboard_scores + + +# leadrboard access actions + + +def create_leaderboard_resource( + leaderboard_id: uuid.UUID, + token: Optional[uuid.UUID] = None, +) -> BugoutResource: + + resource_data: Dict[str, Any] = { + "type": LEADERBOARD_RESOURCE_TYPE, + "leaderboard_id": leaderboard_id, + } + + if token is None: + token = MOONSTREAM_ADMIN_ACCESS_TOKEN + + resource = bc.create_resource( + token=MOONSTREAM_ADMIN_ACCESS_TOKEN, + application_id=MOONSTREAM_APPLICATION_ID, + resource_data=resource_data, + timeout=10, + ) + return resource + + +def assign_resource( + db_session: Session, + leaderboard_id: uuid.UUID, + resource_id: Optional[uuid.UUID] = None, +): + + """ + Assign a resource handler to a leaderboard + """ + + leaderboard = ( + db_session.query(Leaderboard).filter(Leaderboard.id == leaderboard_id).one() + ) + + if leaderboard.resource_id is not None: + + raise Exception("Leaderboard already has a resource") + + if resource_id is not None: + leaderboard.resource_id = resource_id + else: + # Create resource via admin token + + resource = create_leaderboard_resource( + leaderboard_id=leaderboard_id, + ) + + leaderboard.resource_id = resource.id + + db_session.commit() + db_session.flush() + + return leaderboard.resource_id + + +def list_leaderboards_resources( + db_session: Session, +): + + """ + List all leaderboards resources + """ + + query = db_session.query(Leaderboard.id, Leaderboard.title, Leaderboard.resource_id) + + return query.all() + + +def revoke_resource(db_session: Session, leaderboard_id: uuid.UUID): + + """ + Revoke a resource handler to a leaderboard + """ + + # TODO(ANDREY): Delete resource via admin token + + leaderboard = ( + db_session.query(Leaderboard).filter(Leaderboard.id == leaderboard_id).one() + ) + + if leaderboard.resource_id is None: + + raise Exception("Leaderboard does not have a resource") + + leaderboard.resource_id = None + + db_session.commit() + db_session.flush() + + return leaderboard.resource_id + + +def check_leaderboard_resource_permissions( + db_session: Session, leaderboard_id: uuid.UUID, token: uuid.UUID +): + """ + Check if the user has permissions to access the leaderboard + """ + leaderboard = ( + db_session.query(Leaderboard).filter(Leaderboard.id == leaderboard_id).one() + ) + + permission_url = f"{bc.brood_url}/resources/{leaderboard.resource_id}/holders" + headers = { + "Authorization": f"Bearer {token}", + } + # If user don't have at least read permission return 404 + result = requests.get(url=permission_url, headers=headers, timeout=10) + + if result.status_code == 200: + return True + + return False diff --git a/api/engineapi/cli.py b/api/engineapi/cli.py index ad1edfa3..8ea49195 100644 --- a/api/engineapi/cli.py +++ b/api/engineapi/cli.py @@ -316,23 +316,32 @@ def list_claimants_handler(args: argparse.Namespace) -> None: def add_scores_handler(args: argparse.Namespace) -> None: - scores = [] - + """ + Adding scores to leaderboard + """ with open(args.input_file, "r") as f: json_input = json.load(f) - with db.yield_db_session_ctx() as db_session: + try: + new_scores = [data.Score(**score) for score in json_input] + except Exception as err: + logger.error(f"Can't parse json input in score format") + logger.error(f"Invalid input: {err}") + return - try: - scores = actions.add_scores( - db_session=db_session, - leaderboard_id=args.leaderboard_id, - scores=json_input, - ) - except Exception as err: - logger.error(f"Unhandled /add_scores exception: {err}") - return + with db.yield_db_session_ctx() as db_session: + + try: + scores = actions.add_scores( + db_session=db_session, + leaderboard_id=args.leaderboard_id, + scores=new_scores, + overwrite=args.overwrite, + ) + except Exception as err: + logger.error(f"Unhandled /add_scores exception: {err}") + return def list_leaderboards_handler(args: argparse.Namespace) -> None: @@ -380,6 +389,61 @@ def create_leaderboard_handler(args: argparse.Namespace) -> None: # print(f"Amount of updated claimants: {len(claimant_signature)}") +def assign_resource_handler(args: argparse.Namespace) -> None: + + with db.yield_db_session_ctx() as db_session: + + try: + resource_id = actions.assign_resource( + db_session=db_session, + resource_id=args.resource_id, + leaderboard_id=args.leaderboard_id, + ) + logger.info( + f"leaderboard:{args.leaderboard_id} assign resource_id:{resource_id}" + ) + except Exception as err: + logger.error(f"Unhandled /assign_resource exception: {err}") + return + + +def list_resources_handler(args: argparse.Namespace) -> None: + + with db.yield_db_session_ctx() as db_session: + resources = actions.list_leaderboards_resources(db_session=db_session) + + logger.info(resources) + + +def revoke_resource_handler(args: argparse.Namespace) -> None: + with db.yield_db_session_ctx() as db_session: + try: + resource = actions.revoke_resource( + db_session=db_session, + leaderboard_id=args.leaderboard_id, + ) + logger.info( + f"leaderboard:{args.leaderboard_id} revoke resource current resource_id:{resource}" + ) + except Exception as err: + logger.error(f"Unhandled /revoke_resource exception: {err}") + return + + +def add_user_handler(args: argparse.Namespace) -> None: + """ + Add permission to resource cross bugout api. + """ + pass + + +def delete_user_handler(args: argparse.Namespace) -> None: + """ + Delete read access from resource cross bugout api. + """ + pass + + def main() -> None: parser = argparse.ArgumentParser( @@ -469,7 +533,7 @@ def main() -> None: parser_leaderboard = subparsers_engine_database.add_parser( "leaderboard", description="Leaderboard db commands" ) - parser_leaderboard.set_defaults(func=lambda _: parser_dropper.print_help()) + parser_leaderboard.set_defaults(func=lambda _: parser_leaderboard.print_help()) subparsers_leaderboard = parser_leaderboard.add_subparsers( description="Leaderboard db commands" @@ -523,8 +587,104 @@ def main() -> None: help="File with scores", ) + parser_leaderboard_score.add_argument("--overwrite", type=bool, default=True) + parser_leaderboard_score.set_defaults(func=add_scores_handler) + parser_leaderboard_permissions = subparsers_leaderboard.add_parser( + "permissions", description="Manage leaderboard permissions" + ) + + parser_leaderboard_permissions.set_defaults( + func=lambda _: parser_leaderboard_score.print_help() + ) + + subparsers_leaderboard_permissions = parser_leaderboard_permissions.add_subparsers( + description="Manage leaderboard permissions" + ) + + parser_leaderboard_resource_assign = subparsers_leaderboard_permissions.add_parser( + "assign", description="Assign resource to leaderboard" + ) + + parser_leaderboard_resource_assign.add_argument( + "--leaderboard-id", + type=str, + required=True, + help="Leaderboard id", + ) + + parser_leaderboard_resource_assign.add_argument( + "--resource-id", + type=UUID, + required=False, + help="Resource id", + ) + + parser_leaderboard_resource_assign.set_defaults(func=assign_resource_handler) + + parser_leaderboard_resource_revoke = subparsers_leaderboard_permissions.add_parser( + "revoke", description="Revoke resource from leaderboard" + ) + + parser_leaderboard_resource_revoke.add_argument( + "--leaderboard-id", + type=str, + required=True, + help="Leaderboard id", + ) + + parser_leaderboard_resource_revoke.set_defaults(func=revoke_resource_handler) + + parser_leaderboard_resource_list = subparsers_leaderboard_permissions.add_parser( + "list", description="List leaderboard resources and ids" + ) + + parser_leaderboard_resource_list.set_defaults(func=list_resources_handler) + + parser_leaderboard_resource_add_user = ( + subparsers_leaderboard_permissions.add_parser( + "add-user", description="Add to user write access to leaderboard" + ) + ) + parser_leaderboard_resource_add_user.add_argument( + "--leaderboard-id", + type=str, + required=True, + help="Leaderboard id", + ) + + parser_leaderboard_resource_add_user.add_argument( + "--user-id", + type=str, + required=True, + help="User id", + ) + + parser_leaderboard_resource_add_user.set_defaults(func=add_user_handler) + + parser_leaderboard_resource_remove_user = ( + subparsers_leaderboard_permissions.add_parser( + "remove-user", description="Delete write access to leaderboard from user" + ) + ) + + parser_leaderboard_resource_remove_user.add_argument( + "--leaderboard-id", + type=str, + required=True, + help="Leaderboard id", + ) + + parser_leaderboard_resource_remove_user.add_argument( + "--user-id", + type=str, + required=True, + help="User id", + ) + + parser_leaderboard_resource_remove_user.set_defaults(func=delete_user_handler) + parser_dropper = subparsers_engine_database.add_parser( "dropper", description="Dropper db commands" ) diff --git a/api/engineapi/data.py b/api/engineapi/data.py index d68d34b9..d3fb117a 100644 --- a/api/engineapi/data.py +++ b/api/engineapi/data.py @@ -184,3 +184,22 @@ class QuartilesResponse(BaseModel): class CountAddressesResponse(BaseModel): count: int = Field(default_factory=int) + + +class Score(BaseModel): + address: str + score: int + points_data: Dict[str, Any] + + +class LeaderboardPosition(BaseModel): + address: str + rank: int + score: int + points_data: Dict[str, Any] + + +class RanksResponse(BaseModel): + rank: int + score: int + size: int diff --git a/api/engineapi/middleware.py b/api/engineapi/middleware.py index 651cf481..6c3f99b8 100644 --- a/api/engineapi/middleware.py +++ b/api/engineapi/middleware.py @@ -12,7 +12,7 @@ MoonstreamAuthorizationVerificationError, verify, ) - +from .settings import bugout_client as bc, MOONSTREAM_APPLICATION_ID logger = logging.getLogger(__name__) @@ -102,3 +102,38 @@ def __init__( if internal_error is not None: print(internal_error) # reporter.error_report(internal_error) + + +class ExtractBearerTokenMiddleware(BaseHTTPMiddleware): + """ + Checks the authorization header on the request and extract token. + """ + + def __init__(self, app, whitelist: Optional[Dict[str, str]] = None): + self.whitelist: Dict[str, str] = {} + if whitelist is not None: + self.whitelist = whitelist + super().__init__(app) + + async def dispatch( + self, request: Request, call_next: Callable[[Request], Awaitable[Response]] + ): + # Filter out endpoints with proper method to work without Bearer token (as create_user, login, etc) + path = request.url.path.rstrip("/") + method = request.method + if path in self.whitelist.keys() and self.whitelist[path] == method: + return await call_next(request) + + authorization_header = request.headers.get("authorization") + if authorization_header is None: + return Response( + status_code=403, content="No authorization header passed with request" + ) + authorization_header_components = authorization_header.split() + if len(authorization_header_components) != 2: + return Response(status_code=403, content="Wrong authorization header") + user_token: str = authorization_header_components[-1] + + request.state.token = user_token + + return await call_next(request) diff --git a/api/engineapi/models.py b/api/engineapi/models.py index fd9ca2f5..06cba5fa 100644 --- a/api/engineapi/models.py +++ b/api/engineapi/models.py @@ -168,6 +168,7 @@ class Leaderboard(Base): # type: ignore ) title = Column(VARCHAR(128), nullable=False) description = Column(String, nullable=True) + resource_id = Column(UUID(as_uuid=True), nullable=True, index=True) created_at = Column( DateTime(timezone=True), server_default=utcnow(), nullable=False ) diff --git a/api/engineapi/routes/leaderboard.py b/api/engineapi/routes/leaderboard.py index 84f63e1c..c0f9529f 100644 --- a/api/engineapi/routes/leaderboard.py +++ b/api/engineapi/routes/leaderboard.py @@ -5,14 +5,17 @@ from uuid import UUID from web3 import Web3 -from fastapi import FastAPI, Request, Depends +from fastapi import FastAPI, Request, Depends, Response from fastapi.middleware.cors import CORSMiddleware from sqlalchemy.orm import Session +from sqlalchemy.orm.exc import NoResultFound +from typing import List, Optional from .. import actions from .. import data from .. import db -from ..settings import DOCS_TARGET_PATH +from ..middleware import ExtractBearerTokenMiddleware, EngineHTTPException +from ..settings import DOCS_TARGET_PATH, bugout_client as bc from ..version import VERSION logger = logging.getLogger(__name__) @@ -23,6 +26,15 @@ ] +leaderboad_whitelist = { + "/leaderboard/quartiles": "GET", + "/leaderboard/count/addresses": "GET", + "/leaderboard/position": "GET", + "/leaderboard": "GET", + "/leaderboard/rank": "GET", + "/leaderboard/ranks": "GET", +} + app = FastAPI( title=f"Moonstream Engine leaderboard API", description="Moonstream Engine leaderboard API endpoints.", @@ -33,6 +45,9 @@ redoc_url=f"/{DOCS_TARGET_PATH}", ) + +app.add_middleware(ExtractBearerTokenMiddleware, whitelist=leaderboad_whitelist) + app.add_middleware( CORSMiddleware, allow_origins="*", @@ -44,7 +59,6 @@ @app.get("/count/addresses") async def count_addresses( - request: Request, leaderboard_id: UUID, db_session: Session = Depends(db.yield_db_session), ): @@ -53,6 +67,18 @@ async def count_addresses( Returns the number of addresses in the leaderboard. """ + ### Check if leaderboard exists + try: + actions.get_leaderboard_by_id(db_session, leaderboard_id) + except NoResultFound as e: + raise EngineHTTPException( + status_code=404, + detail="Leaderboard not found.", + ) + except Exception as e: + logger.error(f"Error while getting leaderboard: {e}") + raise EngineHTTPException(status_code=500, detail="Internal server error") + count = actions.get_leaderboard_total_count(db_session, leaderboard_id) return data.CountAddressesResponse(count=count) @@ -60,18 +86,33 @@ async def count_addresses( @app.get("/quartiles") async def quartiles( - request: Request, leaderboard_id: UUID, db_session: Session = Depends(db.yield_db_session), ): """ - Returns the quartiles of the leaderboard. - """ + ### Check if leaderboard exists + try: + actions.get_leaderboard_by_id(db_session, leaderboard_id) + except NoResultFound as e: + raise EngineHTTPException( + status_code=404, + detail="Leaderboard not found.", + ) + except Exception as e: + logger.error(f"Error while getting leaderboard: {e}") + raise EngineHTTPException(status_code=500, detail="Internal server error") + + try: + q1, q2, q3 = actions.get_qurtiles(db_session, leaderboard_id) - q1, q2, q3 = actions.get_qurtiles(db_session, leaderboard_id) + except actions.LeaderboardIsEmpty: + return Response(status_code=204) + except Exception as e: + logger.error(f"Error while getting quartiles: {e}") + raise EngineHTTPException(status_code=500, detail="Internal server error") return data.QuartilesResponse( percentile_25={"address": q1[0], "score": q1[1], "rank": q1[2]}, @@ -82,12 +123,12 @@ async def quartiles( @app.get("/position") async def position( - request: Request, leaderboard_id: UUID, address: str, window_size: int = 1, limit: int = 10, offset: int = 0, + normalize_addresses: bool = True, db_session: Session = Depends(db.yield_db_session), ): @@ -96,7 +137,20 @@ async def position( With given window size. """ - address = Web3.toChecksumAddress(address) + ### Check if leaderboard exists + try: + actions.get_leaderboard_by_id(db_session, leaderboard_id) + except NoResultFound as e: + raise EngineHTTPException( + status_code=404, + detail="Leaderboard not found.", + ) + except Exception as e: + logger.error(f"Error while getting leaderboard: {e}") + raise EngineHTTPException(status_code=500, detail="Internal server error") + + if normalize_addresses: + address = Web3.toChecksumAddress(address) positions = actions.get_position( db_session, leaderboard_id, address, window_size, limit, offset @@ -105,21 +159,178 @@ async def position( return positions +@app.get("") @app.get("/") async def leaderboard( - request: Request, leaderboard_id: UUID, limit: int = 10, offset: int = 0, db_session: Session = Depends(db.yield_db_session), -): +) -> List[data.LeaderboardPosition]: """ - Returns the leaderboard. + Returns the leaderboard positions. """ - leaderboard = actions.get_leaderboard_positions( + ### Check if leaderboard exists + try: + actions.get_leaderboard_by_id(db_session, leaderboard_id) + except NoResultFound as e: + raise EngineHTTPException( + status_code=404, + detail="Leaderboard not found.", + ) + except Exception as e: + logger.error(f"Error while getting leaderboard: {e}") + raise EngineHTTPException(status_code=500, detail="Internal server error") + + leaderboard_positions = actions.get_leaderboard_positions( db_session, leaderboard_id, limit, offset ) + result = [ + data.LeaderboardPosition( + address=position.address, + score=position.score, + rank=position.rank, + points_data=position.points_data, + ) + for position in leaderboard_positions + ] + + return result + + +@app.get("/rank") +async def rank( + leaderboard_id: UUID, + rank: int = 1, + limit: Optional[int] = None, + offset: Optional[int] = None, + db_session: Session = Depends(db.yield_db_session), +) -> List[data.LeaderboardPosition]: + + """ + Returns the leaderboard scores for the given rank. + """ + + ### Check if leaderboard exists + try: + actions.get_leaderboard_by_id(db_session, leaderboard_id) + except NoResultFound as e: + raise EngineHTTPException( + status_code=404, + detail="Leaderboard not found.", + ) + except Exception as e: + logger.error(f"Error while getting leaderboard: {e}") + raise EngineHTTPException(status_code=500, detail="Internal server error") + + leaderboard_rank = actions.get_rank( + db_session, leaderboard_id, rank, limit=limit, offset=offset + ) + results = [ + data.LeaderboardPosition( + address=rank_position.address, + score=rank_position.score, + rank=rank_position.rank, + points_data=rank_position.points_data, + ) + for rank_position in leaderboard_rank + ] + return results + + +@app.get("/ranks") +async def ranks( + leaderboard_id: UUID, db_session: Session = Depends(db.yield_db_session) +) -> List[data.RanksResponse]: + + """ + Returns the leaderboard rank buckets overview with score and size of bucket. + """ + + ### Check if leaderboard exists + try: + actions.get_leaderboard_by_id(db_session, leaderboard_id) + except NoResultFound as e: + raise EngineHTTPException( + status_code=404, + detail="Leaderboard not found.", + ) + except Exception as e: + logger.error(f"Error while getting leaderboard: {e}") + raise EngineHTTPException(status_code=500, detail="Internal server error") + + ranks = actions.get_ranks(db_session, leaderboard_id) + results = [ + data.RanksResponse( + score=rank.score, + rank=rank.rank, + size=rank.size, + ) + for rank in ranks + ] + return results + + +@app.put("/{leaderboard_id}/scores") +async def leaderboard( + request: Request, + leaderboard_id: UUID, + scores: List[data.Score], + overwrite: bool = False, + normalize_addresses: bool = True, + db_session: Session = Depends(db.yield_db_session), +): + + """ + Put the leaderboard to the database. + """ + + access = actions.check_leaderboard_resource_permissions( + db_session=db_session, + leaderboard_id=leaderboard_id, + token=request.state.token, + ) + + if not access: + raise EngineHTTPException( + status_code=403, detail="You don't have access to this leaderboard." + ) + + ### Check if leaderboard exists + try: + actions.get_leaderboard_by_id(db_session, leaderboard_id) + except NoResultFound as e: + raise EngineHTTPException( + status_code=404, + detail="Leaderboard not found.", + ) + except Exception as e: + logger.error(f"Error while getting leaderboard: {e}") + raise EngineHTTPException(status_code=500, detail="Internal server error") + + try: + leaderboard_points = actions.add_scores( + db_session=db_session, + leaderboard_id=leaderboard_id, + scores=scores, + overwrite=overwrite, + normalize_addresses=normalize_addresses, + ) + except actions.DuplicateLeaderboardAddressError as e: + raise EngineHTTPException( + status_code=409, + detail=f"Duplicates in push to database is disallowed.\n List of duplicates:{e.duplicates}.\n Please handle duplicates manualy.", + ) + except actions.LeaderboardDeleteScoresError as e: + logger.error(f"Delete scores failed with error: {e}") + raise EngineHTTPException( + status_code=500, + detail=f"Delete scores failed.", + ) + except Exception as e: + logger.error(f"Score update failed with error: {e}") + raise EngineHTTPException(status_code=500, detail="Score update failed.") - return leaderboard + return leaderboard_points diff --git a/api/engineapi/settings.py b/api/engineapi/settings.py index 3027032f..ec6ba4fb 100644 --- a/api/engineapi/settings.py +++ b/api/engineapi/settings.py @@ -2,6 +2,14 @@ from web3 import Web3, HTTPProvider from web3.middleware import geth_poa_middleware +from bugout.app import Bugout + +# Bugout +BUGOUT_BROOD_URL = os.environ.get("BUGOUT_BROOD_URL", "https://auth.bugout.dev") +BUGOUT_SPIRE_URL = os.environ.get("BUGOUT_SPIRE_URL", "https://spire.bugout.dev") + + +bugout_client = Bugout(brood_api_url=BUGOUT_BROOD_URL, spire_api_url=BUGOUT_SPIRE_URL) # Origin @@ -61,6 +69,13 @@ ) MOONSTREAM_XDAI_WEB3_PROVIDER_URI = os.environ.get("MOONSTREAM_XDAI_WEB3_PROVIDER_URI") +# TODO(kompotkot): Leave a comment here explaining templated *_WEB3_PROVIDER_URI when we set +# NODEBALANCER_ACCESS_ID +ETHEREUM_PROVIDER_URI = MOONSTREAM_ETHEREUM_WEB3_PROVIDER_URI +MUMBAI_PROVIDER_URI = MOONSTREAM_MUMBAI_WEB3_PROVIDER_URI +POLYGON_PROVIDER_URI = MOONSTREAM_POLYGON_WEB3_PROVIDER_URI +XDAI_PROVIDER_URI = MOONSTREAM_XDAI_WEB3_PROVIDER_URI + NODEBALANCER_ACCESS_ID = os.environ.get("ENGINE_NODEBALANCER_ACCESS_ID") if NODEBALANCER_ACCESS_ID is not None: NODEBALANCER_URI_TEMPLATE = "{}?access_id={}&data_source=blockchain" @@ -139,3 +154,13 @@ raise ValueError( f"ENGINE_DB_POOL_RECYCLE_SECONDS must be an integer: {ENGINE_DB_POOL_RECYCLE_SECONDS_RAW}" ) + +MOONSTREAM_APPLICATION_ID = os.environ.get("MOONSTREAM_APPLICATION_ID", "") +if MOONSTREAM_APPLICATION_ID == "": + raise ValueError("MOONSTREAM_APPLICATION_ID environment variable must be set") + +LEADERBOARD_RESOURCE_TYPE = "leaderboard" + +MOONSTREAM_ADMIN_ACCESS_TOKEN = os.environ.get("MOONSTREAM_ADMIN_ACCESS_TOKEN", "") +if MOONSTREAM_ADMIN_ACCESS_TOKEN == "": + raise ValueError("MOONSTREAM_ADMIN_ACCESS_TOKEN environment variable must be set") diff --git a/api/regular-scores.json b/api/regular-scores.json new file mode 100644 index 00000000..a6365536 --- /dev/null +++ b/api/regular-scores.json @@ -0,0 +1,10 @@ +[ + { + "address": "0x0000000000000000000000000000000000000001", + "score": 200000000, + "points_data": { + "something": 100000000, + "something_else": 100000000 + } + } +] diff --git a/api/requirements.txt b/api/requirements.txt index 446eba52..63b65483 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,13 +1,17 @@ -aiohttp==3.8.1 +aiohttp==3.8.3 aiosignal==1.2.0 -anyio==3.6.1 +alembic==1.8.1 +anyio==3.6.2 async-timeout==4.0.2 attrs==22.1.0 base58==2.1.1 bitarray==2.6.0 -boto3==1.24.67 -botocore==1.27.67 -certifi==2022.6.15 +black==22.10.0 +boto3==1.24.93 +botocore==1.27.93 +Brownie==0.5.1 +bugout==0.2.2 +certifi==2022.9.24 charset-normalizer==2.1.1 click==8.1.3 cytoolz==0.12.0 @@ -21,24 +25,32 @@ eth-keys==0.3.4 eth-rlp==0.2.1 eth-typing==2.3.0 eth-utils==1.9.5 -fastapi==0.82.0 +fastapi==0.85.1 frozenlist==1.3.1 -greenlet==1.1.3 -h11==0.13.0 +greenlet==1.1.3.post0 +h11==0.14.0 hexbytes==0.2.3 -idna==3.3 -importlib-resources==5.9.0 +idna==3.4 +importlib-metadata==5.0.0 +importlib-resources==5.10.0 ipfshttpclient==0.8.0a2 +isort==5.10.1 jmespath==1.0.1 -jsonschema==4.15.0 +jsonschema==4.16.0 lru-dict==1.1.8 +Mako==1.2.3 +MarkupSafe==2.1.1 multiaddr==0.0.9 multidict==6.0.2 +mypy==0.982 +mypy-extensions==0.4.3 netaddr==0.8.0 parsimonious==0.8.1 +pathspec==0.10.1 pkgutil_resolve_name==1.3.10 -protobuf==3.20.1 -psycopg2-binary==2.9.3 +platformdirs==2.5.2 +protobuf==3.19.5 +psycopg2-binary==2.9.4 pycryptodome==3.15.0 pydantic==1.10.2 pyrsistent==0.18.1 @@ -48,16 +60,17 @@ rlp==2.0.1 s3transfer==0.6.0 six==1.16.0 sniffio==1.3.0 -SQLAlchemy==1.4.41 -starlette==0.19.1 -tabulate==0.8.10 +SQLAlchemy==1.4.42 +starlette==0.20.4 +tabulate==0.9.0 +tomli==2.0.1 toolz==0.12.0 tqdm==4.64.1 -typing_extensions==4.3.0 +typing_extensions==4.4.0 urllib3==1.26.12 uvicorn==0.18.3 varint==1.0.2 -web3==5.30.0 +web3==5.31.1 websockets==9.1 yarl==1.8.1 -zipp==3.8.1 +zipp==3.9.0 diff --git a/api/setup.py b/api/setup.py index 0c2eab52..655fd0d5 100644 --- a/api/setup.py +++ b/api/setup.py @@ -13,6 +13,7 @@ packages=find_packages(), install_requires=[ "boto3", + "bugout>=0.2.2", "eip712", "eth-typing>=2.3.0", "fastapi", diff --git a/api/token-id-scores.json b/api/token-id-scores.json new file mode 100644 index 00000000..eca6d698 --- /dev/null +++ b/api/token-id-scores.json @@ -0,0 +1,10 @@ +[ + { + "address": "1", + "score": 200000000, + "points_data": { + "something": 100000000, + "something_else": 100000000 + } + } +] diff --git a/apps/play/.eslintrc.json b/apps/play/.eslintrc.json index 72e82297..df5a25ed 100644 --- a/apps/play/.eslintrc.json +++ b/apps/play/.eslintrc.json @@ -104,6 +104,15 @@ ], "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", - "prettier/prettier": "warn" + "prettier/prettier": "warn", + "react/no-unknown-property": [ + 2, + { + "ignore": [ + "jsx", + "global" + ] + } + ] } } diff --git a/apps/play/components/GroupImage.js b/apps/play/components/GroupImage.js new file mode 100644 index 00000000..bdf4abc9 --- /dev/null +++ b/apps/play/components/GroupImage.js @@ -0,0 +1,39 @@ +import { Flex, Image } from "@chakra-ui/react"; + +const GroupImage = ({ shadowcorns, metadata }) => { + if (!metadata) return <>; + return ( + + + {shadowcorns.map((item, idx) => { + return ( + metadata.has(item.address) && ( + {item.address} + ) + ); + })} + + + ); +}; + +export default GroupImage; diff --git a/apps/play/components/LeaderboardGroup.js b/apps/play/components/LeaderboardGroup.js new file mode 100644 index 00000000..0c1de5b4 --- /dev/null +++ b/apps/play/components/LeaderboardGroup.js @@ -0,0 +1,47 @@ +import { AccordionPanel, GridItem, Flex } from "@chakra-ui/react"; +import ShadowcornRow from "./ShadocornRow"; +import LeaderboardRank from "./LeaderboardRank"; + +const LeaderboardGroup = ({ group, shadowcorns }) => { + return ( + + + {group.records.map((item) => { + return ( + + + + + + + + + {item.score} + + + ); + })} + + + ); +}; + +export default LeaderboardGroup; diff --git a/apps/play/components/LeaderboardGroupHeader.js b/apps/play/components/LeaderboardGroupHeader.js new file mode 100644 index 00000000..4109ab4c --- /dev/null +++ b/apps/play/components/LeaderboardGroupHeader.js @@ -0,0 +1,61 @@ +import { + AccordionButton, + AccordionIcon, + GridItem, + Flex, + Text, + Spacer, +} from "@chakra-ui/react"; +import LeaderboardRank from "./LeaderboardRank"; + +import GroupImage from "./GroupImage"; + +const LeaderboardGroupHeader = ({ group, metadata }) => { + return ( + + + + + + + + 2 + ? group.records.slice(0, 3) + : group.records + } + metadata={metadata} + /> + {`${group.records.length} Shadowcorns`} + + + + + + {group.score} + + + + + + ); +}; + +export default LeaderboardGroupHeader; diff --git a/apps/play/components/LeaderboardRank.js b/apps/play/components/LeaderboardRank.js new file mode 100644 index 00000000..d2b88c8b --- /dev/null +++ b/apps/play/components/LeaderboardRank.js @@ -0,0 +1,30 @@ +import { Flex } from "@chakra-ui/react"; + +const LeaderboardRank = ({ rank }) => { + return ( + + {rank} + + ); +}; + +export default LeaderboardRank; diff --git a/apps/play/components/ShadocornRow.js b/apps/play/components/ShadocornRow.js new file mode 100644 index 00000000..c42be7d1 --- /dev/null +++ b/apps/play/components/ShadocornRow.js @@ -0,0 +1,53 @@ +import { Text, Icon, Image, Link, Box, Flex } from "@chakra-ui/react"; +import { FiExternalLink } from "react-icons/fi"; +import { SHADOWCORN_CONTRACT_ADDRESS } from "moonstream-components/src/core/cu/constants"; + +const buildOpenseaLink = (tokenId) => { + return `https://opensea.io/assets/matic/${SHADOWCORN_CONTRACT_ADDRESS}/${tokenId}`; +}; + +const ShadowcornRow = ({ shadowcorn, tokenId }) => { + return ( + + + {shadowcorn && ( + sc + )} + {shadowcorn?.name && ( + <> + {`${shadowcorn.name} (${tokenId})`} + + )} + {!shadowcorn?.name && ( + + {tokenId} + + )} + + + + + ); +}; + +export default ShadowcornRow; diff --git a/apps/play/games/GoFPABI.json b/apps/play/games/GoFPABI.json new file mode 100644 index 00000000..deaca362 --- /dev/null +++ b/apps/play/games/GoFPABI.json @@ -0,0 +1,878 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "stage", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "path", + "type": "uint256" + } + ], + "name": "PathChosen", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "stage", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "path", + "type": "uint256" + } + ], + "name": "PathRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isActive", + "type": "bool" + } + ], + "name": "SessionActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isChoosingActive", + "type": "bool" + } + ], + "name": "SessionChoosingActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "playerTokenAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "paymentTokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paymentAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "uri", + "type": "string" + }, + { + "indexed": false, + "internalType": "bool", + "name": "active", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isForgiving", + "type": "bool" + } + ], + "name": "SessionCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "uri", + "type": "string" + } + ], + "name": "SessionUriChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "stage", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "terminusAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "terminusPoolId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "rewardAmount", + "type": "uint256" + } + ], + "name": "StageRewardChanged", + "type": "event" + }, + { + "inputs": [], + "name": "adminTerminusInfo", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "tokenIds", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "paths", + "type": "uint256[]" + } + ], + "name": "chooseCurrentStagePaths", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "playerTokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "paymentTokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "paymentAmount", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isActive", + "type": "bool" + }, + { + "internalType": "string", + "name": "uri", + "type": "string" + }, + { + "internalType": "uint256[]", + "name": "stages", + "type": "uint256[]" + }, + { + "internalType": "bool", + "name": "isForgiving", + "type": "bool" + } + ], + "name": "createSession", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stage", + "type": "uint256" + } + ], + "name": "getCorrectPathForStage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getCurrentStage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stage", + "type": "uint256" + } + ], + "name": "getPathChoice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getSession", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "playerTokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "paymentTokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "paymentAmount", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isActive", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isChoosingActive", + "type": "bool" + }, + { + "internalType": "string", + "name": "uri", + "type": "string" + }, + { + "internalType": "uint256[]", + "name": "stages", + "type": "uint256[]" + }, + { + "internalType": "bool", + "name": "isForgiving", + "type": "bool" + } + ], + "internalType": "struct Session", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getSessionTokenStakeGuard", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stage", + "type": "uint256" + } + ], + "name": "getStageReward", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "terminusAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "terminusPoolId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rewardAmount", + "type": "uint256" + } + ], + "internalType": "struct StageReward", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "nftAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getStakedTokenInfo", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "adminTerminusAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "adminTerminusPoolID", + "type": "uint256" + } + ], + "name": "init", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "numSessions", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "staker", + "type": "address" + } + ], + "name": "numTokensStakedIntoSession", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155BatchReceived", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC721Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stage", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "path", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "setIsChoosingActive", + "type": "bool" + } + ], + "name": "setCorrectPathForStage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isActive", + "type": "bool" + } + ], + "name": "setSessionActive", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isChoosingActive", + "type": "bool" + } + ], + "name": "setSessionChoosingActive", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "uri", + "type": "string" + } + ], + "name": "setSessionUri", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "stages", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "terminusAddresses", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "terminusPoolIds", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "rewardAmounts", + "type": "uint256[]" + } + ], + "name": "setStageRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "tokenIds", + "type": "uint256[]" + } + ], + "name": "stakeTokensIntoSession", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "staker", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenOfStakerInSessionByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "tokenIds", + "type": "uint256[]" + } + ], + "name": "unstakeTokensFromSession", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/apps/play/games/cu/GameBankABI.js b/apps/play/games/cu/GameBankABI.js new file mode 100644 index 00000000..a1a45080 --- /dev/null +++ b/apps/play/games/cu/GameBankABI.js @@ -0,0 +1,5 @@ +"use strict"; +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +exports.__esModule = true; diff --git a/apps/play/games/cu/Multicall2.json b/apps/play/games/cu/Multicall2.json new file mode 100644 index 00000000..4f1ec501 --- /dev/null +++ b/apps/play/games/cu/Multicall2.json @@ -0,0 +1,313 @@ + [ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall2.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "returnData", + "type": "bytes[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall2.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "blockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall2.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getBlockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockCoinbase", + "outputs": [ + { + "internalType": "address", + "name": "coinbase", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockDifficulty", + "outputs": [ + { + "internalType": "uint256", + "name": "difficulty", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockGasLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "gaslimit", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "getEthBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastBlockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "requireSuccess", + "type": "bool" + }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall2.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryAggregate", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall2.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "requireSuccess", + "type": "bool" + }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall2.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryBlockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall2.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ] diff --git a/apps/play/games/cu/Multicall2.ts b/apps/play/games/cu/Multicall2.ts new file mode 100644 index 00000000..0ff9979b --- /dev/null +++ b/apps/play/games/cu/Multicall2.ts @@ -0,0 +1,90 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import BN from "bn.js"; +import { ContractOptions } from "web3-eth-contract"; +import { EventLog } from "web3-core"; +import { EventEmitter } from "events"; +import { + Callback, + PayableTransactionObject, + NonPayableTransactionObject, + BlockType, + ContractEventLog, + BaseContract, +} from "./types"; + +export interface EventOptions { + filter?: object; + fromBlock?: BlockType; + topics?: string[]; +} + +export interface Multicall2 extends BaseContract { + constructor( + jsonInterface: any[], + address?: string, + options?: ContractOptions + ): Multicall2; + clone(): Multicall2; + methods: { + aggregate( + calls: [string, string | number[]][] + ): NonPayableTransactionObject<{ + blockNumber: string; + returnData: string[]; + 0: string; + 1: string[]; + }>; + + blockAndAggregate( + calls: [string, string | number[]][] + ): NonPayableTransactionObject<{ + blockNumber: string; + blockHash: string; + returnData: [boolean, string][]; + 0: string; + 1: string; + 2: [boolean, string][]; + }>; + + getBlockHash( + blockNumber: number | string | BN + ): NonPayableTransactionObject; + + getBlockNumber(): NonPayableTransactionObject; + + getCurrentBlockCoinbase(): NonPayableTransactionObject; + + getCurrentBlockDifficulty(): NonPayableTransactionObject; + + getCurrentBlockGasLimit(): NonPayableTransactionObject; + + getCurrentBlockTimestamp(): NonPayableTransactionObject; + + getEthBalance(addr: string): NonPayableTransactionObject; + + getLastBlockHash(): NonPayableTransactionObject; + + tryAggregate( + requireSuccess: boolean, + calls: [string, string | number[]][] + ): NonPayableTransactionObject<[boolean, string][]>; + + tryBlockAndAggregate( + requireSuccess: boolean, + calls: [string, string | number[]][] + ): NonPayableTransactionObject<{ + blockNumber: string; + blockHash: string; + returnData: [boolean, string][]; + 0: string; + 1: string; + 2: [boolean, string][]; + }>; + }; + events: { + allEvents(options?: EventOptions, cb?: Callback): EventEmitter; + }; +} diff --git a/apps/play/games/cu/StashABI.js b/apps/play/games/cu/StashABI.js new file mode 100644 index 00000000..a1a45080 --- /dev/null +++ b/apps/play/games/cu/StashABI.js @@ -0,0 +1,5 @@ +"use strict"; +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +exports.__esModule = true; diff --git a/apps/play/games/cu/types.js b/apps/play/games/cu/types.js new file mode 100644 index 00000000..0e345787 --- /dev/null +++ b/apps/play/games/cu/types.js @@ -0,0 +1,2 @@ +"use strict"; +exports.__esModule = true; diff --git a/apps/play/package.json b/apps/play/package.json index a031f3b4..cc0e98f1 100644 --- a/apps/play/package.json +++ b/apps/play/package.json @@ -12,7 +12,11 @@ "generate_classes_cu": "typechain --target=web3-v1 --out-dir games/cu 'games/cu/*.json'" }, "dependencies": { - "moonstream-components": "1.0.0" + "@nivo/bar": "^0.80.0", + "@nivo/core": "^0.80.0", + "@nivo/line": "^0.80.0", + "moonstream-components": "1.0.0", + "yarn": "^1.22.19" }, "devDependencies": { "@babel/core": "^7.14.3", diff --git a/apps/play/pages/_app.js b/apps/play/pages/_app.js index a4ee000c..91248099 100644 --- a/apps/play/pages/_app.js +++ b/apps/play/pages/_app.js @@ -15,14 +15,14 @@ const AppContext = dynamic(() => import("../src/AppContext"), { ssr: false, }); const DefaultLayout = dynamic( - () => import("moonstream-components/src/layouts"), + () => import("moonstream-components/src/layoutsForPlay"), { ssr: false, } ); import { useRouter } from "next/router"; import NProgress from "nprogress"; -import { WHITE_LOGO_W_TEXT_URL } from "../src/constants"; +import { PRIMARY_MOON_LOGO_URL } from "../src/constants"; export default function CachingApp({ Component, pageProps }) { const [queryClient] = useState(new QueryClient()); @@ -61,7 +61,7 @@ export default function CachingApp({ Component, pageProps }) { Component.getLayout || ((page) => {page}); const headLinks = [ - { rel: "preload", as: "image", href: WHITE_LOGO_W_TEXT_URL }, + { rel: "preload", as: "image", href: PRIMARY_MOON_LOGO_URL }, ]; pageProps.preloads && headLinks.push(...pageProps.preloads); return ( diff --git a/apps/play/pages/_document.js b/apps/play/pages/_document.js index 5751eb5b..34408f32 100644 --- a/apps/play/pages/_document.js +++ b/apps/play/pages/_document.js @@ -33,6 +33,15 @@ export default class MyDocument extends Document { + + {/* { return ( <> diff --git a/apps/play/pages/drops/details.js b/apps/play/pages/drops/details.js index 250fb04f..d19671da 100644 --- a/apps/play/pages/drops/details.js +++ b/apps/play/pages/drops/details.js @@ -1,12 +1,12 @@ import React, { useContext } from "react"; import { Flex, Spinner } from "@chakra-ui/react"; -import Paginator from "moonstream-components/src/components/Paginator"; -import ClaimCard from "moonstream-components/src/components/ClaimCard"; +import Paginator from "moonstream-components/src/components/PaginatorPlay"; +import ClaimCard from "moonstream-components/src/components/ClaimCardPlay"; import usePlayerClaims from "moonstream-components/src/core/hooks/dropper/useClaims"; import Web3Context from "moonstream-components/src/core/providers/Web3Provider/context"; import { useRouter } from "moonstream-components/src/core/hooks"; -import { getLayout } from "moonstream-components/src/layouts/EngineLayout"; +import { getLayout } from "moonstream-components/src/layoutsForPlay/EngineLayout"; const DropDetails = () => { const router = useRouter(); const web3Provider = useContext(Web3Context); @@ -35,7 +35,7 @@ const DropDetails = () => { return ( { @@ -68,15 +67,16 @@ const Drops = () => { ) .map((contract) => { return ( - - + + - + ); diff --git a/apps/play/pages/entry-point.js b/apps/play/pages/entry-point.js index 300fedf8..7b174b0c 100644 --- a/apps/play/pages/entry-point.js +++ b/apps/play/pages/entry-point.js @@ -2,7 +2,7 @@ import { useRouter } from "next/router"; import { useLayoutEffect } from "react"; -import { getLayout } from "../../../packages/moonstream-components/src/layouts/EntryPointLayout"; +import { getLayout } from "../../../packages/moonstream-components/src/layoutsForPlay/EntryPointLayout"; const EntryPoint = () => { const router = useRouter(); diff --git a/apps/play/pages/games/cryptoUnicorns.tsx b/apps/play/pages/games/CryptoUnicorns/index.tsx similarity index 71% rename from apps/play/pages/games/cryptoUnicorns.tsx rename to apps/play/pages/games/CryptoUnicorns/index.tsx index f7fd7312..14a19001 100644 --- a/apps/play/pages/games/cryptoUnicorns.tsx +++ b/apps/play/pages/games/CryptoUnicorns/index.tsx @@ -1,5 +1,5 @@ import React, { useContext, useEffect } from "react"; -import { getLayout } from "moonstream-components/src/layouts/EngineLayout"; +import { getLayout } from "moonstream-components/src/layoutsForPlay/EngineLayout"; import { Flex, Center, @@ -18,30 +18,30 @@ import { FormErrorMessage, Button, HStack, - Grid, } from "@chakra-ui/react"; -import { CloseIcon } from "@chakra-ui/icons"; -const StashABI = require("../../games/cu/StashABI.json"); -import { StashABI as StashABIType } from "../../games/cu/StashABI"; -const GameBankABI = require("../../games/cu/GameBankABI.json"); -import { GameBankABI as GameBankABIType } from "../../games/cu/GameBankABI"; -const ERC721MetadataABI = require("../../../../abi/MockERC721.json"); -import { MockERC721 } from "../../../../types/contracts/MockERC721"; + +import { useRouter } from "../../../../../packages/moonstream-components/src/core/hooks"; +const StashABI = require("../../../games/cu/StashABI.json"); +import { StashABI as StashABIType } from "../../../games/cu/StashABI"; +const GameBankABI = require("../../../games/cu/GameBankABI.json"); +import { GameBankABI as GameBankABIType } from "../../../games/cu/GameBankABI"; +const ERC721MetadataABI = require("../../../../../abi/MockERC721.json"); +import { MockERC721 } from "../../../../../types/contracts/MockERC721"; import Web3Context from "moonstream-components/src/core/providers/Web3Provider/context"; -import { supportedChains } from "../../../../types/Moonstream"; +import { supportedChains } from "../../../../../types/Moonstream"; import { useERC20, useToast } from "moonstream-components/src/core/hooks"; import { useMutation, useQuery } from "react-query"; -import { DEFAULT_METATAGS } from "../../src/constants"; +import { DEFAULT_METATAGS } from "../../../src/constants"; import { MAX_INT, chainByChainId, } from "moonstream-components/src/core/providers/Web3Provider"; -import LootboxCard from "../../../../packages/moonstream-components/src/components/CryptoUnicorns/LootboxCard"; -import { MockTerminus as TerminusFacet } from "../../../../types/contracts/MockTerminus"; +import LootboxCard from "../../../../../packages/moonstream-components/src/components/CryptoUnicorns/LootboxCardPlay"; +import { MockTerminus as TerminusFacet } from "../../../../../types/contracts/MockTerminus"; import { hookCommon } from "moonstream-components/src/core/hooks"; -const terminusAbi = require("../../../../abi/MockTerminus.json"); +const terminusAbi = require("../../../../../abi/MockTerminus.json"); const GameBankAddresses: { [key in supportedChains]: string } = { mumbai: "0x762aF8cbE298bbFE568BBB6709f854A01c07333D", @@ -92,6 +92,13 @@ const LandsAddresses: { [key in supportedChains]: string } = { localhost: "0x0000000000000000000000000000000000000000", }; +const ShadowcornsAddresses: { [key in supportedChains]: string } = { + mumbai: "0x8819CFdb4Fd6Ba0464Ef2283F5F621443B7eC2F4", + polygon: "0xa7D50EE3D7485288107664cf758E877a0D351725", + ethereum: "0x0000000000000000000000000000000000000000", + localhost: "0x0000000000000000000000000000000000000000", +}; + //keystonePoolIdByLandType // Land type: 1, 62 // mythic // Land type: 2, 59 // light @@ -142,7 +149,12 @@ type terminusType = | "communityCouncil" | "summerOfLoveTier1" | "summerOfLoveTier2" - | "summerOfLoveTier3"; + | "summerOfLoveTier3" + | "fireShadowcornLootbox" + | "slimeShadowcornLootbox" + | "soulShadowcornLootbox" + | "voltShadowcornLootbox" + | "nebulaShadowcornLootbox"; interface LootboxInfo { poolIdByChain: { @@ -187,6 +199,11 @@ const terminusTypes: terminusType[] = [ "summerOfLoveTier1", "summerOfLoveTier2", "summerOfLoveTier3", + "fireShadowcornLootbox", + "slimeShadowcornLootbox", + "soulShadowcornLootbox", + "voltShadowcornLootbox", + "nebulaShadowcornLootbox", ]; const terminusInfo: { [key in terminusType]: LootboxInfo } = { @@ -470,6 +487,46 @@ const terminusInfo: { [key in terminusType]: LootboxInfo } = { localhost: -1, }, }, + fireShadowcornLootbox: { + poolIdByChain: { + mumbai: -1, + polygon: 75, + ethereum: -1, + localhost: -1, + }, + }, + slimeShadowcornLootbox: { + poolIdByChain: { + mumbai: -1, + polygon: 77, + ethereum: -1, + localhost: -1, + }, + }, + soulShadowcornLootbox: { + poolIdByChain: { + mumbai: -1, + polygon: 78, + ethereum: -1, + localhost: -1, + }, + }, + voltShadowcornLootbox: { + poolIdByChain: { + mumbai: -1, + polygon: 79, + ethereum: -1, + localhost: -1, + }, + }, + nebulaShadowcornLootbox: { + poolIdByChain: { + mumbai: -1, + polygon: 76, + ethereum: -1, + localhost: -1, + }, + }, }; const defaultLootboxBalances: { [key in terminusType]: number } = { @@ -508,6 +565,11 @@ const defaultLootboxBalances: { [key in terminusType]: number } = { summerOfLoveTier1: 0, summerOfLoveTier2: 0, summerOfLoveTier3: 0, + fireShadowcornLootbox: 0, + slimeShadowcornLootbox: 0, + soulShadowcornLootbox: 0, + voltShadowcornLootbox: 0, + nebulaShadowcornLootbox: 0, }; interface NFTMetadata { @@ -526,6 +588,8 @@ interface NFTInfo { } const CryptoUnicorns = () => { + const router = useRouter(); + const [currentAccount, setCurrentAccount] = React.useState( "0x0000000000000000000000000000000000000000" ); @@ -540,15 +604,17 @@ const CryptoUnicorns = () => { const [RBWAddress, setRBWAddress] = React.useState(""); const [unicornsAddress, setUnicornsAddress] = React.useState(""); const [landsAddress, setLandsAddress] = React.useState(""); + const [shadowcornsAddress, setShadowcornsAddress] = React.useState(""); - const [lootboxBalances, setLootboxBalances] = React.useState({ - ...defaultLootboxBalances, - }); + // const [lootboxBalances, setLootboxBalances] = React.useState({ + // ...defaultLootboxBalances, + // }); - const [unicorns, setUnicorns] = React.useState([]); - const [lands, setLands] = React.useState([]); + // const [unicorns, setUnicorns] = React.useState([]); + // const [lands, setLands] = React.useState([]); + // const [shadowcorns, setShadowcorns] = React.useState([]); - const inputField = React.useRef(null); + const spyAddressInput = React.useRef(null); const [spyMode, setSpyMode] = React.useState(false); const [displayType, setDisplayType] = React.useState(0); @@ -571,12 +637,14 @@ const CryptoUnicorns = () => { setRBWAddress(""); setUnicornsAddress(""); setLandsAddress(""); + setShadowcornsAddress(""); } else { setTerminusAddress(TerminusAddresses[chain as supportedChains]); setUNIMAddress(UNIMAddresses[chain as supportedChains]); setRBWAddress(RBWAddresses[chain as supportedChains]); setUnicornsAddress(UnicornsAddresses[chain as supportedChains]); setLandsAddress(LandsAddresses[chain as supportedChains]); + setShadowcornsAddress(ShadowcornsAddresses[chain as supportedChains]); } }, [web3ctx.chainId]); @@ -619,11 +687,11 @@ const CryptoUnicorns = () => { ); // Fetch terminus balances. - useQuery( - ["cuTerminus", web3ctx.chainId, terminusAddress, currentAccount], + const lootboxBalances = useQuery( + ["cuTerminus", terminusAddress, currentAccount], async ({ queryKey }) => { - const currentChain = chainByChainId[queryKey[1] as number]; - const currentUserAddress = queryKey[3]; + const currentChain = chainByChainId[web3ctx.chainId as number]; + const currentUserAddress = queryKey[2]; if (!currentChain) { return; @@ -665,8 +733,7 @@ const CryptoUnicorns = () => { `Crypto Unicorns player portal: Could not retrieve lootbox balances for the given user: ${currentUserAddress}. Lootbox pool IDs: ${poolIds}. Terminus contract address: ${terminusAddress}.` ); } - - setLootboxBalances(currentBalances); + return currentBalances; }, { ...hookCommon, @@ -674,11 +741,11 @@ const CryptoUnicorns = () => { ); // Fetch unicorns. - useQuery( - ["cuUnicorns", web3ctx.chainId, unicornsAddress, currentAccount], + const unicorns = useQuery( + ["cuUnicorns", unicornsAddress, currentAccount], async ({ queryKey }) => { - const currentChain = chainByChainId[queryKey[1] as number]; - const currentUserAddress = String(queryKey[3]); + const currentChain = chainByChainId[web3ctx.chainId as number]; + const currentUserAddress = String(queryKey[2]); if (!currentChain) { return; @@ -690,7 +757,7 @@ const CryptoUnicorns = () => { const unicornsContract = new web3ctx.web3.eth.Contract( ERC721MetadataABI ) as unknown as MockERC721; - unicornsContract.options.address = String(queryKey[2]); + unicornsContract.options.address = String(queryKey[1]); let unicornsInventory: NFTInfo[] = []; @@ -745,7 +812,7 @@ const CryptoUnicorns = () => { console.error(e); } - setUnicorns(unicornsInventory); + return unicornsInventory; }, { ...hookCommon, @@ -753,11 +820,11 @@ const CryptoUnicorns = () => { ); // Fetch lands. - useQuery( - ["cuLands", web3ctx.chainId, landsAddress, currentAccount], + const lands = useQuery( + ["cuLands", landsAddress, currentAccount], async ({ queryKey }) => { - const currentChain = chainByChainId[queryKey[1] as number]; - const currentUserAddress = String(queryKey[3]); + const currentChain = chainByChainId[web3ctx.chainId as number]; + const currentUserAddress = String(queryKey[2]); if (!currentChain) { return; @@ -769,7 +836,7 @@ const CryptoUnicorns = () => { const landsContract = new web3ctx.web3.eth.Contract( ERC721MetadataABI ) as unknown as MockERC721; - landsContract.options.address = String(queryKey[2]); + landsContract.options.address = String(queryKey[1]); let landsInventory: NFTInfo[] = []; @@ -824,7 +891,86 @@ const CryptoUnicorns = () => { console.error(e); } - setLands(landsInventory); + return landsInventory; + }, + { + ...hookCommon, + } + ); + + // Fetch shadowcorns. + const shadowcorns = useQuery( + ["cuShadowcorns", shadowcornsAddress, currentAccount], + async ({ queryKey }) => { + const currentChain = chainByChainId[web3ctx.chainId as number]; + const currentUserAddress = String(queryKey[2]); + + if (!currentChain) { + return; + } + if (currentUserAddress == "0x0000000000000000000000000000000000000000") { + return; + } + + const shadowcornsContract = new web3ctx.web3.eth.Contract( + ERC721MetadataABI + ) as unknown as MockERC721; + shadowcornsContract.options.address = String(queryKey[1]); + + let shadowcornsInventory: NFTInfo[] = []; + + try { + const numShadowcornsRaw: string = await shadowcornsContract.methods + .balanceOf(currentUserAddress) + .call(); + + let numShadowcorns: number = 0; + try { + numShadowcorns = parseInt(numShadowcornsRaw, 10); + } catch (e) { + console.error( + `Error: Could not parse number of owned shadowcorns as an integer: ${numShadowcornsRaw}` + ); + } + + let tokenIDPromises = []; + for (let i = 0; i < numShadowcorns; i++) { + tokenIDPromises.push( + shadowcornsContract.methods + .tokenOfOwnerByIndex(currentUserAddress, i) + .call() + ); + } + const tokenIDs = await Promise.all(tokenIDPromises); + + const tokenURIPromises = tokenIDs.map((tokenID) => + shadowcornsContract.methods.tokenURI(tokenID).call() + ); + const tokenURIs = await Promise.all(tokenURIPromises); + + const tokenMetadataPromises = tokenURIs.map((tokenURI) => + fetch(tokenURI).then((response) => response.json()) + ); + const tokenMetadata = await Promise.all(tokenMetadataPromises); + + const imageURIs = tokenMetadata.map((metadata) => metadata.image); + + tokenIDs.forEach((tokenID, index) => { + shadowcornsInventory.push({ + tokenID, + tokenURI: tokenURIs[index], + imageURI: imageURIs[index], + metadata: tokenMetadata[index], + }); + }); + } catch (e) { + console.error( + "Error: There was an issue retrieving information about user's lands:" + ); + console.error(e); + } + + return shadowcornsInventory; }, { ...hookCommon, @@ -838,6 +984,7 @@ const CryptoUnicorns = () => { GameBankAddresses[ web3ctx.targetChain?.name ? web3ctx.targetChain.name : "localhost" ], + account: currentAccount, }); const unim = useERC20({ contractAddress: UNIMAddress, @@ -846,6 +993,7 @@ const CryptoUnicorns = () => { web3ctx.targetChain?.name ? web3ctx.targetChain.name : "localhost" ], ctx: web3ctx, + account: currentAccount, }); const contract = new web3ctx.web3.eth.Contract( StashABI @@ -858,7 +1006,8 @@ const CryptoUnicorns = () => { const playAssetPath = "https://s3.amazonaws.com/static.simiotics.com/play"; const assets = { - unicornMilk: `${playAssetPath}/CU_UNIM_256px.png`, + unimLogo: `${playAssetPath}/cu/unim-logo.png`, + rbwLogo: `${playAssetPath}/cu/rbw-logo.png`, }; const toast = useToast(); @@ -931,7 +1080,7 @@ const CryptoUnicorns = () => { } else { setNotEnoughUNIM(false); } - }, [unimToStash, unim.spenderState.data, web3ctx.web3.utils]); + }, [unimToStash, unim.spenderState.data, web3ctx.web3.utils, currentAccount]); React.useLayoutEffect(() => { if (rbw.spenderState.data?.allowance && rbwToStash.length !== 0) { @@ -968,7 +1117,7 @@ const CryptoUnicorns = () => { } else { setNotEnoughRBW(false); } - }, [rbwToStash, rbw.spenderState.data, web3ctx.web3.utils]); + }, [rbwToStash, rbw.spenderState.data, web3ctx.web3.utils, currentAccount]); const lootboxes = [ { @@ -1007,6 +1156,36 @@ const CryptoUnicorns = () => { displayName: "RMP Lootbox", balanceKey: "RMPLootbox", }, + { + imageUrl: + "https://i.seadn.io/gae/NY1AJidjM4HnDbOrBw_474MUhbp4tL8EZBRmCXFtPDcEYO_B_pF2D1fua6ggpajhJm5_4xstKg94SySs2QY4mit_XNNd4Rm8LS06?auto=format&w=1000", + displayName: "Fire Shadowcorn Lootbox", + balanceKey: "fireShadowcornLootbox", + }, + { + imageUrl: + "https://i.seadn.io/gae/0RBQ7zZ0YJsoh4Ffd38olTDRZKCqlK_3FNpqYF30baO77djp_gedIiD5IRJrUArfmGmQs0VBupzaJyzKaiAHRHolWxrCDm_JvQ_4rQ?auto=format&w=1000", + displayName: "Slime Shadowcorn Lootbox", + balanceKey: "slimeShadowcornLootbox", + }, + { + imageUrl: + "https://i.seadn.io/gae/J9KyCj2jbkZ93hB_ilcXBBkyhfTgObG8CjFFmFIT8_d2b6nnmpyikbGGO7_7MH45KcH1VSaqFXFeLjbWsLLj0yJxvULxbzB-1PGTETw?auto=format&w=1000", + displayName: "Soul Shadowcorn Lootbox", + balanceKey: "soulShadowcornLootbox", + }, + { + imageUrl: + "https://i.seadn.io/gae/XroR84IEp89RHeEkI2ozw6h9t-hMXY8HDi1uB2nsmjpv_5-fKZrmyX8T2kF7yFRl8SArBhZOCCf6GmOBTDRLngmkdoj0moBQYt5L6sU?auto=format&w=1000", + displayName: "Volt Shadowcorn Lootbox", + balanceKey: "voltShadowcornLootbox", + }, + { + imageUrl: + "https://i.seadn.io/gae/U3EE-yhtgc44g3bxUX7FWLiTmNA_q_qdCch-4jxbcd7va_LzDmMm_Mm-RL3RYszPOOu0e8DukUdyBaYo_cSyGM8Dq0l6PYbpreuwrZY?auto=format&w=1000", + displayName: "Nebula Shadowcorn Lootbox", + balanceKey: "nebulaShadowcornLootbox", + }, ]; const keystones = [ @@ -1201,47 +1380,112 @@ const CryptoUnicorns = () => { } }; - const displayCardList = (list: any, showQuantity: boolean = true) => { + const displayCardList = ( + list: any, + displayId: number, + showQuantity: boolean = true + ) => { const html = ( - <> - {list.map((item: any, idx: any) => { - const quantity = lootboxBalances[item["balanceKey"] as terminusType]; - if (quantity == 0 && !showQuantity) { - return; - } else { + + {spyMode && ( + + {"bottle"} + UNIM: {getUnimBalance()} + {"bottle"} + RBW: {getRBWBalance()} + + )} + + {list.map((item: any, idx: any) => { + if (!lootboxBalances?.data) { + return; + } + const quantity = + lootboxBalances.data[item["balanceKey"] as terminusType]; + if (quantity == 0 && !showQuantity) { + return; + } else { + return ( + + ); + } + })} + + + ); + return html; + }; + + const displayERC721List = ( + list: any, + displayId: number, + showQuantity: boolean = true, + isVideo = false + ) => { + return ( + + {spyMode && ( + + {"bottle"} + UNIM: {getUnimBalance()} + {"bottle"} + RBW: {getRBWBalance()} + + {`${list.length} Item${list.length === 1 ? "" : "s"}`} + + )} + {!spyMode && ( + + {`${list.length} Item${list.length === 1 ? "" : "s"}`} + + )} + + + {list.map((item: any, idx: any) => { return ( ); - } - })} - + })} + + ); - return html; }; - const displayERC721List = (list: any, showQuantity: boolean = true) => { - const html = ( - <> - {list.map((item: any, idx: any) => { - return ( - - ); - })} - - ); - return html; + const getUnimBalance = () => { + return unim.spenderState.data?.balance + ? Math.floor( + web3ctx.web3.utils.fromWei( + unim.spenderState.data?.balance, + "ether" + ) as unknown as number + ) + : "0"; + }; + + const getRBWBalance = () => { + return rbw.spenderState.data?.balance + ? Math.floor( + web3ctx.web3.utils.fromWei( + rbw.spenderState.data?.balance, + "ether" + ) as unknown as number + ) + : "0"; }; if ( @@ -1264,15 +1508,21 @@ const CryptoUnicorns = () => { - + { { ml={2} alt={"bottle"} h="48px" - src={assets["unicornMilk"]} + src={assets["unimLogo"]} /> @@ -1300,14 +1549,9 @@ const CryptoUnicorns = () => { {unim.spenderState.isLoading ? ( ) : ( - + {`balance: `} - {unim.spenderState.data?.balance - ? web3ctx.web3.utils.fromWei( - unim.spenderState.data?.balance, - "ether" - ) - : "0"} + {getUnimBalance()} )} @@ -1326,64 +1570,49 @@ const CryptoUnicorns = () => { mr={2} p={1} > - - {"rbw"} + + {"rbw"} - + {rbw.spenderState.isLoading ? ( ) : ( - + {`balance: `} - {rbw.spenderState.data?.balance - ? web3ctx.web3.utils.fromWei( - rbw.spenderState.data?.balance, - "ether" - ) - : "0"} + {getRBWBalance()} )} - + - - - {" "} - Use this form to stash any amount of UNIM and RBW into Crypto - Unicorns. - - - WARNING: Only use an account with which you have already logged - into the game. Otherwise, the game server will not respect your - stash operation. - - + + Use this form to stash any amount of UNIM and RBW into Crypto + Unicorns. + + + WARNING: Only use an account with which you have already logged into + the game. Otherwise, the game server will not respect your stash + operation. +
- + - + {"UNIM to stash"} @@ -1394,10 +1623,15 @@ const CryptoUnicorns = () => { w="100%" variant={"outline"} > - + {
)}
{spyMode && ( - - + + { const nextValue = e.target.value; @@ -1578,15 +1839,22 @@ const CryptoUnicorns = () => { } }} placeholder="Type an address" - _placeholder={{ color: "black" }} + _placeholder={{ color: "white" }} /> - - - {displayType == 0 && displayERC721List(unicorns, false)} - {displayType == 1 && displayERC721List(lands, false)} - {displayType == 2 && displayCardList(lootboxes)} - {displayType == 3 && displayCardList(keystones)} - {displayType == 4 && displayCardList(badges, false)} - {displayType == 5 && displayCardList(miscItems)} - + {!spyMode && ( + + + + + )} + {unicorns?.data && displayERC721List(unicorns.data, 0, false)} + {lands?.data && displayERC721List(lands.data, 1, false)} + {lootboxBalances?.data && displayCardList(lootboxes, 2)} + {lootboxBalances?.data && displayCardList(keystones, 3)} + {lootboxBalances?.data && displayCardList(badges, 4, false)} + {lootboxBalances?.data && displayCardList(miscItems, 5)} + {shadowcorns?.data && + displayERC721List(shadowcorns.data, 6, false, true)}
); diff --git a/apps/play/pages/games/CryptoUnicorns/leaderboard.tsx b/apps/play/pages/games/CryptoUnicorns/leaderboard.tsx new file mode 100644 index 00000000..535aa33c --- /dev/null +++ b/apps/play/pages/games/CryptoUnicorns/leaderboard.tsx @@ -0,0 +1,526 @@ +import React, { useContext, useEffect, useState } from "react"; +import { useQuery } from "react-query"; +import { getLayout } from "moonstream-components/src/layoutsForPlay/EngineLayout"; +import LeaderboardGroupHeader from "./../../../components/LeaderboardGroupHeader"; +import LeaderboardGroup from "./../../../components/LeaderboardGroup"; +import ShadowcornRow from "../../../components/ShadocornRow"; +import LeaderboardRank from "./../../../components/LeaderboardRank"; + +import { + Box, + Heading, + Flex, + Image, + Accordion, + AccordionItem, + Spacer, + Link, + Spinner, + HStack, + GridItem, + Text, +} from "@chakra-ui/react"; +import { InfoOutlineIcon } from "@chakra-ui/icons"; + +import http from "moonstream-components/src/core/utils/http"; +import queryCacheProps from "moonstream-components/src/core/hooks/hookCommon"; +import { DEFAULT_METATAGS } from "../../../src/constants"; + +import Web3 from "web3"; +import Web3Context from "moonstream-components/src/core/providers/Web3Provider/context"; +const GardenABI = require("../../../games/GoFPABI.json"); +import { GOFPFacet as GardenABIType } from "../../../../../types/contracts/GOFPFacet"; +const MulticallABI = require("../../../games/cu/Multicall2.json"); +const ERC721MetadataABI = require("../../../../../abi/MockERC721.json"); +import { MockERC721 } from "../../../../../types/contracts/MockERC721"; +import { + GOFP_CONTRACT_ADDRESS, + MULTICALL2_CONTRACT_ADDRESS, + SHADOWCORN_CONTRACT_ADDRESS, +} from "moonstream-components/src/core/cu/constants"; + +const playAssetPath = "https://s3.amazonaws.com/static.simiotics.com/play"; +const assets = { + shadowcornsLogo: `${playAssetPath}/cu/shadowcorns-logo.png`, +}; + +const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; + +const Leaderboard = () => { + const [limit] = React.useState(0); + const [offset] = React.useState(0); + const [currentAccount, setCurrentAccount] = React.useState(ZERO_ADDRESS); + + const fetchLeaders = async (pageLimit: number, pageOffset: number) => { + return http( + { + method: "GET", + url: `https://engineapi.moonstream.to/leaderboard/?leaderboard_id=863429ad-ea0d-4cbf-b0f9-6e5c3fc83bb2&limit=${pageLimit}&offset=${pageOffset}`, + }, + true + ); + }; + + const fetchShadowcorns = async () => { + return http( + { + method: "GET", + url: "https://data.moonstream.to/shadowcorns/shadowcorns.json", + }, + true + ); + }; + + const shadowcorns = useQuery( + ["fetch_shadowcorns"], + () => { + return fetchShadowcorns().then((res) => { + const shadowcorns = new Map< + string, + { tokenId: number; name: string; image: string } + >(); + res.data.forEach( + (sc: { + token_id: number; + metadata: { + name: string; + attributes: [{ trait_type: string; value: string }]; + }; + }) => { + const { name, attributes } = sc.metadata; + const image = `${playAssetPath}/cu/shadowcorns/shadowcorn_${ + attributes + .find((attr) => attr.trait_type === "Class") + ?.value.toLowerCase() ?? "" + }_${ + attributes + .find((attr) => attr.trait_type === "Rarity") + ?.value.toLowerCase() ?? "" + }.jpg`; + shadowcorns.set(String(sc.token_id), { + tokenId: sc.token_id, + name, + image, + }); + } + ); + return shadowcorns; + }); + }, + { + ...queryCacheProps, + onSuccess: () => {}, + } + ); + + const groups = useQuery( + ["fetch_leaders", limit, offset], + () => { + return fetchLeaders(limit, offset).then((res) => { + try { + let groups = new Map< + number, + { + rank: number; + records: { + address: string; + rank: number; + score: number; + }[]; + score: number; + } + >(); + for (const record of res.data) { + if (groups.has(record.score)) { + let { records } = groups.get(record.score)!; + records.push(record); + } else { + groups.set(record.score, { + rank: record.rank, + records: [record], + score: record.score, + }); + } + } + return groups; + } catch (err) { + console.log(err); + } + }); + }, + { + ...queryCacheProps, + onSuccess: () => {}, + } + ); + + const web3ctx = useContext(Web3Context); + + useEffect(() => { + if (Web3.utils.isAddress(web3ctx.account)) { + setCurrentAccount(web3ctx.account); + } + }, [web3ctx.account]); + + const stakedShadowcorns = useQuery( + ["staked_tokens", currentAccount], + async () => { + if (currentAccount == ZERO_ADDRESS) return; + const gardenContract = new web3ctx.polygonClient.eth.Contract( + GardenABI, + GOFP_CONTRACT_ADDRESS + ) as any as GardenABIType; + const multicallContract = new web3ctx.polygonClient.eth.Contract( + MulticallABI, + MULTICALL2_CONTRACT_ADDRESS + ); + const numStaked = await gardenContract.methods + .numTokensStakedIntoSession(4, currentAccount) + .call(); + let stakedTokensQueries = []; + for (var i = 1; i <= parseInt(numStaked); i++) { + stakedTokensQueries.push({ + target: GOFP_CONTRACT_ADDRESS, + callData: gardenContract.methods + .tokenOfStakerInSessionByIndex(4, currentAccount, i.toString()) + .encodeABI(), + }); + } + + return multicallContract.methods + .tryAggregate(false, stakedTokensQueries) + .call() + .then((results: any[]) => { + const parsedResults = results.map((result) => { + return Number(result[1]); + }); + return parsedResults; + }); + }, + { + ...queryCacheProps, + onSuccess: () => {}, + } + ); + + const unstakedShadowcorns = useQuery( + ["owned_tokens", currentAccount], + async () => { + if (currentAccount == ZERO_ADDRESS) return; + const shadowcornsContract = new web3ctx.polygonClient.eth.Contract( + ERC721MetadataABI, + SHADOWCORN_CONTRACT_ADDRESS + ) as unknown as MockERC721; + const multicallContract = new web3ctx.polygonClient.eth.Contract( + MulticallABI, + MULTICALL2_CONTRACT_ADDRESS + ); + const numTokens = await shadowcornsContract.methods + .balanceOf(currentAccount) + .call(); + let tokenOfOwnerQueries = []; + for (var i = 0; i < parseInt(numTokens); i++) { + tokenOfOwnerQueries.push({ + target: SHADOWCORN_CONTRACT_ADDRESS, + callData: shadowcornsContract.methods + .tokenOfOwnerByIndex(currentAccount, i) + .encodeABI(), + }); + } + + return multicallContract.methods + .tryAggregate(false, tokenOfOwnerQueries) + .call() + .then((results: any[]) => { + const parsedResults = results.map((result) => { + return Number(result[1]); + }); + return parsedResults; + }); + }, + { + ...queryCacheProps, + onSuccess: () => {}, + } + ); + + const [ownedShadowcorns, setOwnedShadowcorns] = useState< + { + address: string; + rank: number; + score: number; + }[] + >([]); + + useEffect(() => { + if (!groups.data || (!stakedShadowcorns.data && !unstakedShadowcorns.data)) + return; + const allShadowcorns = [...groups.data.values()] + .map((group) => group.records) + .flat(); + const ownedTokens = (stakedShadowcorns.data ?? []).concat( + unstakedShadowcorns.data ?? [] + ); + const shadowcorns = allShadowcorns.filter((sc) => + ownedTokens.includes(parseInt(sc.address)) + ); + setOwnedShadowcorns(shadowcorns.sort((scA, scB) => scA.rank - scB.rank)); + }, [stakedShadowcorns.data, unstakedShadowcorns.data, groups.data]); + + const panelBackground = "#2D2D2D"; + + return ( + + + + + {"Shadowcorns"} + + Throwing Shade Leaderboard + + + + + + + {" "} + About the event + + + + + + + This leaderboard ranks Shadowcorn NFTs and not player wallets. Each + room a Shadowcorn reaches during the Throwing Shade Event earns them + points. At the end of the event, players will be airdropped rewards + according to their Shadowcorns' ranks. Shadowcorns can share + ranks. + + {ownedShadowcorns.length > 0 && ( + + {" "} + + trophy + + Your shadowcorns + + + + + Rank + + Shadowcorn + + Score + + + {ownedShadowcorns.map((item) => { + return ( + + + + + + + + + {item.score} + + + ); + })} + + )} + + + Rank + + Shadowcorn + + Score + + + {groups.data ? ( + + {[...groups.data.entries()].map(([score, group]) => { + return ( + + {group.records.length > 1 && ( + <> + + + + )} + {group.records.length === 1 && ( + + + + + + + + + + {group.score} + + + + )} + + ); + })} + + ) : ( + + )} + + + ); +}; + +export async function getStaticProps() { + const metatags = { + title: "Moonstream player portal: Throwing Shade", + description: "Throwing Shade Leaderboard", + }; + return { + props: { metaTags: { DEFAULT_METATAGS, ...metatags } }, + }; +} + +Leaderboard.getLayout = getLayout; +export default Leaderboard; diff --git a/apps/play/pages/games/OpenGamingCollective.tsx b/apps/play/pages/games/OpenGamingCollective.tsx new file mode 100644 index 00000000..a386c285 --- /dev/null +++ b/apps/play/pages/games/OpenGamingCollective.tsx @@ -0,0 +1,293 @@ +import React, { useContext, useEffect } from "react"; +import { getLayout } from "moonstream-components/src/layoutsForPlay/EngineLayout"; +import { + Flex, + Center, + Spacer, + Image, + Box, + Text, + Input, + InputGroup, + InputRightElement, + Button, + Grid, +} from "@chakra-ui/react"; + +import Web3Context from "moonstream-components/src/core/providers/Web3Provider/context"; +import { useQuery } from "react-query"; +import { useRouter } from "moonstream-components/src/core/hooks"; +import { DEFAULT_METATAGS } from "../../src/constants"; +import LootboxCard from "moonstream-components/src/components/CryptoUnicorns/LootboxCardPlay"; +import { MockTerminus as TerminusFacet } from "../../../../types/contracts/MockTerminus"; +import { hookCommon } from "moonstream-components/src/core/hooks"; + +const terminusAbi = require("../../../../abi/MockTerminus.json"); + +type terminusType = "type1" | "type2" | "type3"; + +// TODO: Using an Enum here would make this less clumsy. The Enum defines a type *and* a value. +const terminusTypes: terminusType[] = ["type1", "type2", "type3"]; + +const terminusPoolIds: { [key in terminusType]: number } = { + type1: 1, + type2: 2, + type3: 3, +}; + +const defaultBalances: { [key in terminusType]: number } = { + type1: 0, + type2: 0, + type3: 0, +}; + +const OpenGamingCollective = () => { + const web3ctx = useContext(Web3Context); + const router = useRouter(); + var defaultSpyAddress = router.query["spyAddress"]; + if (!web3ctx.web3.utils.isAddress(defaultSpyAddress)) { + defaultSpyAddress = undefined; + } + const [currentAccount, setCurrentAccount] = React.useState( + defaultSpyAddress || "0x0000000000000000000000000000000000000000" + ); + const terminusAddress = "0xa4BE88fF51D069430E4DdAF6b3044353954cf011"; + const spyAddressInput = React.useRef(null); + + const [spyMode, setSpyMode] = React.useState(!!defaultSpyAddress); + + useEffect(() => { + if (!spyMode) { + setCurrentAccount(web3ctx.account); + } + }, [web3ctx.account, spyMode]); + + // Fetch terminus balances. + const terminusBalances = useQuery( + currentAccount, + async ({ queryKey }) => { + const currentUserAddress = queryKey[0]; + + if (currentUserAddress == "0x0000000000000000000000000000000000000000") { + return; + } + + const terminusFacet = new web3ctx.polygonClient.eth.Contract( + terminusAbi + ) as any as TerminusFacet; + terminusFacet.options.address = terminusAddress; + + let accounts: string[] = []; + let poolIds: number[] = []; + + terminusTypes.forEach((terminusType) => { + const pool = terminusPoolIds[terminusType]; + if (pool > 0) { + accounts.push(`${currentUserAddress}`); + poolIds.push(pool); + } + }); + + let currentBalances = { ...defaultBalances }; + + try { + const balances = await terminusFacet.methods + .balanceOfBatch(accounts, poolIds) + .call(); + balances.forEach((balance, index) => { + currentBalances[terminusTypes[index]] = parseInt(balance, 10); + }); + } catch (e) { + console.error( + `Open Gaming Collective Portal: Could not retrieve terminus balances for the given user: ${currentUserAddress}. Terminus pool IDs: ${poolIds}. Terminus contract address: ${terminusAddress}.` + ); + } + + return currentBalances; + }, + { + ...hookCommon, + } + ); + + const PLAY_ASSET_PATH = "https://s3.amazonaws.com/static.simiotics.com/play"; + const assets = { + logo: `${PLAY_ASSET_PATH}/ogc/logo.png`, + blackSpyIcon: `${PLAY_ASSET_PATH}/games/spy-icon-black.png`, + whiteSpyIcon: `${PLAY_ASSET_PATH}/games/spy-icon-white.png`, + }; + + const badges = [ + { + imageUrl: + "https://badges.moonstream.to/open-gaming-collective/open-gaming-gr15-open-source-trumps-all.png", + displayName: "Gitcoin GR15: Open Source Trumps All", + balanceKey: "type1", + }, + { + imageUrl: + "https://badges.moonstream.to/open-gaming-collective/gr15-i-demoed.png", + displayName: "Gitcoin GR15: I Demoed!", + balanceKey: "type2", + }, + { + imageUrl: + "https://badges.moonstream.to/open-gaming-collective/GR15-OG_Tesseract_final.png", + displayName: "Gitcoin GR15: The Open Gaming Tesseract", + balanceKey: "type3", + }, + ]; + + return ( + + + {spyMode && ( + + Spy Mode + Spy Mode + +
{ + setSpyMode(false); + if (spyAddressInput?.current != null) { + spyAddressInput.current.value = ""; + } + }} + > + + exit + + {/* */} +
+
+ )} + {spyMode && ( + + + { + const nextValue = e.target.value; + if (web3ctx.web3.utils.isAddress(nextValue)) { + setCurrentAccount(nextValue); + } + }} + placeholder="Type an address" + _placeholder={{ color: "white" }} + /> + + + + + + )} + + + {badges.map((item: any, idx: any) => { + if (!terminusBalances?.data) { + return; + } + const quantity = + terminusBalances.data[item["balanceKey"] as terminusType]; + + return ( + + ); + })} + + + {!spyMode && ( + + )} +
+
+ ); +}; + +export async function getStaticProps() { + const metatags = { + title: "Moonstream community portal: Open Gaming Collective", + description: "See your Open Gaming Collective badges", + }; + return { + props: { metaTags: { DEFAULT_METATAGS, ...metatags } }, + }; +} + +OpenGamingCollective.getLayout = getLayout; + +export default OpenGamingCollective; diff --git a/apps/play/pages/games/cu/dashboard.tsx b/apps/play/pages/games/cu/dashboard.tsx new file mode 100644 index 00000000..bed6d0de --- /dev/null +++ b/apps/play/pages/games/cu/dashboard.tsx @@ -0,0 +1,191 @@ +import React, { useState } from "react"; +import { useQuery } from "react-query"; +import { getLayout } from "moonstream-components/src/layouts/EngineLayout"; +import { + Box, + Heading, + Spinner, + Flex, + HStack, + VStack, + Spacer, + Text, + Tabs, + TabList, + Tab, + Select, +} from "@chakra-ui/react"; +import { + Period, + AssetType, +} from "moonstream-components/src/core/types/DashboardTypes"; +import LineChart from "moonstream-components/src/components/LineChart"; +import RecentSales from "moonstream-components/src/components/CryptoUnicorns/RecentSales"; +import MostActiveUsers from "moonstream-components/src/components/CryptoUnicorns/MostActiveUsers"; +import TotalSupply from "moonstream-components/src/components/CryptoUnicorns/TotalSupply"; +import queryCacheProps from "moonstream-components/src/core/hooks/hookCommon"; +import http from "moonstream-components/src/core/utils/http"; + +const DATA_API = "https://data.moonstream.to/prod/"; + +const Dashboard = () => { + const [volumeAssetType, setVolumeAssetType] = useState( + AssetType.UNIM + ); + + const getAssetAddress = () => { + if (volumeAssetType == AssetType.UNIM) + return "0x64060aB139Feaae7f06Ca4E63189D86aDEb51691"; + if (volumeAssetType == AssetType.RBW) + return "0x431CD3C9AC9Fc73644BF68bF5691f4B83F9E104f"; + if (volumeAssetType == AssetType.Unicorn) + return "0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f"; + if (volumeAssetType == AssetType.Land) + return "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8"; + return "0x0000000000000000000000000000000000000000"; + }; + + const [volumePeriod, setVolumePeriod] = useState(Period.Day); + const volumeEndpoint = () => { + const endpoint = `${DATA_API}erc20_721_volume/${getAssetAddress()}/${volumePeriod}/data.json`; + return endpoint; + }; + + const fetchVolume = async () => { + return http( + { + method: "GET", + url: volumeEndpoint(), + }, + true + ); + }; + + const scaleERC20 = (value: number) => { + return Math.floor(Math.pow(10, -18) * value); + }; + + const unim_volume = useQuery( + ["unim_volume", volumePeriod, volumeAssetType], + () => { + return fetchVolume().then((res) => { + try { + const formattedData = [ + { + id: "UNIM", + color: "#F5468F", + data: res.data.data.map((item: any) => { + let volume: number = item.value; + if ( + volumeAssetType == AssetType.UNIM || + volumeAssetType == AssetType.RBW + ) + volume = scaleERC20(volume); + return { + x: item.time, + y: volume, + }; + }), + }, + ]; + return formattedData; + // return LINE_CHART_TEST_DATA; + } catch (err) { + console.log(err); + } + }); + }, + { + ...queryCacheProps, + onSuccess: () => {}, + } + ); + + const panelBackground = "#2D2D2D"; + const changeVolumePeriod = (index: Number) => { + if (index == 1) setVolumePeriod(Period.Week); + else if (index == 2) setVolumePeriod(Period.Month); + else setVolumePeriod(Period.Day); + }; + + return ( + + Crypto Unicorns Dashboard + + + + + + Trading Volume + + + + + {unim_volume.data ? ( + + ) : ( + + )} + + + changeVolumePeriod(index)} + variant="unstyled" + defaultIndex={0} + > + + + 24H + + + 1W + + + 1M + + + + + + + + + + + + + + ); +}; + +Dashboard.getLayout = getLayout; + +export default Dashboard; diff --git a/apps/play/pages/games/index.js b/apps/play/pages/games/index.js index 376501a2..8b79e50d 100644 --- a/apps/play/pages/games/index.js +++ b/apps/play/pages/games/index.js @@ -1,24 +1,23 @@ import React from "react"; -import { getLayout } from "../../../../packages/moonstream-components/src/layouts/EngineLayout"; +import { getLayout } from "../../../../packages/moonstream-components/src/layoutsForPlay/EngineLayout"; import { Flex, Center } from "@chakra-ui/react"; -import FeatureCard from "moonstream-components/src/components/FeatureCard"; +import FeatureCard from "moonstream-components/src/components/FeatureCardPlay"; const Games = () => { return (
- + { h="450px" /> {/* */} + { { heading="Inventory" imageUrl={assets["lender"]} alt="Inventory" - textColor={"white.100"} level="h2" // imgH="220px" h="450px" diff --git a/apps/play/pages/inventory/index.js b/apps/play/pages/inventory/index.js index 7d74c570..8059ca16 100644 --- a/apps/play/pages/inventory/index.js +++ b/apps/play/pages/inventory/index.js @@ -2,8 +2,8 @@ import React, { useContext } from "react"; import { Flex, Button, Image, Center, Spinner } from "@chakra-ui/react"; import { DEFAULT_METATAGS, AWS_ASSETS_PATH } from "../../src/constants"; import Web3Context from "moonstream-components/src/core/providers/Web3Provider/context"; -import { getLayout } from "../../../../packages/moonstream-components/src/layouts/EngineLayout"; -import LootboxCard from "moonstream-components/src/components/lootbox/LootboxCard"; +import { getLayout } from "../../../../packages/moonstream-components/src/layoutsForPlay/EngineLayout"; +import LootboxCard from "moonstream-components/src/components/lootbox/LootboxCardPlay"; import useLootbox from "moonstream-components/src/core/hooks/useLootbox"; const assets = { @@ -35,7 +35,7 @@ const Lootboxes = () => { @@ -43,6 +43,7 @@ const Lootboxes = () => { state?.data?.lootboxIds?.map((lootboxId) => { return ( Px#1ZP1_ zK>z@;j|==^1poj7vPnciRCoc!S3S<+Fc5XVmV(qEL0TwSN+=M!9R|0V+-h40@1D(F7Uwx2wZ@00Eznf+n*OVc!qqyZM#U^ZoTr}41DSn@8gVmokBOl|dIulhpM!-Ymw zgFRTKKEW-^qT}(HwpuM(E|+O0li`-j<+yJ?pVRC08bqS{*2*y{0yqJ~tk>)L5xMv( zL7#i>3651^g?~|li>hF(4+&jGz>{#0J8odvFoyzDmGqvKV}g3U&MgTEA0-Ttz)JiF z`v(b;X!ra5tu2tZLy;3R#Qw1Nqbn-HWFwEqgY5TvqU$>Uo&*@*@AqZ*55)c;&+_Wx zcDs?yW7yM>}rC*OL5rkc06FE zQgMBSLV=_Ks34&V0*2^0pU<9vjYfl{0hWle%K(9wqJVvp2@Hork_M1p5_l4ZBAd;o zrh*I*csV#=|5dNua=GM0l0Y%u0s&L>Kz>ECaAKp;h$MlS*fvZMIEfxe4FyyrkPAsj zwA*dj^?!*n+!gC1dcM%4_7{cvgYu8;Hgr zPmUFqN+qsH5b!J%0qJ}`?*>F8bGj(}iq)^&ji4dzIE;h}7*ng&qP1g{3V?GUff4~{ zJr7tlW}c*VZXuc$HDO6kXJ0_Kn?|OyUO`a4Zi>IQSqpPEbC;oWaL(Q3?9LE+zsAnSCKCUC;jl^oT=>BZyX<00000NkvXXu0mjf DMWA19 literal 2723 zcmY*bc|4SB8y;hcv5cJ%V@Qr+h6ypo*pH<&V<=0q?`CW>Wn?|J;t(AwJ2{9HDM=&y zR+iz5vVF*mtthe`O!AFRU+4Rt=l49<_1y1uU-xsr?>}z}?z}laND2f10QfB}Oz|v) z94&4x)=s*1ZHonLB)qvXpoS*%fptjqcChrZwg#xMI5&WkO&Gv_Bwbm25rrIWKastJ?H|Vy;7>KAg#91R$^h)e+ni(pPl$yh z2>?(%d$icf_3u=%tQ`IA9LNsV=TM%(M7X|0-z}<7A1O<-60erA&7*c z=tI9NP%M5FMnECoCFDSTsDm{QViFwY4bg_H!_}b}5Cj52hk5y+@TR~1NN1h&p?+j? z2nvA+4-bcjpN0pA`64uQb#)QyNCXn8#!{$}B7(^76ty6d!rvzU^)dA(d4~Ciko|*$ zAVG`0`cUXmp`YXLdy@Tq{;3p1`q37vLB!D$ga%w4@za}?iav^>uwnk*tjb6J z7!CAy<^RQg)IlSTivQ1L{_ga9l+`K*ghu>)Z5YsTm$_rCk8f*fYGmiYQ*Iwfkg*s0 zwpgdfbTMd{QJv|!$>>SRdS%I;VkT2Laxoq%i!W~jmssVA%j16M;M_Lf6UtNu$^&m1 zRti73(P#THLj|8`;cuB1r%0;~D$oplQ+Aa;-qa92#wJXu$!Y&Bi#)XGGhx#K-jpS*HL ziLJXL6nsv_xr%!|;MvDGKTY_GJ%^#Km^!}k3r&xQz^v1N5W)`VR8 zZA_-}?m^5c;e{|q3$~12_A5bG#VZ_rEVlOr%j503((BWm8*WtiWnwuVZ+?v37yeUe zuVh7bH;Qe!%I?lUk)e)^siH9b?e)b9LCv=&?0gai72X@t>U@7WdM09&Q=OJi(n~La zIrzG!>_kB)%SK2!IXOm3wuYB3aWpquQuMi0(lT|_ifRj(hsX0CJn#z$a-nAxsHQr%f+GodhUEjA^aVF|J_(<={f9dxh?&!vbGR6 z2M=`qE7!RK87Z^CK*@vI%;Wt%NQ7?Ab{dB83Cl8fo7@}3z<@R-p$UYZLds?mohArE(!5y z#5==G-?zf1h2b%1D{3d|jz=0bGjtP%V zlj*;9bC#Cjjg9RUgXb(blE*^AVEMGy_k>nucNH)j|B~!9_!VbnV^k~r__p}W*$hEo z6DrDu)2NjmhaJpo)KJ-kjq8ucvop`Queb9%6sGBA5i9Nsv?*PiUU5I2s-tT=Q3P9c zz@qn%F`I8RuQ8)uNu0)g%6EPks*d{TOiq&f@M-17V2#_59nQvyvIVtX1|Wzy*K4C02{QJtX*7;`R!4!t8_VN zqOwcDzu;kX^5wcDN?C?}e;`My)MCK`MDcm4mHQ?hCSISGB;pNjIZTmhLGJTryABwE zHRfG_fEdgJ4dmJ}CCaS5OhmMV;>Hu&x=Gwy;E)*Ws_MMi z8gOUlAAx7S3MehrNptq^bgn=1L|qZ7;^Ae2B}aw3Yh4R^sE=II=6SLg_-a$Jlblgi zWKQ-&G~ZQ@97Gg&?o?6YDe>!rLChiyWG2wmxh6Y=UZyF*z6T`rj*gj99m@kcd^H(u zjOz7HxtHxv>7qqUnrs1vluB$$jUK!}(DJR@-r}Z?xf+rKkFEL&7)eJ--YZHc+w?UU zf3CzyVY_KTy)hV!Mv@ov?D+7KS(P(yEI*JWh0iUL5jY@73s-sgWMr%zM02ANQxY9Xi9tA482 zhPZYx&->i_2#+Zmn6VpZV$tO!80mLrvN%@U%3K0uaw>B|H;$l_7EiYOJrdY_wa=ry zQ8@|=3fPmA76Ej@Vu4LYW#di{#Ak@Y8$LD>+sZppM7cF@Feh+G&h(et$+CQgzgS9? z!MaNs{+X8!@U*uIkRob+ViGJE8+tr4o-CB=Cb$3%ohf9z2+BJgDwvF6dtJL`{)NU- zjC{KF#_QNZ55P>I=)8y<7$Oh*+C190T+J>BPLwhGynPpbHsMPqp(d2amiKHQq{KE^ zE@Nnnbr{>-X5(=IPkEI8oarAE-4bLc1Txy`+=Z$v9|iV^fT`HoxU zI%3{KfKvF1nYep|7MzJDn9wweTFY#9~rvrRK$KWn(j?PJK$<>8#vK?Y%YdnHOKnb;SW*bi* zrUf4eU-XrTS`c1Ruh+z>+{RVe6&xro1Kh`7VxB3IpM<^8@}}eGsfFZL(s!r+x7bC-tz+oh8=xIX*4CJsR($!S+=*Y zBxYH5WWu%UHL|_WKoQxSG7!J?dvu(I*k`s6E1t^aM<#K;*y!Y^*K{$5GNVK2WI~wM zVZMC(a+PP4ktM^7@6{c+F7`hDypvZ}^&dE^PRq)M3IRiL8f`#lZZ!FP2U?GhXl?+E zCa4T(WmM+mzGTch`EA8OBetc`@^$zp^T{ { @@ -41,8 +42,9 @@ const AppContext = (props) => { COPYRIGHT_NAME: COPYRIGHT_NAME, SUPPORT_EMAIL: SUPPORT_EMAIL, APP_NAME: APP_NAME, - WHITE_LOGO_W_TEXT_URL: WHITE_LOGO_W_TEXT_URL, + PRIMARY_MOON_LOGO_URL: PRIMARY_MOON_LOGO_URL, AWS_ASSETS_PATH: AWS_ASSETS_PATH, + BACKGROUND_COLOR: BACKGROUND_COLOR, }} > diff --git a/apps/play/src/EngineLayout.d.ts b/apps/play/src/EngineLayout.d.ts new file mode 100644 index 00000000..cb1c91b2 --- /dev/null +++ b/apps/play/src/EngineLayout.d.ts @@ -0,0 +1 @@ +declare module "moonstream-components/src/layouts/EngineLayout"; diff --git a/apps/play/src/Theme/Button/index.js b/apps/play/src/Theme/Button/index.js index fc73aeb2..6119abd7 100644 --- a/apps/play/src/Theme/Button/index.js +++ b/apps/play/src/Theme/Button/index.js @@ -114,6 +114,55 @@ const variantGhost = (props) => { }; }; +const variantWhiteOutline = (props) => { + const color = props["data-selected"] ? "white" : "#BFBFBF"; + return { + color, + borderColor: color, + padding: ["2px 5px", "5px 10px", "10px 20px"], + borderWidth: "2px", + borderStyle: "solid", + borderRadius: ["50px", "75px", "100px"], + fontWeight: "400", + fontSize: ["sm", "md", "lg"], + }; +}; + +const variantOrangeOutline = () => { + return { + color: "#F56646", + backgroundColor: "transparent", + borderColor: "#F56646", + borderStyle: "solid", + borderWidth: "2px", + borderRadius: "10px", + fontWeight: "500", + _hover: { + borderColor: "#F4532F", + backgroundColor: "transparent", + color: "#F4532F", + fontWeight: "700", + }, + _active: { + color: "#F4532F", + borderColor: "#F4532F", + textDecoration: "none", + }, + }; +}; + +const variantCUbutton = () => { + return { + padding: ["5px 10px", "5px 10px", "10px 20px"], + fontSize: ["sm", "md", "lg"], + fontWeight: "700", + borderRadius: "10px", + _hover: { + backgroundColor: "#D5D5D5", + }, + }; +}; + const Button = { // 1. We can update the base styles baseStyle: () => ({ @@ -151,6 +200,9 @@ const Button = { ghost: variantGhost, outline: variantOutline, link: variantLink, + orangeOutline: variantOrangeOutline, + whiteOutline: variantWhiteOutline, + cuButton: variantCUbutton, }, }; export default Button; diff --git a/apps/play/src/Theme/theme.js b/apps/play/src/Theme/theme.js index 6269fd18..da35d2fd 100644 --- a/apps/play/src/Theme/theme.js +++ b/apps/play/src/Theme/theme.js @@ -12,10 +12,11 @@ import Tooltip from "./Tooltip"; import Spinner from "./Spinner"; import Heading from "./Heading"; import { createBreakpoints } from "@chakra-ui/theme-tools"; +import { BACKGROUND_COLOR } from "../constants"; const breakpointsCustom = createBreakpoints({ sm: "24em", //Mobile phone - md: "64.01em", //Tablet or rotated phone + md: "48.01em", //Tablet or rotated phone lg: "89.9em", //QHD xl: "160em", //4k monitor "2xl": "192em", // Mac Book 16" and above @@ -41,7 +42,7 @@ const theme = extendTheme({ styles: { global: { body: { - color: "blue.1200", + color: { BACKGROUND_COLOR }, }, }, }, @@ -62,9 +63,9 @@ const theme = extendTheme({ }, fonts: { - heading: '"Work Sans", sans-serif', - body: '"Work Sans", sans-serif', - mono: '"Work Sans", monospace', + heading: '"Space Grotesk", sans-serif', + body: '"Space Grotesk", sans-serif', + mono: '"Space Grotesk", monospace', }, fontSizes: { xs: "0.625rem", //10px diff --git a/apps/play/src/constants.js b/apps/play/src/constants.js index 90ba140a..1eca68e6 100644 --- a/apps/play/src/constants.js +++ b/apps/play/src/constants.js @@ -79,14 +79,10 @@ and getStaticProps of the page (see /pages_templates for examples) export const AWS_ASSETS_PATH = `https://s3.amazonaws.com/static.simiotics.com/moonstream/assets`; /* -White Logo with text URL (.png) +New Logo URL (.png) */ -export const WHITE_LOGO_W_TEXT_URL = `https://s3.amazonaws.com/static.simiotics.com/moonstream/assets/moon-logo%2Btext-white.png`; - -/* -White Logo URL (.svg) -*/ -export const WHITE_LOGO_SVG = `https://s3.amazonaws.com/static.simiotics.com/moonstream/assets/moon-logo%2Btext-white.png`; +export const PRIMARY_MOON_LOGO_URL = `${AWS_ASSETS_PATH}/moonstream-full-logo-2022.png`; +export const BACKGROUND_COLOR = "#1A1D22"; export const TIME_RANGE_SECONDS = { day: 86400, diff --git a/apps/play/styles/sidebar.css b/apps/play/styles/sidebar.css index 72bfbf12..775eb5de 100644 --- a/apps/play/styles/sidebar.css +++ b/apps/play/styles/sidebar.css @@ -39,7 +39,8 @@ z-index: 1009; } .pro-sidebar > .pro-sidebar-inner { - background: #212990; + background: #1A1D22 + ; height: 100%; position: relative; z-index: 101; @@ -65,13 +66,13 @@ z-index: 101; } .pro-sidebar > .pro-sidebar-inner > .pro-sidebar-layout .pro-sidebar-header { - border-bottom: 1px solid #212990; + border-bottom: 1px solid white; } .pro-sidebar > .pro-sidebar-inner > .pro-sidebar-layout .pro-sidebar-content { flex-grow: 1; } .pro-sidebar > .pro-sidebar-inner > .pro-sidebar-layout .pro-sidebar-footer { - border-top: 1px solid #212990; + border-top: 1px solid white; } .pro-sidebar > .pro-sidebar-inner > .pro-sidebar-layout ul { list-style-type: none; @@ -256,6 +257,11 @@ white-space: nowrap; } +#sidebar-account { + background-color: white; + color: black; +} + .pro-sidebar .pro-menu { padding-top: 10px; padding-bottom: 10px; diff --git a/brownie-config.yaml b/brownie-config.yaml index b6c33686..f416b2fd 100644 --- a/brownie-config.yaml +++ b/brownie-config.yaml @@ -1,11 +1,12 @@ dependencies: - "OpenZeppelin/openzeppelin-contracts@4.3.2" - "bugout-dev/dao@0.0.7" - - "smartcontractkit/chainlink@1.0.1" + - "smartcontractkit/chainlink@1.10.0" compiler: solc: remappings: - - "@openzeppelin-contracts=OpenZeppelin/openzeppelin-contracts@4.3.2" + - "@openzeppelin-contracts=OpenZeppelin/openzeppelin-contracts@4.4.0" + - "@openzeppelin/contracts=OpenZeppelin/openzeppelin-contracts@4.4.0" - "@moonstream=bugout-dev/dao@0.0.7" - - "@chainlink=smartcontractkit/chainlink@1.0.1" + - "@chainlink=smartcontractkit/chainlink@1.10.0" diff --git a/cli/enginecli/GOFPFacet.py b/cli/enginecli/GOFPFacet.py new file mode 100644 index 00000000..7ddb3cc4 --- /dev/null +++ b/cli/enginecli/GOFPFacet.py @@ -0,0 +1,1114 @@ +# Code generated by moonworm : https://github.com/bugout-dev/moonworm +# Moonworm version : 0.5.1 + +import argparse +import json +import os +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +from brownie import Contract, network, project +from brownie.network.contract import ContractContainer +from eth_typing.evm import ChecksumAddress + + +PROJECT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "build", "contracts") + + +def boolean_argument_type(raw_value: str) -> bool: + TRUE_VALUES = ["1", "t", "y", "true", "yes"] + FALSE_VALUES = ["0", "f", "n", "false", "no"] + + if raw_value.lower() in TRUE_VALUES: + return True + elif raw_value.lower() in FALSE_VALUES: + return False + + raise ValueError( + f"Invalid boolean argument: {raw_value}. Value must be one of: {','.join(TRUE_VALUES + FALSE_VALUES)}" + ) + + +def bytes_argument_type(raw_value: str) -> str: + return raw_value + + +def get_abi_json(abi_name: str) -> List[Dict[str, Any]]: + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + abi_json = build.get("abi") + if abi_json is None: + raise ValueError(f"Could not find ABI definition in: {abi_full_path}") + + return abi_json + + +def contract_from_build(abi_name: str) -> ContractContainer: + # This is workaround because brownie currently doesn't support loading the same project multiple + # times. This causes problems when using multiple contracts from the same project in the same + # python project. + PROJECT = project.main.Project("moonworm", Path(PROJECT_DIRECTORY)) + + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + return ContractContainer(PROJECT, build) + + +class GOFPFacet: + def __init__(self, contract_address: Optional[ChecksumAddress]): + self.contract_name = "GOFPFacet" + self.address = contract_address + self.contract = None + self.abi = get_abi_json("GOFPFacet") + if self.address is not None: + self.contract: Optional[Contract] = Contract.from_abi( + self.contract_name, self.address, self.abi + ) + + def deploy(self, transaction_config): + contract_class = contract_from_build(self.contract_name) + deployed_contract = contract_class.deploy(transaction_config) + self.address = deployed_contract.address + self.contract = deployed_contract + return deployed_contract.tx + + def assert_contract_is_instantiated(self) -> None: + if self.contract is None: + raise Exception("contract has not been instantiated") + + def verify_contract(self): + self.assert_contract_is_instantiated() + contract_class = contract_from_build(self.contract_name) + contract_class.publish_source(self.contract) + + def admin_terminus_info( + self, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.adminTerminusInfo.call(block_identifier=block_number) + + def choose_current_stage_paths( + self, session_id: int, token_ids: List, paths: List, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.chooseCurrentStagePaths( + session_id, token_ids, paths, transaction_config + ) + + def create_session( + self, + player_token_address: ChecksumAddress, + payment_token_address: ChecksumAddress, + payment_amount: int, + is_active: bool, + uri: str, + stages: List, + is_forgiving: bool, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.createSession( + player_token_address, + payment_token_address, + payment_amount, + is_active, + uri, + stages, + is_forgiving, + transaction_config, + ) + + def get_correct_path_for_stage( + self, + session_id: int, + stage: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.getCorrectPathForStage.call( + session_id, stage, block_identifier=block_number + ) + + def get_current_stage( + self, session_id: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.getCurrentStage.call( + session_id, block_identifier=block_number + ) + + def get_path_choice( + self, + session_id: int, + token_id: int, + stage: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.getPathChoice.call( + session_id, token_id, stage, block_identifier=block_number + ) + + def get_session( + self, session_id: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.getSession.call(session_id, block_identifier=block_number) + + def get_session_token_stake_guard( + self, + session_id: int, + token_id: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.getSessionTokenStakeGuard.call( + session_id, token_id, block_identifier=block_number + ) + + def get_stage_reward( + self, + session_id: int, + stage: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.getStageReward.call( + session_id, stage, block_identifier=block_number + ) + + def get_staked_token_info( + self, + nft_address: ChecksumAddress, + token_id: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.getStakedTokenInfo.call( + nft_address, token_id, block_identifier=block_number + ) + + def init( + self, + admin_terminus_address: ChecksumAddress, + admin_terminus_pool_id: int, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.init( + admin_terminus_address, admin_terminus_pool_id, transaction_config + ) + + def num_sessions(self, block_number: Optional[Union[str, int]] = "latest") -> Any: + self.assert_contract_is_instantiated() + return self.contract.numSessions.call(block_identifier=block_number) + + def num_tokens_staked_into_session( + self, + session_id: int, + staker: ChecksumAddress, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.numTokensStakedIntoSession.call( + session_id, staker, block_identifier=block_number + ) + + def on_erc1155_batch_received( + self, + arg1: ChecksumAddress, + arg2: ChecksumAddress, + arg3: List, + arg4: List, + arg5: bytes, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.onERC1155BatchReceived( + arg1, arg2, arg3, arg4, arg5, transaction_config + ) + + def on_erc1155_received( + self, + arg1: ChecksumAddress, + arg2: ChecksumAddress, + arg3: int, + arg4: int, + arg5: bytes, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.onERC1155Received( + arg1, arg2, arg3, arg4, arg5, transaction_config + ) + + def on_erc721_received( + self, + arg1: ChecksumAddress, + arg2: ChecksumAddress, + arg3: int, + arg4: bytes, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.onERC721Received( + arg1, arg2, arg3, arg4, transaction_config + ) + + def set_correct_path_for_stage( + self, + session_id: int, + stage: int, + path: int, + set_is_choosing_active: bool, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setCorrectPathForStage( + session_id, stage, path, set_is_choosing_active, transaction_config + ) + + def set_session_active( + self, session_id: int, is_active: bool, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setSessionActive(session_id, is_active, transaction_config) + + def set_session_choosing_active( + self, session_id: int, is_choosing_active: bool, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setSessionChoosingActive( + session_id, is_choosing_active, transaction_config + ) + + def set_session_uri(self, session_id: int, uri: str, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setSessionUri(session_id, uri, transaction_config) + + def set_stage_rewards( + self, + session_id: int, + stages: List, + terminus_addresses: List, + terminus_pool_ids: List, + reward_amounts: List, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setStageRewards( + session_id, + stages, + terminus_addresses, + terminus_pool_ids, + reward_amounts, + transaction_config, + ) + + def stake_tokens_into_session( + self, session_id: int, token_ids: List, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.stakeTokensIntoSession( + session_id, token_ids, transaction_config + ) + + def supports_interface( + self, interface_id: bytes, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.supportsInterface.call( + interface_id, block_identifier=block_number + ) + + def token_of_staker_in_session_by_index( + self, + session_id: int, + staker: ChecksumAddress, + index: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.tokenOfStakerInSessionByIndex.call( + session_id, staker, index, block_identifier=block_number + ) + + def unstake_tokens_from_session( + self, session_id: int, token_ids: List, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.unstakeTokensFromSession( + session_id, token_ids, transaction_config + ) + + +def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]: + signer = network.accounts.load(args.sender, args.password) + transaction_config: Dict[str, Any] = {"from": signer} + if args.gas_price is not None: + transaction_config["gas_price"] = args.gas_price + if args.max_fee_per_gas is not None: + transaction_config["max_fee"] = args.max_fee_per_gas + if args.max_priority_fee_per_gas is not None: + transaction_config["priority_fee"] = args.max_priority_fee_per_gas + if args.confirmations is not None: + transaction_config["required_confs"] = args.confirmations + if args.nonce is not None: + transaction_config["nonce"] = args.nonce + return transaction_config + + +def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> None: + parser.add_argument( + "--network", required=True, help="Name of brownie network to connect to" + ) + parser.add_argument( + "--address", required=False, help="Address of deployed contract to connect to" + ) + if not transact: + parser.add_argument( + "--block-number", + required=False, + type=int, + help="Call at the given block number, defaults to latest", + ) + return + parser.add_argument( + "--sender", required=True, help="Path to keystore file for transaction sender" + ) + parser.add_argument( + "--password", + required=False, + help="Password to keystore file (if you do not provide it, you will be prompted for it)", + ) + parser.add_argument( + "--gas-price", default=None, help="Gas price at which to submit transaction" + ) + parser.add_argument( + "--max-fee-per-gas", + default=None, + help="Max fee per gas for EIP1559 transactions", + ) + parser.add_argument( + "--max-priority-fee-per-gas", + default=None, + help="Max priority fee per gas for EIP1559 transactions", + ) + parser.add_argument( + "--confirmations", + type=int, + default=None, + help="Number of confirmations to await before considering a transaction completed", + ) + parser.add_argument( + "--nonce", type=int, default=None, help="Nonce for the transaction (optional)" + ) + parser.add_argument( + "--value", default=None, help="Value of the transaction in wei(optional)" + ) + parser.add_argument("--verbose", action="store_true", help="Print verbose output") + + +def handle_deploy(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = get_transaction_config(args) + contract = GOFPFacet(None) + result = contract.deploy(transaction_config=transaction_config) + print(result) + if args.verbose: + print(result.info()) + + +def handle_verify_contract(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + result = contract.verify_contract() + print(result) + + +def handle_admin_terminus_info(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + result = contract.admin_terminus_info(block_number=args.block_number) + print(result) + + +def handle_choose_current_stage_paths(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.choose_current_stage_paths( + session_id=args.session_id, + token_ids=args.token_ids, + paths=args.paths, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_create_session(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.create_session( + player_token_address=args.player_token_address, + payment_token_address=args.payment_token_address, + payment_amount=args.payment_amount, + is_active=args.is_active, + uri=args.uri, + stages=args.stages, + is_forgiving=args.is_forgiving, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_get_correct_path_for_stage(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + result = contract.get_correct_path_for_stage( + session_id=args.session_id, stage=args.stage, block_number=args.block_number + ) + print(result) + + +def handle_get_current_stage(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + result = contract.get_current_stage( + session_id=args.session_id, block_number=args.block_number + ) + print(result) + + +def handle_get_path_choice(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + result = contract.get_path_choice( + session_id=args.session_id, + token_id=args.token_id, + stage=args.stage, + block_number=args.block_number, + ) + print(result) + + +def handle_get_session(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + result = contract.get_session( + session_id=args.session_id, block_number=args.block_number + ) + print(result) + + +def handle_get_session_token_stake_guard(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + result = contract.get_session_token_stake_guard( + session_id=args.session_id, + token_id=args.token_id, + block_number=args.block_number, + ) + print(result) + + +def handle_get_stage_reward(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + result = contract.get_stage_reward( + session_id=args.session_id, stage=args.stage, block_number=args.block_number + ) + print(result) + + +def handle_get_staked_token_info(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + result = contract.get_staked_token_info( + nft_address=args.nft_address, + token_id=args.token_id, + block_number=args.block_number, + ) + print(result) + + +def handle_init(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.init( + admin_terminus_address=args.admin_terminus_address, + admin_terminus_pool_id=args.admin_terminus_pool_id, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_num_sessions(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + result = contract.num_sessions(block_number=args.block_number) + print(result) + + +def handle_num_tokens_staked_into_session(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + result = contract.num_tokens_staked_into_session( + session_id=args.session_id, staker=args.staker, block_number=args.block_number + ) + print(result) + + +def handle_on_erc1155_batch_received(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.on_erc1155_batch_received( + arg1=args.arg1, + arg2=args.arg2, + arg3=args.arg3, + arg4=args.arg4, + arg5=args.arg5, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_on_erc1155_received(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.on_erc1155_received( + arg1=args.arg1, + arg2=args.arg2, + arg3=args.arg3, + arg4=args.arg4, + arg5=args.arg5, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_on_erc721_received(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.on_erc721_received( + arg1=args.arg1, + arg2=args.arg2, + arg3=args.arg3, + arg4=args.arg4, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_set_correct_path_for_stage(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_correct_path_for_stage( + session_id=args.session_id, + stage=args.stage, + path=args.path, + set_is_choosing_active=args.set_is_choosing_active, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_set_session_active(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_session_active( + session_id=args.session_id, + is_active=args.is_active, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_set_session_choosing_active(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_session_choosing_active( + session_id=args.session_id, + is_choosing_active=args.is_choosing_active, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_set_session_uri(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_session_uri( + session_id=args.session_id, uri=args.uri, transaction_config=transaction_config + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_set_stage_rewards(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_stage_rewards( + session_id=args.session_id, + stages=args.stages, + terminus_addresses=args.terminus_addresses, + terminus_pool_ids=args.terminus_pool_ids, + reward_amounts=args.reward_amounts, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_stake_tokens_into_session(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.stake_tokens_into_session( + session_id=args.session_id, + token_ids=args.token_ids, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_supports_interface(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + result = contract.supports_interface( + interface_id=args.interface_id, block_number=args.block_number + ) + print(result) + + +def handle_token_of_staker_in_session_by_index(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + result = contract.token_of_staker_in_session_by_index( + session_id=args.session_id, + staker=args.staker, + index=args.index, + block_number=args.block_number, + ) + print(result) + + +def handle_unstake_tokens_from_session(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = GOFPFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.unstake_tokens_from_session( + session_id=args.session_id, + token_ids=args.token_ids, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def generate_cli() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="CLI for GOFPFacet") + parser.set_defaults(func=lambda _: parser.print_help()) + subcommands = parser.add_subparsers() + + deploy_parser = subcommands.add_parser("deploy") + add_default_arguments(deploy_parser, True) + deploy_parser.set_defaults(func=handle_deploy) + + verify_contract_parser = subcommands.add_parser("verify-contract") + add_default_arguments(verify_contract_parser, False) + verify_contract_parser.set_defaults(func=handle_verify_contract) + + admin_terminus_info_parser = subcommands.add_parser("admin-terminus-info") + add_default_arguments(admin_terminus_info_parser, False) + admin_terminus_info_parser.set_defaults(func=handle_admin_terminus_info) + + choose_current_stage_paths_parser = subcommands.add_parser( + "choose-current-stage-paths" + ) + add_default_arguments(choose_current_stage_paths_parser, True) + choose_current_stage_paths_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + choose_current_stage_paths_parser.add_argument( + "--token-ids", required=True, help="Type: uint256[]", nargs="+" + ) + choose_current_stage_paths_parser.add_argument( + "--paths", required=True, help="Type: uint256[]", nargs="+" + ) + choose_current_stage_paths_parser.set_defaults( + func=handle_choose_current_stage_paths + ) + + create_session_parser = subcommands.add_parser("create-session") + add_default_arguments(create_session_parser, True) + create_session_parser.add_argument( + "--player-token-address", required=True, help="Type: address" + ) + create_session_parser.add_argument( + "--payment-token-address", required=True, help="Type: address" + ) + create_session_parser.add_argument( + "--payment-amount", required=True, help="Type: uint256", type=int + ) + create_session_parser.add_argument( + "--is-active", required=True, help="Type: bool", type=boolean_argument_type + ) + create_session_parser.add_argument( + "--uri", required=True, help="Type: string", type=str + ) + create_session_parser.add_argument( + "--stages", required=True, help="Type: uint256[]", nargs="+" + ) + create_session_parser.add_argument( + "--is-forgiving", required=True, help="Type: bool", type=boolean_argument_type + ) + create_session_parser.set_defaults(func=handle_create_session) + + get_correct_path_for_stage_parser = subcommands.add_parser( + "get-correct-path-for-stage" + ) + add_default_arguments(get_correct_path_for_stage_parser, False) + get_correct_path_for_stage_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + get_correct_path_for_stage_parser.add_argument( + "--stage", required=True, help="Type: uint256", type=int + ) + get_correct_path_for_stage_parser.set_defaults( + func=handle_get_correct_path_for_stage + ) + + get_current_stage_parser = subcommands.add_parser("get-current-stage") + add_default_arguments(get_current_stage_parser, False) + get_current_stage_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + get_current_stage_parser.set_defaults(func=handle_get_current_stage) + + get_path_choice_parser = subcommands.add_parser("get-path-choice") + add_default_arguments(get_path_choice_parser, False) + get_path_choice_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + get_path_choice_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + get_path_choice_parser.add_argument( + "--stage", required=True, help="Type: uint256", type=int + ) + get_path_choice_parser.set_defaults(func=handle_get_path_choice) + + get_session_parser = subcommands.add_parser("get-session") + add_default_arguments(get_session_parser, False) + get_session_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + get_session_parser.set_defaults(func=handle_get_session) + + get_session_token_stake_guard_parser = subcommands.add_parser( + "get-session-token-stake-guard" + ) + add_default_arguments(get_session_token_stake_guard_parser, False) + get_session_token_stake_guard_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + get_session_token_stake_guard_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + get_session_token_stake_guard_parser.set_defaults( + func=handle_get_session_token_stake_guard + ) + + get_stage_reward_parser = subcommands.add_parser("get-stage-reward") + add_default_arguments(get_stage_reward_parser, False) + get_stage_reward_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + get_stage_reward_parser.add_argument( + "--stage", required=True, help="Type: uint256", type=int + ) + get_stage_reward_parser.set_defaults(func=handle_get_stage_reward) + + get_staked_token_info_parser = subcommands.add_parser("get-staked-token-info") + add_default_arguments(get_staked_token_info_parser, False) + get_staked_token_info_parser.add_argument( + "--nft-address", required=True, help="Type: address" + ) + get_staked_token_info_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + get_staked_token_info_parser.set_defaults(func=handle_get_staked_token_info) + + init_parser = subcommands.add_parser("init") + add_default_arguments(init_parser, True) + init_parser.add_argument( + "--admin-terminus-address", required=True, help="Type: address" + ) + init_parser.add_argument( + "--admin-terminus-pool-id", required=True, help="Type: uint256", type=int + ) + init_parser.set_defaults(func=handle_init) + + num_sessions_parser = subcommands.add_parser("num-sessions") + add_default_arguments(num_sessions_parser, False) + num_sessions_parser.set_defaults(func=handle_num_sessions) + + num_tokens_staked_into_session_parser = subcommands.add_parser( + "num-tokens-staked-into-session" + ) + add_default_arguments(num_tokens_staked_into_session_parser, False) + num_tokens_staked_into_session_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + num_tokens_staked_into_session_parser.add_argument( + "--staker", required=True, help="Type: address" + ) + num_tokens_staked_into_session_parser.set_defaults( + func=handle_num_tokens_staked_into_session + ) + + on_erc1155_batch_received_parser = subcommands.add_parser( + "on-erc1155-batch-received" + ) + add_default_arguments(on_erc1155_batch_received_parser, True) + on_erc1155_batch_received_parser.add_argument( + "--arg1", required=True, help="Type: address" + ) + on_erc1155_batch_received_parser.add_argument( + "--arg2", required=True, help="Type: address" + ) + on_erc1155_batch_received_parser.add_argument( + "--arg3", required=True, help="Type: uint256[]", nargs="+" + ) + on_erc1155_batch_received_parser.add_argument( + "--arg4", required=True, help="Type: uint256[]", nargs="+" + ) + on_erc1155_batch_received_parser.add_argument( + "--arg5", required=True, help="Type: bytes", type=bytes_argument_type + ) + on_erc1155_batch_received_parser.set_defaults(func=handle_on_erc1155_batch_received) + + on_erc1155_received_parser = subcommands.add_parser("on-erc1155-received") + add_default_arguments(on_erc1155_received_parser, True) + on_erc1155_received_parser.add_argument( + "--arg1", required=True, help="Type: address" + ) + on_erc1155_received_parser.add_argument( + "--arg2", required=True, help="Type: address" + ) + on_erc1155_received_parser.add_argument( + "--arg3", required=True, help="Type: uint256", type=int + ) + on_erc1155_received_parser.add_argument( + "--arg4", required=True, help="Type: uint256", type=int + ) + on_erc1155_received_parser.add_argument( + "--arg5", required=True, help="Type: bytes", type=bytes_argument_type + ) + on_erc1155_received_parser.set_defaults(func=handle_on_erc1155_received) + + on_erc721_received_parser = subcommands.add_parser("on-erc721-received") + add_default_arguments(on_erc721_received_parser, True) + on_erc721_received_parser.add_argument( + "--arg1", required=True, help="Type: address" + ) + on_erc721_received_parser.add_argument( + "--arg2", required=True, help="Type: address" + ) + on_erc721_received_parser.add_argument( + "--arg3", required=True, help="Type: uint256", type=int + ) + on_erc721_received_parser.add_argument( + "--arg4", required=True, help="Type: bytes", type=bytes_argument_type + ) + on_erc721_received_parser.set_defaults(func=handle_on_erc721_received) + + set_correct_path_for_stage_parser = subcommands.add_parser( + "set-correct-path-for-stage" + ) + add_default_arguments(set_correct_path_for_stage_parser, True) + set_correct_path_for_stage_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + set_correct_path_for_stage_parser.add_argument( + "--stage", required=True, help="Type: uint256", type=int + ) + set_correct_path_for_stage_parser.add_argument( + "--path", required=True, help="Type: uint256", type=int + ) + set_correct_path_for_stage_parser.add_argument( + "--set-is-choosing-active", + required=True, + help="Type: bool", + type=boolean_argument_type, + ) + set_correct_path_for_stage_parser.set_defaults( + func=handle_set_correct_path_for_stage + ) + + set_session_active_parser = subcommands.add_parser("set-session-active") + add_default_arguments(set_session_active_parser, True) + set_session_active_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + set_session_active_parser.add_argument( + "--is-active", required=True, help="Type: bool", type=boolean_argument_type + ) + set_session_active_parser.set_defaults(func=handle_set_session_active) + + set_session_choosing_active_parser = subcommands.add_parser( + "set-session-choosing-active" + ) + add_default_arguments(set_session_choosing_active_parser, True) + set_session_choosing_active_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + set_session_choosing_active_parser.add_argument( + "--is-choosing-active", + required=True, + help="Type: bool", + type=boolean_argument_type, + ) + set_session_choosing_active_parser.set_defaults( + func=handle_set_session_choosing_active + ) + + set_session_uri_parser = subcommands.add_parser("set-session-uri") + add_default_arguments(set_session_uri_parser, True) + set_session_uri_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + set_session_uri_parser.add_argument( + "--uri", required=True, help="Type: string", type=str + ) + set_session_uri_parser.set_defaults(func=handle_set_session_uri) + + set_stage_rewards_parser = subcommands.add_parser("set-stage-rewards") + add_default_arguments(set_stage_rewards_parser, True) + set_stage_rewards_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + set_stage_rewards_parser.add_argument( + "--stages", required=True, help="Type: uint256[]", nargs="+" + ) + set_stage_rewards_parser.add_argument( + "--terminus-addresses", required=True, help="Type: address[]", nargs="+" + ) + set_stage_rewards_parser.add_argument( + "--terminus-pool-ids", required=True, help="Type: uint256[]", nargs="+" + ) + set_stage_rewards_parser.add_argument( + "--reward-amounts", required=True, help="Type: uint256[]", nargs="+" + ) + set_stage_rewards_parser.set_defaults(func=handle_set_stage_rewards) + + stake_tokens_into_session_parser = subcommands.add_parser( + "stake-tokens-into-session" + ) + add_default_arguments(stake_tokens_into_session_parser, True) + stake_tokens_into_session_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + stake_tokens_into_session_parser.add_argument( + "--token-ids", required=True, help="Type: uint256[]", nargs="+" + ) + stake_tokens_into_session_parser.set_defaults(func=handle_stake_tokens_into_session) + + supports_interface_parser = subcommands.add_parser("supports-interface") + add_default_arguments(supports_interface_parser, False) + supports_interface_parser.add_argument( + "--interface-id", required=True, help="Type: bytes4", type=bytes_argument_type + ) + supports_interface_parser.set_defaults(func=handle_supports_interface) + + token_of_staker_in_session_by_index_parser = subcommands.add_parser( + "token-of-staker-in-session-by-index" + ) + add_default_arguments(token_of_staker_in_session_by_index_parser, False) + token_of_staker_in_session_by_index_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + token_of_staker_in_session_by_index_parser.add_argument( + "--staker", required=True, help="Type: address" + ) + token_of_staker_in_session_by_index_parser.add_argument( + "--index", required=True, help="Type: uint256", type=int + ) + token_of_staker_in_session_by_index_parser.set_defaults( + func=handle_token_of_staker_in_session_by_index + ) + + unstake_tokens_from_session_parser = subcommands.add_parser( + "unstake-tokens-from-session" + ) + add_default_arguments(unstake_tokens_from_session_parser, True) + unstake_tokens_from_session_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + unstake_tokens_from_session_parser.add_argument( + "--token-ids", required=True, help="Type: uint256[]", nargs="+" + ) + unstake_tokens_from_session_parser.set_defaults( + func=handle_unstake_tokens_from_session + ) + + return parser + + +def main() -> None: + parser = generate_cli() + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/cli/enginecli/cli.py b/cli/enginecli/cli.py index ea253fb6..0290fd9d 100644 --- a/cli/enginecli/cli.py +++ b/cli/enginecli/cli.py @@ -5,11 +5,14 @@ core, drop, DropperFacet, + Dropper, Lootbox, MockErc20, + MockERC721, MockTerminus, setup_drop, CraftingFacet, + GOFPFacet, ) @@ -28,14 +31,20 @@ def main() -> None: core_parser = core.generate_cli() subparsers.add_parser("core", parents=[core_parser], add_help=False) - dropper_parser = DropperFacet.generate_cli() - subparsers.add_parser("dropper", parents=[dropper_parser], add_help=False) + dropper_parser = Dropper.generate_cli() + subparsers.add_parser("dropper-v1", parents=[dropper_parser], add_help=False) + + dropper_facet_parser = DropperFacet.generate_cli() + subparsers.add_parser("dropper", parents=[dropper_facet_parser], add_help=False) lootbox_parser = Lootbox.generate_cli() subparsers.add_parser("lootbox", parents=[lootbox_parser], add_help=False) erc20_parser = MockErc20.generate_cli() - subparsers.add_parser("mock-erc20", parents=[erc20_parser], add_help=False) + subparsers.add_parser("erc20", parents=[erc20_parser], add_help=False) + + erc721_parser = MockERC721.generate_cli() + subparsers.add_parser("erc721", parents=[erc721_parser], add_help=False) drop_parser = drop.generate_cli() subparsers.add_parser("drop", parents=[drop_parser], add_help=False) @@ -46,6 +55,9 @@ def main() -> None: crafting_parser = CraftingFacet.generate_cli() subparsers.add_parser("crafting", parents=[crafting_parser], add_help=False) + gofp_parser = GOFPFacet.generate_cli() + subparsers.add_parser("gofp", parents=[gofp_parser], add_help=False) + setup_drop_parser = setup_drop.generate_cli() subparsers.add_parser("setup-drop", parents=[setup_drop_parser], add_help=False) diff --git a/cli/enginecli/core.py b/cli/enginecli/core.py index eed43e22..7a7f38a4 100644 --- a/cli/enginecli/core.py +++ b/cli/enginecli/core.py @@ -22,6 +22,7 @@ OwnershipFacet, ReentrancyExploitable, CraftingFacet, + GOFPFacet, ) FACETS: Dict[str, Any] = { @@ -31,12 +32,16 @@ "OwnershipFacet": OwnershipFacet, "ReentrancyExploitable": ReentrancyExploitable, "CraftingFacet": CraftingFacet, + "GOFPFacet": GOFPFacet, } FACET_INIT_CALLDATA: Dict[str, str] = { "DropperFacet": lambda address, *args: DropperFacet.DropperFacet( address - ).contract.init.encode_input(*args) + ).contract.init.encode_input(*args), + "GOFPFacet": lambda address, *args: GOFPFacet.GOFPFacet( + address + ).contract.init.encode_input(*args), } DIAMOND_FACET_PRECEDENCE: List[str] = [ @@ -54,6 +59,7 @@ class EngineFeatures(Enum): DROPPER = "dropper" + GOFP = "GardenOfForkingPaths" def feature_from_facet_name(facet_name: str) -> Optional[EngineFeatures]: @@ -64,11 +70,13 @@ def feature_from_facet_name(facet_name: str) -> Optional[EngineFeatures]: FEATURE_FACETS: Dict[EngineFeatures, List[str]] = { - EngineFeatures.DROPPER: ["DropperFacet"] + EngineFeatures.DROPPER: ["DropperFacet"], + EngineFeatures.GOFP: ["GOFPFacet"], } FEATURE_IGNORES: Dict[EngineFeatures, List[str]] = { - EngineFeatures.DROPPER: {"methods": ["init"], "selectors": []} + EngineFeatures.DROPPER: {"methods": ["init"], "selectors": []}, + EngineFeatures.GOFP: {"methods": ["init"], "selectors": []}, } FACET_ACTIONS: Dict[str, int] = {"add": 0, "replace": 1, "remove": 2} @@ -550,6 +558,36 @@ def dropper_gogogo( return deployment_info +def gofp_gogogo( + admin_terminus_address: str, + admin_terminus_pool_id: int, + transaction_config: Dict[str, Any], +) -> Dict[str, Any]: + deployment_info = diamond_gogogo( + owner_address=transaction_config["from"].address, + transaction_config=transaction_config, + ) + + gofp_facet = GOFPFacet.GOFPFacet(None) + gofp_facet.deploy(transaction_config=transaction_config) + deployment_info["contracts"]["GOFPFacet"] = gofp_facet.address + + diamond_address = deployment_info["contracts"]["Diamond"] + facet_cut( + diamond_address, + "GOFPFacet", + gofp_facet.address, + "add", + transaction_config, + initializer_address=gofp_facet.address, + feature=EngineFeatures.GOFP, + initializer_args=[admin_terminus_address, admin_terminus_pool_id], + ) + deployment_info["attached"].append("GOFPFacet") + + return deployment_info + + def handle_facet_cut(args: argparse.Namespace) -> None: network.connect(args.network) diamond_address = args.address @@ -602,6 +640,18 @@ def handle_dropper_gogogo(args: argparse.Namespace) -> None: json.dump(result, sys.stdout, indent=4) +def handle_gofp_gogogo(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = MockTerminus.get_transaction_config(args) + result = gofp_gogogo( + args.admin_terminus_address, args.admin_terminus_pool_id, transaction_config + ) + if args.outfile is not None: + with args.outfile: + json.dump(result, args.outfile) + json.dump(result, sys.stdout, indent=4) + + def handle_crafting_gogogo(args: argparse.Namespace) -> None: network.connect(args.network) @@ -715,6 +765,32 @@ def generate_cli(): ) dropper_gogogo_parser.set_defaults(func=handle_dropper_gogogo) + gofp_gogogo_parser = subcommands.add_parser( + "gofp-gogogo", + help="Deploy gofp diamond contract", + description="Deploy gofp diamond contract", + ) + Diamond.add_default_arguments(gofp_gogogo_parser, transact=True) + gofp_gogogo_parser.add_argument( + "--admin-terminus-address", + required=True, + help="Address of Terminus contract defining access control for this GardenOfForkingPaths contract", + ) + gofp_gogogo_parser.add_argument( + "--admin-terminus-pool-id", + required=True, + type=int, + help="Pool ID of Terminus pool for administrators of this GardenOfForkingPaths contract", + ) + gofp_gogogo_parser.add_argument( + "-o", + "--outfile", + type=argparse.FileType("w"), + default=None, + help="(Optional) file to write deployed addresses to", + ) + gofp_gogogo_parser.set_defaults(func=handle_gofp_gogogo) + lootbox_gogogo_parser = subcommands.add_parser( "lootbox-gogogo", help="Deploys Lootbox contract", diff --git a/cli/enginecli/gas_profiler.py b/cli/enginecli/gas_profiler.py index 335afa61..51c0e00b 100644 --- a/cli/enginecli/gas_profiler.py +++ b/cli/enginecli/gas_profiler.py @@ -89,7 +89,7 @@ def gas_profile(): print("gas usages saved to gas_usages.json") -if os.environ.get("GAS_PROFILE") is not None: +if os.environ.get("GAS_PROFILE", "").lower() in ["y", "1", "t", "true", "yes", "da"]: print("gas profiling enabled") atexit.register(gas_profile) contract_abis = project_abis(".") diff --git a/cli/enginecli/setup_drop.py b/cli/enginecli/setup_drop.py index 504de50f..2c43be61 100644 --- a/cli/enginecli/setup_drop.py +++ b/cli/enginecli/setup_drop.py @@ -2,20 +2,78 @@ from brownie import network -from . import Dropper +from . import Dropper, MockTerminus -def setup_drop(args: argparse.Namespace) -> None: + +def handle_setup_drop(args: argparse.Namespace) -> None: network.connect(args.network) tx_config = Dropper.get_transaction_config(args) + claim_id, claim_info, claim_uri, signer_address = setup_drop( + args.address, + args.claim_id, + args.claim_type, + args.claim_address, + args.claim_pool_id, + args.claim_default_amount, + args.claim_uri, + args.signer_address, + args.use_pool_uri, + tx_config, + ) + + print(f"Set up claim: {claim_id} -- {claim_info}") + print(f"URI: {claim_uri}, Signer: {signer_address}") + + +def setup_drop( + address, + claim_id, + claim_type, + claim_address, + claim_pool_id, + claim_default_amount, + claim_uri, + signer_address, + use_pool_uri, + tx_config, +): + dropper = Dropper.Dropper(address) - dropper = Dropper.Dropper(args.address) + if claim_type == 1 or claim_type == 1155: + terminus = MockTerminus.MockTerminus(claim_address) + + if claim_type == 1: + dropper_is_approved_for_pool = terminus.is_approved_for_pool( + claim_pool_id, dropper.address + ) + if not dropper_is_approved_for_pool: + pool_approval_check = input( + f"Dropper contract {dropper.address} is not approved to mint tokens from Terminus pool {claim_pool_id} on {terminus.address}. Approve dropper? (y/N)" + ) + if pool_approval_check.strip().lower() == "y": + terminus.approve_for_pool(claim_pool_id, dropper.address, tx_config) + + if use_pool_uri: + if claim_uri is not None: + raise ValueError( + "You cannot use use_pool_uri and provide a claim_uri at the same time" + ) + else: + claim_uri = terminus.uri(claim_pool_id) - claim_id = args.claim_id if claim_id is None: - if args.claim_type is None or args.claim_address is None or args.claim_pool_id is None: - raise ValueError("Please specify the following arguments: --claim-type, --claim-address, --claim-pool-id") + if claim_type is None or claim_address is None or claim_pool_id is None: + raise ValueError( + "Please specify the following arguments: claim_type, claim_address, claim_pool_id" + ) - dropper.create_claim(args.claim_type, args.claim_address, args.claim_pool_id, args.claim_default_amount, tx_config) + dropper.create_claim( + claim_type, + claim_address, + claim_pool_id, + claim_default_amount, + tx_config, + ) claim_id = dropper.num_claims() @@ -24,13 +82,15 @@ def setup_drop(args: argparse.Namespace) -> None: if permission_check.strip().lower() != "y": raise Exception(f"You did not wish to proceed with setup of claim: {claim_id}") - if args.claim_uri: - dropper.set_claim_uri(claim_id, args.claim_uri, tx_config) - print(f"Claim ID: {claim_id}, claim URI: {args.claim_uri}") + if claim_uri: + dropper.set_claim_uri(claim_id, claim_uri, tx_config) + print(f"Claim ID: {claim_id}, claim URI: {claim_uri}") + + if signer_address: + dropper.set_signer_for_claim(claim_id, signer_address, tx_config) + print(f"Claim ID: {claim_id}, signer: {signer_address}") - if args.signer_address: - dropper.set_signer_for_claim(claim_id, args.signer_address, tx_config) - print(f"Claim ID: {claim_id}, signer: {args.signer_address}") + return claim_id, claim_info, claim_uri, signer_address def generate_cli() -> argparse.ArgumentParser: @@ -39,7 +99,10 @@ def generate_cli() -> argparse.ArgumentParser: ) Dropper.add_default_arguments(parser, True) parser.add_argument( - "--claim-id", type=int, help="Claim ID of drop to set up (if known)" + "--claim-id", + type=int, + default=None, + help="Claim ID of drop to set up (if known)", ) parser.add_argument("--claim-type", type=int, choices=[1, 20, 1155], default=None) parser.add_argument("--claim-address", help="Address of contract to claim from") @@ -49,7 +112,12 @@ def generate_cli() -> argparse.ArgumentParser: ) parser.add_argument("--claim-uri", help="Metadata URI for claim") parser.add_argument("--signer-address", help="Address for signer") - parser.set_defaults(func=setup_drop) + parser.add_argument( + "--use-pool-uri", + action="store_true", + help="For Terminus claim types, use the Terminus pool URI as the claim URI", + ) + parser.set_defaults(func=handle_setup_drop) return parser diff --git a/cli/enginecli/test_crafting.py b/cli/enginecli/test_crafting.py index 4442cd0a..638d2e2e 100644 --- a/cli/enginecli/test_crafting.py +++ b/cli/enginecli/test_crafting.py @@ -83,7 +83,7 @@ def setUpClass(cls) -> None: {"from": accounts[0]}, ) - cls.diamond_address = gogogo_result["Diamond"] + cls.diamond_address = gogogo_result["contracts"]["Diamond"] crafting_facet = CraftingFacet.CraftingFacet(None) crafting_facet.deploy({"from": accounts[0]}) diff --git a/cli/enginecli/test_dropper.py b/cli/enginecli/test_dropper.py index 1af108c8..97c4b892 100644 --- a/cli/enginecli/test_dropper.py +++ b/cli/enginecli/test_dropper.py @@ -362,7 +362,7 @@ def test_claim_erc20(self): claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -370,7 +370,7 @@ def test_claim_erc20(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -378,7 +378,7 @@ def test_claim_erc20(self): balance_dropper_0 = self.erc20_contract.balance_of(self.dropper.address) self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.erc20_contract.balance_of(accounts[1].address) balance_dropper_1 = self.erc20_contract.balance_of(self.dropper.address) @@ -398,7 +398,7 @@ def test_claim_erc20_with_custom_amount(self): MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -406,7 +406,7 @@ def test_claim_erc20_with_custom_amount(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, custom_reward + 1, claim_id, accounts[1].address, block_deadline, custom_reward ) signed_message = sign_message(message_hash, self.signer_0) @@ -414,6 +414,7 @@ def test_claim_erc20_with_custom_amount(self): balance_dropper_0 = self.erc20_contract.balance_of(self.dropper.address) self.dropper.claim( + 1, claim_id, block_deadline, custom_reward, @@ -432,7 +433,7 @@ def test_claim_erc20_fails_if_block_deadline_exceeded(self): claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -440,7 +441,7 @@ def test_claim_erc20_fails_if_block_deadline_exceeded(self): block_deadline = current_block - 1 message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -449,7 +450,7 @@ def test_claim_erc20_fails_if_block_deadline_exceeded(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.erc20_contract.balance_of(accounts[1].address) @@ -464,7 +465,7 @@ def test_claim_erc20_fails_if_incorrect_amount(self): claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -472,7 +473,7 @@ def test_claim_erc20_fails_if_incorrect_amount(self): block_deadline = current_block message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -481,6 +482,7 @@ def test_claim_erc20_fails_if_incorrect_amount(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( + 1, claim_id, block_deadline, reward + 1, @@ -500,7 +502,7 @@ def test_claim_erc20_fails_if_wrong_claimant(self): claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -508,7 +510,7 @@ def test_claim_erc20_fails_if_wrong_claimant(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -518,7 +520,7 @@ def test_claim_erc20_fails_if_wrong_claimant(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} ) balance_attacker_1 = self.erc20_contract.balance_of(accounts[2].address) @@ -535,7 +537,7 @@ def test_claim_erc20_fails_if_wrong_signer(self): claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -543,7 +545,7 @@ def test_claim_erc20_fails_if_wrong_signer(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_1) @@ -552,7 +554,7 @@ def test_claim_erc20_fails_if_wrong_signer(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} ) balance_claimant_1 = self.erc20_contract.balance_of(accounts[1].address) @@ -567,7 +569,7 @@ def test_claim_erc20_fails_on_repeated_attempts_with_same_signed_message(self): claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -575,7 +577,7 @@ def test_claim_erc20_fails_on_repeated_attempts_with_same_signed_message(self): block_deadline = current_block + 1000 message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -583,7 +585,7 @@ def test_claim_erc20_fails_on_repeated_attempts_with_same_signed_message(self): balance_dropper_0 = self.erc20_contract.balance_of(self.dropper.address) self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.erc20_contract.balance_of(accounts[1].address) balance_dropper_1 = self.erc20_contract.balance_of(self.dropper.address) @@ -593,7 +595,7 @@ def test_claim_erc20_fails_on_repeated_attempts_with_same_signed_message(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_2 = self.erc20_contract.balance_of(accounts[1].address) @@ -610,7 +612,7 @@ def test_claim_erc20_fails_on_repeated_attempts_with_different_signed_messages( claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -618,7 +620,7 @@ def test_claim_erc20_fails_on_repeated_attempts_with_different_signed_messages( block_deadline = current_block message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -626,7 +628,7 @@ def test_claim_erc20_fails_on_repeated_attempts_with_different_signed_messages( balance_dropper_0 = self.erc20_contract.balance_of(self.dropper.address) self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.erc20_contract.balance_of(accounts[1].address) balance_dropper_1 = self.erc20_contract.balance_of(self.dropper.address) @@ -638,13 +640,13 @@ def test_claim_erc20_fails_on_repeated_attempts_with_different_signed_messages( block_deadline = current_block message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_2 = self.erc20_contract.balance_of(accounts[1].address) @@ -664,7 +666,7 @@ def test_claim_erc20_fails_when_insufficient_balance(self): claim_id = self.create_drop_and_return_drop_id( 20, self.erc20_contract.address, 0, reward, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -672,7 +674,7 @@ def test_claim_erc20_fails_when_insufficient_balance(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -681,7 +683,7 @@ def test_claim_erc20_fails_when_insufficient_balance(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.erc20_contract.balance_of(accounts[1].address) @@ -698,7 +700,7 @@ def test_claim_erc721(self): claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -706,14 +708,14 @@ def test_claim_erc721(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) self.assertEqual(self.nft_contract.owner_of(token_id), self.dropper.address) self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) self.assertEqual(self.nft_contract.owner_of(token_id), accounts[1].address) @@ -724,7 +726,7 @@ def test_claim_erc721_with_custom_amount(self): claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -732,13 +734,14 @@ def test_claim_erc721_with_custom_amount(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, custom_amount + 1, claim_id, accounts[1].address, block_deadline, custom_amount ) signed_message = sign_message(message_hash, self.signer_0) self.assertEqual(self.nft_contract.owner_of(token_id), self.dropper.address) self.dropper.claim( + 1, claim_id, block_deadline, custom_amount, @@ -753,7 +756,7 @@ def test_claim_erc721_fails_if_block_deadline_exceeded(self): claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -761,7 +764,7 @@ def test_claim_erc721_fails_if_block_deadline_exceeded(self): block_deadline = current_block - 1 message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -769,7 +772,7 @@ def test_claim_erc721_fails_if_block_deadline_exceeded(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) self.assertEqual(self.nft_contract.owner_of(token_id), self.dropper.address) @@ -780,7 +783,7 @@ def test_claim_erc721_fails_if_incorrect_amount(self): claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -788,7 +791,7 @@ def test_claim_erc721_fails_if_incorrect_amount(self): block_deadline = current_block message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -796,7 +799,7 @@ def test_claim_erc721_fails_if_incorrect_amount(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 1, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 1, signed_message, {"from": accounts[1]} ) self.assertEqual(self.nft_contract.owner_of(token_id), self.dropper.address) @@ -807,7 +810,7 @@ def test_claim_erc721_fails_if_wrong_claimant(self): claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -815,7 +818,7 @@ def test_claim_erc721_fails_if_wrong_claimant(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -823,7 +826,7 @@ def test_claim_erc721_fails_if_wrong_claimant(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} ) self.assertEqual(self.nft_contract.owner_of(token_id), self.dropper.address) @@ -834,7 +837,7 @@ def test_claim_erc721_fails_if_wrong_signer(self): claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -842,7 +845,7 @@ def test_claim_erc721_fails_if_wrong_signer(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_1) @@ -850,7 +853,7 @@ def test_claim_erc721_fails_if_wrong_signer(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} ) self.assertEqual(self.nft_contract.owner_of(token_id), self.dropper.address) @@ -861,7 +864,7 @@ def test_claim_erc721_fails_on_repeated_attempts_with_same_signed_message(self): claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -869,14 +872,14 @@ def test_claim_erc721_fails_on_repeated_attempts_with_same_signed_message(self): block_deadline = current_block + 1000 message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) self.assertEqual(self.nft_contract.owner_of(token_id), self.dropper.address) self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) self.assertEqual(self.nft_contract.owner_of(token_id), accounts[1].address) @@ -892,7 +895,7 @@ def test_claim_erc721_fails_on_repeated_attempts_with_same_signed_message(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) self.assertEqual(self.nft_contract.owner_of(token_id), self.dropper.address) @@ -905,7 +908,7 @@ def test_claim_erc721_fails_on_repeated_attempts_with_different_signed_messages( claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -913,12 +916,12 @@ def test_claim_erc721_fails_on_repeated_attempts_with_different_signed_messages( block_deadline = current_block message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) self.assertEqual(self.nft_contract.owner_of(token_id), accounts[1].address) @@ -934,13 +937,13 @@ def test_claim_erc721_fails_on_repeated_attempts_with_different_signed_messages( block_deadline = current_block message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) self.assertEqual(self.nft_contract.owner_of(token_id), self.dropper.address) @@ -952,7 +955,7 @@ def test_claim_erc721_fails_if_dropper_not_owner(self): claim_id = self.create_drop_and_return_drop_id( 721, self.nft_contract.address, token_id, 1, MAX_UINT, {"from": accounts[0]} ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -960,13 +963,13 @@ def test_claim_erc721_fails_if_dropper_not_owner(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) self.assertNotEqual(self.nft_contract.owner_of(token_id), accounts[1].address) @@ -991,13 +994,13 @@ def test_claim_erc1155(self): MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) current_block = len(chain) block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) balance_claimant_0 = self.terminus.balance_of( @@ -1007,7 +1010,7 @@ def test_claim_erc1155(self): self.dropper.address, self.terminus_pool_id ) self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.terminus.balance_of( accounts[1].address, self.terminus_pool_id @@ -1036,13 +1039,13 @@ def test_claim_erc1155_with_custom_amount(self): MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) current_block = len(chain) block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, custom_reward + 1, claim_id, accounts[1].address, block_deadline, custom_reward ) signed_message = sign_message(message_hash, self.signer_0) balance_claimant_0 = self.terminus.balance_of( @@ -1052,6 +1055,7 @@ def test_claim_erc1155_with_custom_amount(self): self.dropper.address, self.terminus_pool_id ) self.dropper.claim( + 1, claim_id, block_deadline, custom_reward, @@ -1084,13 +1088,13 @@ def test_claim_erc1155_fails_if_block_deadline_exceeded(self): MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) current_block = len(chain) block_deadline = current_block - 1 message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) balance_claimant_0 = self.terminus.balance_of( @@ -1101,7 +1105,7 @@ def test_claim_erc1155_fails_if_block_deadline_exceeded(self): ) with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.terminus.balance_of( accounts[1].address, self.terminus_pool_id @@ -1129,13 +1133,13 @@ def test_claim_erc1155_fails_if_incorrect_amount(self): MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) current_block = len(chain) block_deadline = current_block message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) balance_claimant_0 = self.terminus.balance_of( @@ -1146,7 +1150,12 @@ def test_claim_erc1155_fails_if_incorrect_amount(self): ) with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, reward, signed_message, {"from": accounts[1]} + 1, + claim_id, + block_deadline, + reward, + signed_message, + {"from": accounts[1]}, ) balance_claimant_1 = self.terminus.balance_of( accounts[1].address, self.terminus_pool_id @@ -1174,13 +1183,13 @@ def test_claim_erc1155_fails_if_wrong_claimant(self): MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) current_block = len(chain) block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) balance_attacker_0 = self.terminus.balance_of( @@ -1194,7 +1203,7 @@ def test_claim_erc1155_fails_if_wrong_claimant(self): ) with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} ) balance_attacker_1 = self.terminus.balance_of( accounts[2].address, self.terminus_pool_id @@ -1226,13 +1235,13 @@ def test_claim_erc1155_fails_if_wrong_signer(self): MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) current_block = len(chain) block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_1) balance_claimant_0 = self.terminus.balance_of( @@ -1243,7 +1252,7 @@ def test_claim_erc1155_fails_if_wrong_signer(self): ) with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} ) balance_claimant_1 = self.terminus.balance_of( accounts[1].address, self.terminus_pool_id @@ -1271,13 +1280,13 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_same_signed_message(self) MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) current_block = len(chain) block_deadline = current_block + 1000 message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) balance_claimant_0 = self.terminus.balance_of( @@ -1287,7 +1296,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_same_signed_message(self) self.dropper.address, self.terminus_pool_id ) self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.terminus.balance_of( accounts[1].address, self.terminus_pool_id @@ -1299,7 +1308,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_same_signed_message(self) self.assertEqual(balance_dropper_1, balance_dropper_0 - reward) with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_2 = self.terminus.balance_of( accounts[1].address, self.terminus_pool_id @@ -1329,13 +1338,13 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_different_signed_messages MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) current_block = len(chain) block_deadline = current_block message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) balance_claimant_0 = self.terminus.balance_of( @@ -1345,7 +1354,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_different_signed_messages self.dropper.address, self.terminus_pool_id ) self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.terminus.balance_of( accounts[1].address, self.terminus_pool_id @@ -1358,12 +1367,12 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_different_signed_messages current_block = len(chain) block_deadline = current_block message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_2 = self.terminus.balance_of( accounts[1].address, self.terminus_pool_id @@ -1391,13 +1400,13 @@ def test_claim_erc1155_fails_when_insufficient_balance(self): MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) current_block = len(chain) block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) balance_claimant_0 = self.terminus.balance_of( @@ -1408,7 +1417,7 @@ def test_claim_erc1155_fails_when_insufficient_balance(self): ) with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.terminus.balance_of( accounts[1].address, self.terminus_pool_id @@ -1431,7 +1440,7 @@ def test_claim_erc1155(self): MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -1439,7 +1448,7 @@ def test_claim_erc1155(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -1452,7 +1461,7 @@ def test_claim_erc1155(self): ) self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) pool_supply_1 = self.terminus.terminus_pool_supply( @@ -1476,7 +1485,7 @@ def test_claim_erc1155_with_custom_amount(self): MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -1484,7 +1493,7 @@ def test_claim_erc1155_with_custom_amount(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, custom_reward + 1, claim_id, accounts[1].address, block_deadline, custom_reward ) signed_message = sign_message(message_hash, self.signer_0) @@ -1497,6 +1506,7 @@ def test_claim_erc1155_with_custom_amount(self): ) self.dropper.claim( + 1, claim_id, block_deadline, custom_reward, @@ -1535,7 +1545,7 @@ def test_claim_erc1155_fails_if_dropper_does_not_have_pool_control(self): MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -1543,7 +1553,7 @@ def test_claim_erc1155_fails_if_dropper_does_not_have_pool_control(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -1560,7 +1570,7 @@ def test_claim_erc1155_fails_if_dropper_does_not_have_pool_control(self): ) self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) pool_supply_1 = self.terminus.terminus_pool_supply( @@ -1591,13 +1601,13 @@ def test_claim_erc1155_fails_if_dropper_does_not_have_pool_control(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[2].address, block_deadline, 0 + 1, claim_id, accounts[2].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} ) pool_supply_2 = self.terminus.terminus_pool_supply( @@ -1621,7 +1631,7 @@ def test_claim_erc1155_fails_if_block_deadline_exceeded(self): {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -1629,7 +1639,7 @@ def test_claim_erc1155_fails_if_block_deadline_exceeded(self): block_deadline = current_block - 1 message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -1643,7 +1653,7 @@ def test_claim_erc1155_fails_if_block_deadline_exceeded(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.terminus.balance_of( @@ -1668,7 +1678,7 @@ def test_claim_erc1155_fails_if_incorrect_amount(self): {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -1676,7 +1686,7 @@ def test_claim_erc1155_fails_if_incorrect_amount(self): block_deadline = current_block message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -1690,7 +1700,7 @@ def test_claim_erc1155_fails_if_incorrect_amount(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 1, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 1, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.terminus.balance_of( @@ -1715,7 +1725,7 @@ def test_claim_erc1155_fails_if_wrong_claimant(self): MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -1723,7 +1733,7 @@ def test_claim_erc1155_fails_if_wrong_claimant(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -1740,7 +1750,7 @@ def test_claim_erc1155_fails_if_wrong_claimant(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} ) balance_attacker_1 = self.terminus.balance_of( @@ -1767,7 +1777,7 @@ def test_claim_erc1155_fails_if_wrong_signer(self): MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -1775,7 +1785,7 @@ def test_claim_erc1155_fails_if_wrong_signer(self): block_deadline = current_block # since blocks are 0-indexed message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_1) @@ -1788,7 +1798,7 @@ def test_claim_erc1155_fails_if_wrong_signer(self): with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[2]} ) balance_claimant_1 = self.terminus.balance_of( @@ -1812,7 +1822,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_same_signed_message(self) MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -1820,7 +1830,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_same_signed_message(self) block_deadline = current_block + 1000 message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -1832,7 +1842,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_same_signed_message(self) ) self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.terminus.balance_of( @@ -1848,7 +1858,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_same_signed_message(self) with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_2 = self.terminus.balance_of( @@ -1874,7 +1884,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_different_signed_messages MAX_UINT, {"from": accounts[0]}, ) - self.dropper.set_signer_for_claim( + self.dropper.set_signer_for_drop( claim_id, self.signer_0.address, {"from": accounts[0]} ) @@ -1882,7 +1892,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_different_signed_messages block_deadline = current_block message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) @@ -1895,7 +1905,7 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_different_signed_messages ) self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_1 = self.terminus.balance_of( @@ -1913,13 +1923,13 @@ def test_claim_erc1155_fails_on_repeated_attempts_with_different_signed_messages block_deadline = current_block message_hash = self.dropper.claim_message_hash( - claim_id, accounts[1].address, block_deadline, 0 + 1, claim_id, accounts[1].address, block_deadline, 0 ) signed_message = sign_message(message_hash, self.signer_0) with self.assertRaises(VirtualMachineError): self.dropper.claim( - claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} + 1, claim_id, block_deadline, 0, signed_message, {"from": accounts[1]} ) balance_claimant_2 = self.terminus.balance_of( diff --git a/cli/enginecli/test_gofp.py b/cli/enginecli/test_gofp.py new file mode 100644 index 00000000..3b5cc6c9 --- /dev/null +++ b/cli/enginecli/test_gofp.py @@ -0,0 +1,3490 @@ +import unittest + +from brownie import accounts, network, web3 as web3_client, ZERO_ADDRESS +from brownie.exceptions import VirtualMachineError +from moonworm.watch import _fetch_events_chunk + +from . import GOFPFacet, MockTerminus, MockErc20, MockERC721 +from .core import gofp_gogogo + +MAX_UINT = 2**256 - 1 + +SESSION_CREATED_EVENT_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256", + }, + { + "indexed": True, + "internalType": "address", + "name": "playerTokenAddress", + "type": "address", + }, + { + "indexed": True, + "internalType": "address", + "name": "paymentTokenAddress", + "type": "address", + }, + { + "indexed": False, + "internalType": "uint256", + "name": "paymentAmount", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "string", + "name": "uri", + "type": "string", + }, + { + "indexed": False, + "internalType": "bool", + "name": "active", + "type": "bool", + }, + { + "indexed": False, + "internalType": "bool", + "name": "isForgiving", + "type": "bool", + }, + ], + "name": "SessionCreated", + "type": "event", +} + +SESSION_ACTIVATED_EVENT_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "bool", + "name": "active", + "type": "bool", + }, + ], + "name": "SessionActivated", + "type": "event", +} + +SESSION_CHOOSING_ACTIVATED_EVENT_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "bool", + "name": "isChoosingActive", + "type": "bool", + }, + ], + "name": "SessionChoosingActivated", + "type": "event", +} + +SESSION_URI_CHANGED_EVENT_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256", + }, + {"indexed": False, "internalType": "string", "name": "uri", "type": "string"}, + ], + "name": "SessionUriChanged", + "type": "event", +} + +PATH_REGISTERED_EVENT_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "uint256", + "name": "stage", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "uint256", + "name": "path", + "type": "uint256", + }, + ], + "name": "PathRegistered", + "type": "event", +} + +STAGE_REWARD_CHANGED_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256", + }, + { + "indexed": True, + "internalType": "uint256", + "name": "stage", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "address", + "name": "terminusAddress", + "type": "address", + }, + { + "indexed": False, + "internalType": "uint256", + "name": "terminusPoolId", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "uint256", + "name": "rewardAmount", + "type": "uint256", + }, + ], + "name": "StageRewardChanged", + "type": "event", +} + +PATH_CHOSEN_EVENT_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256", + }, + { + "indexed": True, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + { + "indexed": True, + "internalType": "uint256", + "name": "stage", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "uint256", + "name": "path", + "type": "uint256", + }, + ], + "name": "PathChosen", + "type": "event", +} + +ERC1155_TRANSFER_SINGLE_EVENT = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "operator", + "type": "address", + }, + {"indexed": True, "internalType": "address", "name": "from", "type": "address"}, + {"indexed": True, "internalType": "address", "name": "to", "type": "address"}, + {"indexed": False, "internalType": "uint256", "name": "id", "type": "uint256"}, + { + "indexed": False, + "internalType": "uint256", + "name": "value", + "type": "uint256", + }, + ], + "name": "TransferSingle", + "type": "event", +} + + +class GOFPTestCase(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + try: + network.connect() + except: + pass + + cls.owner = accounts[0] + cls.owner_tx_config = {"from": cls.owner} + + cls.game_master = accounts[1] + cls.player = accounts[2] + cls.random_person = accounts[3] + + cls.nft = MockERC721.MockERC721(None) + cls.nft.deploy(cls.owner_tx_config) + + cls.terminus = MockTerminus.MockTerminus(None) + cls.terminus.deploy(cls.owner_tx_config) + + cls.payment_token = MockErc20.MockErc20(None) + cls.payment_token.deploy("lol", "lol", cls.owner_tx_config) + + cls.terminus.set_payment_token(cls.payment_token.address, cls.owner_tx_config) + cls.terminus.set_pool_base_price(1, cls.owner_tx_config) + + cls.payment_token.mint(cls.owner.address, 999999, cls.owner_tx_config) + + cls.payment_token.approve( + cls.terminus.address, 2**256 - 1, cls.owner_tx_config + ) + + cls.terminus.create_pool_v1(1, False, True, cls.owner_tx_config) + cls.admin_pool_id = cls.terminus.total_pools() + + cls.terminus.create_pool_v1(MAX_UINT, True, True, cls.owner_tx_config) + cls.reward_pool_id = cls.terminus.total_pools() + + # It is important for some of the tests that the owner of the contract *not* be the game master. + cls.terminus.mint( + cls.game_master.address, cls.admin_pool_id, 1, "", cls.owner_tx_config + ) + + # Mint NFTs and ERC20 tokens to player + for i in range(1, 6): + cls.nft.mint(cls.player.address, i, {"from": cls.owner}) + + cls.payment_token.mint(cls.player.address, 10**6, {"from": cls.owner}) + + cls.deployed_contracts = gofp_gogogo( + cls.terminus.address, cls.admin_pool_id, cls.owner_tx_config + ) + cls.gofp = GOFPFacet.GOFPFacet(cls.deployed_contracts["contracts"]["Diamond"]) + + # Set gofp as approved pool operator for reward pool + cls.terminus.approve_for_pool( + cls.reward_pool_id, cls.gofp.address, cls.owner_tx_config + ) + + def test_admin_terminus_info(self): + terminus_info = self.gofp.admin_terminus_info() + self.assertEqual(terminus_info[0], self.terminus.address) + self.assertEqual(terminus_info[1], self.admin_pool_id) + + +class TestAdminFlow(GOFPTestCase): + def test_create_session_then_get_session_active(self): + num_sessions_0 = self.gofp.num_sessions() + + expected_payment_amount = 42 + expected_uri = ( + "https://example.com/test_create_session_then_get_session_active.json" + ) + expected_stages = (5, 5, 3, 3, 2) + expected_is_active = True + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + expected_payment_amount, + expected_is_active, + expected_uri, + expected_stages, + False, + {"from": self.game_master}, + ) + + num_sessions_1 = self.gofp.num_sessions() + self.assertEqual(num_sessions_1, num_sessions_0 + 1) + + session_id = num_sessions_1 + + session = self.gofp.get_session(session_id) + + ( + nft_address, + payment_address, + payment_amount, + is_active, + is_choosing_active, + uri, + stages, + is_forgiving, + ) = session + + self.assertEqual(nft_address, self.nft.address) + self.assertEqual(payment_address, self.payment_token.address) + self.assertEqual(payment_amount, expected_payment_amount) + self.assertTrue(is_active) + self.assertEqual(uri, expected_uri) + self.assertEqual(stages, expected_stages) + self.assertEqual(is_forgiving, False) + self.assertEqual(is_choosing_active, True) + + def test_create_session_then_get_session_inactive(self): + num_sessions_0 = self.gofp.num_sessions() + + expected_payment_amount = 43 + expected_uri = ( + "https://example.com/test_create_session_then_get_session_inactive.json" + ) + expected_stages = (5, 5, 3) + expected_is_active = False + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + expected_payment_amount, + expected_is_active, + expected_uri, + expected_stages, + False, + {"from": self.game_master}, + ) + + num_sessions_1 = self.gofp.num_sessions() + self.assertEqual(num_sessions_1, num_sessions_0 + 1) + + session_id = num_sessions_1 + + session = self.gofp.get_session(session_id) + + ( + nft_address, + payment_address, + payment_amount, + is_active, + is_choosing_active, + uri, + stages, + is_forgiving, + ) = session + + self.assertEqual(nft_address, self.nft.address) + self.assertEqual(payment_address, self.payment_token.address) + self.assertEqual(payment_amount, expected_payment_amount) + self.assertFalse(is_active) + self.assertEqual(uri, expected_uri) + self.assertEqual(stages, expected_stages) + self.assertEqual(is_forgiving, False) + self.assertEqual(is_choosing_active, True) + + def test_create_forgiving_session_then_get_session_active(self): + num_sessions_0 = self.gofp.num_sessions() + + expected_payment_amount = 42 + expected_uri = ( + "https://example.com/test_create_session_then_get_session_active.json" + ) + expected_stages = (5, 5, 3, 3, 2) + expected_is_active = True + expected_is_forgiving = True + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + expected_payment_amount, + expected_is_active, + expected_uri, + expected_stages, + expected_is_forgiving, + {"from": self.game_master}, + ) + + num_sessions_1 = self.gofp.num_sessions() + self.assertEqual(num_sessions_1, num_sessions_0 + 1) + + session_id = num_sessions_1 + + session = self.gofp.get_session(session_id) + + ( + nft_address, + payment_address, + payment_amount, + is_active, + is_choosing_active, + uri, + stages, + is_forgiving, + ) = session + + self.assertEqual(nft_address, self.nft.address) + self.assertEqual(payment_address, self.payment_token.address) + self.assertEqual(payment_amount, expected_payment_amount) + self.assertTrue(is_active) + self.assertEqual(uri, expected_uri) + self.assertEqual(stages, expected_stages) + self.assertEqual(is_forgiving, expected_is_forgiving) + self.assertEqual(is_choosing_active, True) + + def test_create_free_session(self): + num_sessions_0 = self.gofp.num_sessions() + + expected_uri = "https://example.com/test_create_free_session.json" + expected_stages = (5, 5, 3) + expected_is_active = False + + with self.assertRaises(VirtualMachineError): + self.gofp.create_session( + self.nft.address, + ZERO_ADDRESS, + 1, + expected_is_active, + expected_uri, + expected_stages, + False, + {"from": self.game_master}, + ) + + self.gofp.create_session( + self.nft.address, + ZERO_ADDRESS, + 0, + expected_is_active, + expected_uri, + expected_stages, + False, + {"from": self.game_master}, + ) + + num_sessions_1 = self.gofp.num_sessions() + self.assertEqual(num_sessions_1, num_sessions_0 + 1) + + session_id = num_sessions_1 + + session = self.gofp.get_session(session_id) + + ( + nft_address, + payment_address, + payment_amount, + is_active, + is_choosing_active, + uri, + stages, + is_forgiving, + ) = session + + self.assertEqual(nft_address, self.nft.address) + self.assertEqual(payment_address, ZERO_ADDRESS) + self.assertEqual(payment_amount, 0) + self.assertFalse(is_active) + self.assertEqual(uri, expected_uri) + self.assertEqual(stages, expected_stages) + self.assertEqual(is_forgiving, False) + self.assertEqual(is_choosing_active, True) + + def test_cannot_create_session_as_player(self): + num_sessions_0 = self.gofp.num_sessions() + + failed_payment_amount = 44 + failed_uri = "https://example.com/test_cannot_create_session_as_player.json" + failed_stages = (5, 5) + failed_is_active = True + + with self.assertRaises(VirtualMachineError): + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + failed_payment_amount, + failed_is_active, + failed_uri, + failed_stages, + False, + {"from": self.player}, + ) + + num_sessions_1 = self.gofp.num_sessions() + self.assertEqual(num_sessions_1, num_sessions_0) + + def test_cannot_create_session_as_contract_owner_who_is_not_game_master(self): + num_sessions_0 = self.gofp.num_sessions() + + failed_payment_amount = 44 + failed_uri = "https://example.com/test_cannot_create_session_as_contract_owner_who_is_not_game_master.json" + failed_stages = (5, 5) + failed_is_active = True + + with self.assertRaises(VirtualMachineError): + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + failed_payment_amount, + failed_is_active, + failed_uri, + failed_stages, + False, + {"from": self.owner}, + ) + + num_sessions_1 = self.gofp.num_sessions() + self.assertEqual(num_sessions_1, num_sessions_0) + + def test_create_session_fires_events(self): + expected_payment_amount = 60 + expected_uri = "https://example.com/test_create_session_fires_events.json" + expected_stages = (5,) + expected_is_active = True + expected_is_forgiving = True + + tx_receipt = self.gofp.create_session( + self.nft.address, + self.payment_token.address, + expected_payment_amount, + expected_is_active, + expected_uri, + expected_stages, + expected_is_forgiving, + {"from": self.game_master}, + ) + + session_created_events = _fetch_events_chunk( + web3_client, + SESSION_CREATED_EVENT_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + + self.assertEqual(len(session_created_events), 1) + + self.assertEqual( + session_created_events[0]["args"]["sessionId"], self.gofp.num_sessions() + ) + self.assertEqual( + session_created_events[0]["args"]["playerTokenAddress"], self.nft.address + ) + self.assertEqual( + session_created_events[0]["args"]["paymentTokenAddress"], + self.payment_token.address, + ) + self.assertEqual( + session_created_events[0]["args"]["paymentAmount"], expected_payment_amount + ) + self.assertEqual(session_created_events[0]["args"]["uri"], expected_uri) + self.assertEqual( + session_created_events[0]["args"]["active"], expected_is_active + ) + self.assertEqual( + session_created_events[0]["args"]["isForgiving"], expected_is_forgiving + ) + + session_activated_events = _fetch_events_chunk( + web3_client, + SESSION_ACTIVATED_EVENT_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + self.assertEqual(len(session_activated_events), 1) + self.assertEqual( + session_activated_events[0]["args"]["sessionId"], self.gofp.num_sessions() + ) + self.assertEqual( + session_activated_events[0]["args"]["active"], expected_is_active + ) + + session_choosing_activated_events = _fetch_events_chunk( + web3_client, + SESSION_CHOOSING_ACTIVATED_EVENT_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + self.assertEqual(len(session_choosing_activated_events), 1) + self.assertEqual( + session_choosing_activated_events[0]["args"]["sessionId"], + self.gofp.num_sessions(), + ) + self.assertEqual( + session_choosing_activated_events[0]["args"]["isChoosingActive"], True + ) + + session_uri_changed_events = _fetch_events_chunk( + web3_client, + SESSION_URI_CHANGED_EVENT_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + self.assertEqual(len(session_uri_changed_events), 1) + self.assertEqual( + session_uri_changed_events[0]["args"]["sessionId"], self.gofp.num_sessions() + ) + self.assertEqual(session_uri_changed_events[0]["args"]["uri"], expected_uri) + + def test_can_change_session_info_as_game_master_and_not_as_random_person(self): + """ + Tests that game masters can correctly modify the following attributes of a session: + - isActive + - isChoosingActive + - uri + + Also tests that non game masters *cannot* call these methods. + + Also tests that the appropriate events are fired when the methods are called. + """ + payment_amount = 130 + uri = "https://example.com/test_can_change_session_info_as_game_master_and_not_as_random_person.json" + stages = (5, 5, 3) + is_active = False + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # We create a second session to ensure that the information modified for the first session is not + # being modified on the second session instead. This was a bug in a development version of the + # contract. + other_stages = (1, 1, 1) + other_active = False + other_uri = "https://example.com/lol.json" + self.gofp.create_session( + self.nft.address, + ZERO_ADDRESS, + 0, + other_active, + other_uri, + other_stages, + False, + {"from": self.game_master}, + ) + + _, _, _, is_active_0, is_choosing_active_0, uri_0, _, _ = self.gofp.get_session( + session_id + ) + self.assertFalse(is_active_0) + self.assertTrue(is_choosing_active_0) + self.assertEqual(uri_0, uri) + + # Sanity check: random person should not be game master: + self.assertEqual( + self.terminus.balance_of(self.random_person.address, self.admin_pool_id), 0 + ) + + # setSessionActive tests + session_active_tx_receipt_0 = self.gofp.set_session_active( + session_id, True, {"from": self.game_master} + ) + + _, _, _, is_active_1, _, _, _, _ = self.gofp.get_session(session_id) + self.assertTrue(is_active_1) + + session_activated_events_0 = _fetch_events_chunk( + web3_client, + SESSION_ACTIVATED_EVENT_ABI, + from_block=session_active_tx_receipt_0.block_number, + to_block=session_active_tx_receipt_0.block_number, + ) + self.assertEqual(len(session_activated_events_0), 1) + self.assertEqual(session_activated_events_0[0]["args"]["sessionId"], session_id) + self.assertEqual(session_activated_events_0[0]["args"]["active"], is_active_1) + + session_active_tx_receipt_1 = self.gofp.set_session_active( + session_id, False, {"from": self.game_master} + ) + + _, _, _, is_active_2, _, _, _, _ = self.gofp.get_session(session_id) + self.assertFalse(is_active_2) + + session_activated_events_1 = _fetch_events_chunk( + web3_client, + SESSION_ACTIVATED_EVENT_ABI, + from_block=session_active_tx_receipt_1.block_number, + to_block=session_active_tx_receipt_1.block_number, + ) + self.assertEqual(len(session_activated_events_1), 1) + self.assertEqual(session_activated_events_1[0]["args"]["sessionId"], session_id) + self.assertEqual(session_activated_events_1[0]["args"]["active"], is_active_2) + + session_active_tx_receipt_2 = self.gofp.set_session_active( + session_id, False, {"from": self.game_master} + ) + + _, _, _, is_active_3, _, _, _, _ = self.gofp.get_session(session_id) + self.assertFalse(is_active_3) + + session_activated_events_2 = _fetch_events_chunk( + web3_client, + SESSION_ACTIVATED_EVENT_ABI, + from_block=session_active_tx_receipt_2.block_number, + to_block=session_active_tx_receipt_2.block_number, + ) + self.assertEqual(len(session_activated_events_2), 1) + self.assertEqual(session_activated_events_2[0]["args"]["sessionId"], session_id) + self.assertEqual(session_activated_events_2[0]["args"]["active"], is_active_3) + + with self.assertRaises(VirtualMachineError): + self.gofp.set_session_active(session_id, True, {"from": self.random_person}) + + _, _, _, is_active_4, _, _, _, _ = self.gofp.get_session(session_id) + self.assertEqual(is_active_4, is_active_3) + + # setSessionChoosingActive tests + session_choosing_active_tx_receipt_0 = self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + + _, _, _, _, is_choosing_active_1, _, _, _ = self.gofp.get_session(session_id) + self.assertFalse(is_choosing_active_1) + + session_choosing_activated_events_0 = _fetch_events_chunk( + web3_client, + SESSION_CHOOSING_ACTIVATED_EVENT_ABI, + from_block=session_choosing_active_tx_receipt_0.block_number, + to_block=session_choosing_active_tx_receipt_0.block_number, + ) + self.assertEqual(len(session_choosing_activated_events_0), 1) + self.assertEqual( + session_choosing_activated_events_0[0]["args"]["sessionId"], session_id + ) + self.assertEqual( + session_choosing_activated_events_0[0]["args"]["isChoosingActive"], + is_choosing_active_1, + ) + + session_choosing_active_tx_receipt_1 = self.gofp.set_session_choosing_active( + session_id, True, {"from": self.game_master} + ) + + _, _, _, _, is_choosing_active_2, _, _, _ = self.gofp.get_session(session_id) + self.assertTrue(is_choosing_active_2) + + session_choosing_activated_events_1 = _fetch_events_chunk( + web3_client, + SESSION_CHOOSING_ACTIVATED_EVENT_ABI, + from_block=session_choosing_active_tx_receipt_1.block_number, + to_block=session_choosing_active_tx_receipt_1.block_number, + ) + self.assertEqual(len(session_choosing_activated_events_1), 1) + self.assertEqual( + session_choosing_activated_events_1[0]["args"]["sessionId"], session_id + ) + self.assertEqual( + session_choosing_activated_events_1[0]["args"]["isChoosingActive"], + is_choosing_active_2, + ) + + session_choosing_active_tx_receipt_2 = self.gofp.set_session_choosing_active( + session_id, True, {"from": self.game_master} + ) + + _, _, _, _, is_choosing_active_3, _, _, _ = self.gofp.get_session(session_id) + self.assertTrue(is_choosing_active_3) + + session_choosing_activated_events_2 = _fetch_events_chunk( + web3_client, + SESSION_CHOOSING_ACTIVATED_EVENT_ABI, + from_block=session_choosing_active_tx_receipt_2.block_number, + to_block=session_choosing_active_tx_receipt_2.block_number, + ) + self.assertEqual(len(session_choosing_activated_events_2), 1) + self.assertEqual( + session_choosing_activated_events_2[0]["args"]["sessionId"], session_id + ) + self.assertEqual( + session_choosing_activated_events_2[0]["args"]["isChoosingActive"], + is_choosing_active_3, + ) + + with self.assertRaises(VirtualMachineError): + self.gofp.set_session_choosing_active( + session_id, not is_choosing_active_3, {"from": self.random_person} + ) + + _, _, _, _, is_choosing_active_4, _, _, _ = self.gofp.get_session(session_id) + self.assertEqual(is_choosing_active_4, is_choosing_active_3) + + # setSessionUri tests + new_uri_0 = "https://example.com/new_uri_0.json" + session_uri_changed_tx_receipt_0 = self.gofp.set_session_uri( + session_id, new_uri_0, {"from": self.game_master} + ) + + _, _, _, _, _, uri_1, _, _ = self.gofp.get_session(session_id) + self.assertEqual(uri_1, new_uri_0) + + session_uri_changed_events_0 = _fetch_events_chunk( + web3_client, + SESSION_URI_CHANGED_EVENT_ABI, + from_block=session_uri_changed_tx_receipt_0.block_number, + to_block=session_uri_changed_tx_receipt_0.block_number, + ) + self.assertEqual(len(session_uri_changed_events_0), 1) + self.assertEqual( + session_uri_changed_events_0[0]["args"]["sessionId"], session_id + ) + self.assertEqual( + session_uri_changed_events_0[0]["args"]["uri"], + uri_1, + ) + + new_uri_1 = "https://example.com/new_uri_1.json" + session_uri_changed_tx_receipt_1 = self.gofp.set_session_uri( + session_id, new_uri_1, {"from": self.game_master} + ) + + _, _, _, _, _, uri_2, _, _ = self.gofp.get_session(session_id) + self.assertEqual(uri_2, new_uri_1) + + session_uri_changed_events_1 = _fetch_events_chunk( + web3_client, + SESSION_URI_CHANGED_EVENT_ABI, + from_block=session_uri_changed_tx_receipt_1.block_number, + to_block=session_uri_changed_tx_receipt_1.block_number, + ) + self.assertEqual(len(session_uri_changed_events_1), 1) + self.assertEqual( + session_uri_changed_events_1[0]["args"]["sessionId"], session_id + ) + self.assertEqual( + session_uri_changed_events_1[0]["args"]["uri"], + uri_2, + ) + + new_uri_2 = "https://example.com/new_uri_2.json" + session_uri_changed_tx_receipt_2 = self.gofp.set_session_uri( + session_id, new_uri_2, {"from": self.game_master} + ) + + _, _, _, _, _, uri_3, _, _ = self.gofp.get_session(session_id) + self.assertEqual(uri_3, new_uri_2) + + session_uri_changed_events_2 = _fetch_events_chunk( + web3_client, + SESSION_URI_CHANGED_EVENT_ABI, + from_block=session_uri_changed_tx_receipt_2.block_number, + to_block=session_uri_changed_tx_receipt_2.block_number, + ) + self.assertEqual(len(session_uri_changed_events_2), 1) + self.assertEqual( + session_uri_changed_events_2[0]["args"]["sessionId"], session_id + ) + self.assertEqual( + session_uri_changed_events_2[0]["args"]["uri"], + uri_3, + ) + + with self.assertRaises(VirtualMachineError): + self.gofp.set_session_uri( + session_id, f"{uri_3}/{uri_3}", {"from": self.random_person} + ) + + _, _, _, _, _, uri_4, _, _ = self.gofp.get_session(session_id) + self.assertEqual(uri_4, uri_3) + + # Check that the next session we created was not modified + ( + _, + _, + _, + other_session_active_final, + other_session_choosing_active_final, + other_session_uri_final, + _, + _, + ) = self.gofp.get_session(session_id + 1) + self.assertEqual(other_session_active_final, other_active) + self.assertTrue(other_session_choosing_active_final) + self.assertEqual(other_session_uri_final, other_uri) + + def test_game_master_can_register_path(self): + payment_amount = 131 + uri = "https://example.com/test_game_master_can_register_path.json" + stages = (5, 5, 3) + is_active = False + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + self.gofp.set_correct_path_for_stage( + session_id, 1, 1, False, {"from": self.game_master} + ) + + paths_0 = ( + self.gofp.get_correct_path_for_stage(session_id, 1), + self.gofp.get_correct_path_for_stage(session_id, 2), + self.gofp.get_correct_path_for_stage(session_id, 3), + ) + self.assertEqual(paths_0, (1, 0, 0)) + + self.gofp.set_correct_path_for_stage( + session_id, 2, 5, False, {"from": self.game_master} + ) + + paths_1 = ( + self.gofp.get_correct_path_for_stage(session_id, 1), + self.gofp.get_correct_path_for_stage(session_id, 2), + self.gofp.get_correct_path_for_stage(session_id, 3), + ) + self.assertEqual(paths_1, (1, 5, 0)) + + self.gofp.set_correct_path_for_stage( + session_id, 3, 3, False, {"from": self.game_master} + ) + paths_2 = ( + self.gofp.get_correct_path_for_stage(session_id, 1), + self.gofp.get_correct_path_for_stage(session_id, 2), + self.gofp.get_correct_path_for_stage(session_id, 3), + ) + self.assertEqual(paths_2, (1, 5, 3)) + + def test_non_game_master_cannot_register_path(self): + payment_amount = 131 + uri = "https://example.com/test_non_game_master_cannot_register_path.json" + stages = (5, 5, 3) + is_active = False + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + with self.assertRaises(VirtualMachineError): + self.gofp.set_correct_path_for_stage( + session_id, 1, 1, True, {"from": self.player} + ) + paths_0 = ( + self.gofp.get_correct_path_for_stage(session_id, 1), + self.gofp.get_correct_path_for_stage(session_id, 2), + self.gofp.get_correct_path_for_stage(session_id, 3), + ) + self.assertEqual(paths_0, (0, 0, 0)) + + def test_game_master_cannot_register_invalid_path(self): + payment_amount = 131 + uri = "https://example.com/test_game_master_cannot_register_invalid_path.json" + stages = (5, 5, 3) + is_active = False + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # Invalid session + with self.assertRaises(VirtualMachineError): + self.gofp.set_correct_path_for_stage( + session_id + 1, 1, 5, True, {"from": self.game_master} + ) + paths_0 = ( + self.gofp.get_correct_path_for_stage(session_id, 1), + self.gofp.get_correct_path_for_stage(session_id, 2), + self.gofp.get_correct_path_for_stage(session_id, 3), + ) + self.assertEqual(paths_0, (0, 0, 0)) + + # Invalid stage - one greater than number of stages + with self.assertRaises(VirtualMachineError): + self.gofp.set_correct_path_for_stage( + session_id, 4, 1, True, {"from": self.game_master} + ) + paths_1 = ( + self.gofp.get_correct_path_for_stage(session_id, 1), + self.gofp.get_correct_path_for_stage(session_id, 2), + self.gofp.get_correct_path_for_stage(session_id, 3), + ) + self.assertEqual(paths_1, (0, 0, 0)) + + # Invalid stage - stage 0 + with self.assertRaises(VirtualMachineError): + self.gofp.set_correct_path_for_stage( + session_id, 0, 1, True, {"from": self.game_master} + ) + + # Path must be >= 1 + with self.assertRaises(VirtualMachineError): + self.gofp.set_correct_path_for_stage( + session_id, 1, 0, True, {"from": self.game_master} + ) + paths_2 = ( + self.gofp.get_correct_path_for_stage(session_id, 1), + self.gofp.get_correct_path_for_stage(session_id, 2), + self.gofp.get_correct_path_for_stage(session_id, 3), + ) + self.assertEqual(paths_2, (0, 0, 0)) + + # Path must be <= number of choices for the given stage + with self.assertRaises(VirtualMachineError): + self.gofp.set_correct_path_for_stage( + session_id, 1, 6, True, {"from": self.game_master} + ) + paths_3 = ( + self.gofp.get_correct_path_for_stage(session_id, 1), + self.gofp.get_correct_path_for_stage(session_id, 2), + self.gofp.get_correct_path_for_stage(session_id, 3), + ) + self.assertEqual(paths_3, (0, 0, 0)) + + def test_game_master_cannot_register_path_multiple_times_for_same_stage(self): + payment_amount = 131 + uri = "https://example.com/test_game_master_cannot_register_path_multiple_times_for_same_stage.json" + stages = (5, 5, 3) + is_active = False + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + + self.gofp.set_correct_path_for_stage( + session_id, 1, 5, False, {"from": self.game_master} + ) + paths_0 = ( + self.gofp.get_correct_path_for_stage(session_id, 1), + self.gofp.get_correct_path_for_stage(session_id, 2), + self.gofp.get_correct_path_for_stage(session_id, 3), + ) + self.assertEqual(paths_0, (5, 0, 0)) + + with self.assertRaises(VirtualMachineError): + self.gofp.set_correct_path_for_stage( + session_id, 1, 4, False, {"from": self.game_master} + ) + paths_1 = ( + self.gofp.get_correct_path_for_stage(session_id, 1), + self.gofp.get_correct_path_for_stage(session_id, 2), + self.gofp.get_correct_path_for_stage(session_id, 3), + ) + self.assertEqual(paths_1, (5, 0, 0)) + + self.gofp.set_correct_path_for_stage( + session_id, 2, 3, False, {"from": self.game_master} + ) + paths_2 = ( + self.gofp.get_correct_path_for_stage(session_id, 1), + self.gofp.get_correct_path_for_stage(session_id, 2), + self.gofp.get_correct_path_for_stage(session_id, 3), + ) + self.assertEqual(paths_2, (5, 3, 0)) + + def test_register_path_fires_event(self): + payment_amount = 131 + uri = "https://example.com/test_register_path_fires_event.json" + stages = (5, 5, 3) + is_active = False + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + + tx_receipt = self.gofp.set_correct_path_for_stage( + session_id, 1, 5, False, {"from": self.game_master} + ) + events = _fetch_events_chunk( + web3_client, + PATH_REGISTERED_EVENT_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + + self.assertEqual(len(events), 1) + + self.assertEqual(events[0]["args"]["sessionId"], self.gofp.num_sessions()) + self.assertEqual(events[0]["args"]["stage"], 1) + self.assertEqual(events[0]["args"]["path"], 5) + + def test_set_and_get_stage_rewards(self): + """ + Tests administrators' ability to set rewards for the stages in a session. Also tests anyone's + ability to view the rewards for a given stage in a given session. + + Test actions: + 1. Create active session + 2. Check rewards have default values + 3. Attempt to set rewards on active session should fail + 4. Check rewards still have default values + 5. Make session inactive + 6. Set rewards for all but the first stage + 7. Check that rewards were correctly set + 8. Check that the extend StageRewardChanged events are fired + """ + payment_amount = 132 + uri = "https://example.com/test_set_and_get_stage_rewards.json" + stages = (5, 5, 3, 3, 2) + is_active = True + + # Rewards should be associated with all but the first stage in the session. + stages_with_rewards = list(range(2, len(stages) + 1)) + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + for i in range(1, len(stages) + 1): + reward = self.gofp.get_stage_reward(session_id, i) + self.assertEqual(reward, (ZERO_ADDRESS, 0, 0)) + + with self.assertRaises(VirtualMachineError): + self.gofp.set_stage_rewards( + session_id, + stages_with_rewards, + [self.terminus.address for _ in stages_with_rewards], + [self.reward_pool_id for _ in stages_with_rewards], + [i + 1 for i, _ in enumerate(stages_with_rewards)], + {"from": self.game_master}, + ) + + for i in range(1, len(stages) + 1): + reward = self.gofp.get_stage_reward(session_id, i) + self.assertEqual(reward, (ZERO_ADDRESS, 0, 0)) + + self.gofp.set_session_active(session_id, False, {"from": self.game_master}) + + tx_receipt = self.gofp.set_stage_rewards( + session_id, + stages_with_rewards, + [self.terminus.address for _ in stages_with_rewards], + [self.reward_pool_id for _ in stages_with_rewards], + [i + 1 for i, _ in enumerate(stages_with_rewards)], + {"from": self.game_master}, + ) + + reward = self.gofp.get_stage_reward(session_id, 1) + self.assertEqual(reward, (ZERO_ADDRESS, 0, 0)) + + for i in range(2, len(stages) + 1): + reward = self.gofp.get_stage_reward(session_id, i) + self.assertEqual( + reward, (self.terminus.address, self.reward_pool_id, i - 1) + ) + + events = _fetch_events_chunk( + web3_client, + STAGE_REWARD_CHANGED_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + + self.assertEqual(len(events), len(stages_with_rewards)) + self.assertListEqual( + [event["args"]["sessionId"] for event in events], + [session_id for _ in stages_with_rewards], + ) + self.assertListEqual( + [event["args"]["stage"] for event in events], + [stage for stage in stages_with_rewards], + ) + self.assertListEqual( + [event["args"]["terminusAddress"] for event in events], + [self.terminus.address for _ in stages_with_rewards], + ) + self.assertListEqual( + [event["args"]["terminusPoolId"] for event in events], + [self.reward_pool_id for _ in stages_with_rewards], + ) + self.assertListEqual( + [event["args"]["rewardAmount"] for event in events], + [i + 1 for i, _ in enumerate(stages_with_rewards)], + ) + + def test_non_game_master_cannot_set_stage_rewards(self): + """ + Tests that non game master accounts cannot set stage rewards on an inactive session. + + Test actions: + 1. Create inactive session + 2. Check that player is not a game master (i.e. does not have game master badge) + 3. Attempt by player to set rewards on active session should fail + 4. Check rewards still have default values + """ + payment_amount = 133 + uri = "https://example.com/test_non_game_master_cannot_set_stage_rewards.json" + stages = (5, 5, 3, 3, 2) + is_active = False + + # Rewards should be associated with all but the first stage in the session. + stages_with_rewards = list(range(2, len(stages) + 1)) + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + self.assertEqual( + self.terminus.balance_of(self.player.address, self.admin_pool_id), 0 + ) + + with self.assertRaises(VirtualMachineError): + self.gofp.set_stage_rewards( + session_id, + stages_with_rewards, + [self.terminus.address for _ in stages_with_rewards], + [self.reward_pool_id for _ in stages_with_rewards], + [i + 1 for i, _ in enumerate(stages_with_rewards)], + {"from": self.player}, + ) + + for i in range(1, len(stages) + 1): + reward = self.gofp.get_stage_reward(session_id, i) + self.assertEqual(reward, (ZERO_ADDRESS, 0, 0)) + + +class TestPlayerFlow(GOFPTestCase): + def test_player_can_stake_and_unstake_nfts(self): + payment_amount = 131 + uri = "https://example.com/test_player_can_stake_and_unstake_nfts.json" + stages = (5, 5, 3) + is_active = True + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + for token_id in token_ids: + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, 0) + self.assertEqual(owner, ZERO_ADDRESS) + + num_staked_0 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_0 = self.nft.balance_of(self.player.address) + num_owned_by_contract_0 = self.nft.balance_of(self.gofp.address) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + num_staked_1 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_1 = self.nft.balance_of(self.player.address) + num_owned_by_contract_1 = self.nft.balance_of(self.gofp.address) + + self.assertEqual(num_staked_1, num_staked_0 + len(token_ids)) + self.assertEqual(num_owned_by_player_1, num_owned_by_player_0 - len(token_ids)) + self.assertEqual( + num_owned_by_contract_1, num_owned_by_contract_0 + len(token_ids) + ) + + for token_id in token_ids: + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, session_id) + self.assertEqual(owner, self.player.address) + + for i, token_id in enumerate(token_ids): + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + session_id, + self.player.address, + num_staked_1 - len(token_ids) + 1 + i, + ), + token_ids[i], + ) + self.assertEqual(self.nft.owner_of(token_id), self.gofp.address) + + self.gofp.set_session_active(session_id, False, {"from": self.game_master}) + + unstaked_token_ids = [token_ids[i] for i in range(1, len(token_ids) - 1)] + + self.gofp.unstake_tokens_from_session( + session_id, unstaked_token_ids, {"from": self.player} + ) + + num_staked_2 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_2 = self.nft.balance_of(self.player.address) + num_owned_by_contract_2 = self.nft.balance_of(self.gofp.address) + + self.assertEqual(num_staked_2, num_staked_1 - len(unstaked_token_ids)) + self.assertEqual( + num_owned_by_player_2, num_owned_by_player_1 + len(unstaked_token_ids) + ) + self.assertEqual( + num_owned_by_contract_2, num_owned_by_contract_1 - len(unstaked_token_ids) + ) + + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_ids[0] + ) + self.assertEqual(staked_session_id, session_id) + self.assertEqual(owner, self.player.address) + + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_ids[-1] + ) + self.assertEqual(staked_session_id, session_id) + self.assertEqual(owner, self.player.address) + + for token_id in unstaked_token_ids: + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, 0) + self.assertEqual(owner, ZERO_ADDRESS) + + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + session_id, self.player.address, num_staked_2 - 1 + ), + token_ids[0], + ) + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + session_id, self.player.address, num_staked_2 + ), + token_ids[-1], + ) + for i in range(len(unstaked_token_ids)): + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + session_id, self.player.address, num_staked_2 + i + 1 + ), + 0, + ) + + self.assertEqual(self.nft.owner_of(token_ids[0]), self.gofp.address) + self.assertEqual(self.nft.owner_of(token_ids[-1]), self.gofp.address) + for token_id in unstaked_token_ids: + self.assertEqual(self.nft.owner_of(token_id), self.player.address) + + def test_player_can_stake_into_free_session(self): + """ + Tests that, when a player stakes their tokens into a session which has no payment token set, + the stake operation works without any ERC20 transfer events. + """ + uri = "https://example.com/test_player_can_stake_into_free_session.json" + stages = (5, 5, 3) + is_active = True + + self.gofp.create_session( + self.nft.address, + ZERO_ADDRESS, + 0, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + for token_id in token_ids: + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, 0) + self.assertEqual(owner, ZERO_ADDRESS) + + num_staked_0 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_0 = self.nft.balance_of(self.player.address) + num_owned_by_contract_0 = self.nft.balance_of(self.gofp.address) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + num_staked_1 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_1 = self.nft.balance_of(self.player.address) + num_owned_by_contract_1 = self.nft.balance_of(self.gofp.address) + + self.assertEqual(num_staked_1, num_staked_0 + len(token_ids)) + self.assertEqual(num_owned_by_player_1, num_owned_by_player_0 - len(token_ids)) + self.assertEqual( + num_owned_by_contract_1, num_owned_by_contract_0 + len(token_ids) + ) + + for token_id in token_ids: + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, session_id) + self.assertEqual(owner, self.player.address) + + for i, token_id in enumerate(token_ids): + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + session_id, + self.player.address, + num_staked_1 - len(token_ids) + 1 + i, + ), + token_ids[i], + ) + self.assertEqual(self.nft.owner_of(token_id), self.gofp.address) + + def test_player_transfers_payment_token_on_staking(self): + """ + Tests that, when a player stakes their tokens into a session which has payment token set, + they also transfer the payment amount for each NFT they are staking. + """ + payment_amount = 231 + uri = "https://example.com/test_player_transfers_payment_token_on_staking.json" + stages = (5, 5, 3) + is_active = True + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + for token_id in token_ids: + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, 0) + self.assertEqual(owner, ZERO_ADDRESS) + + num_staked_0 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_0 = self.nft.balance_of(self.player.address) + num_owned_by_contract_0 = self.nft.balance_of(self.gofp.address) + player_payment_token_balance_0 = self.payment_token.balance_of( + self.player.address + ) + gofp_payment_token_balance_0 = self.payment_token.balance_of(self.gofp.address) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + num_staked_1 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_1 = self.nft.balance_of(self.player.address) + num_owned_by_contract_1 = self.nft.balance_of(self.gofp.address) + player_payment_token_balance_1 = self.payment_token.balance_of( + self.player.address + ) + gofp_payment_token_balance_1 = self.payment_token.balance_of(self.gofp.address) + + self.assertEqual(num_staked_1, num_staked_0 + len(token_ids)) + self.assertEqual(num_owned_by_player_1, num_owned_by_player_0 - len(token_ids)) + self.assertEqual( + num_owned_by_contract_1, num_owned_by_contract_0 + len(token_ids) + ) + self.assertEqual( + player_payment_token_balance_1, + player_payment_token_balance_0 - num_nfts * payment_amount, + ) + self.assertEqual( + gofp_payment_token_balance_1, + gofp_payment_token_balance_0 + num_nfts * payment_amount, + ) + + for token_id in token_ids: + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, session_id) + self.assertEqual(owner, self.player.address) + + for i, token_id in enumerate(token_ids): + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + session_id, + self.player.address, + num_staked_1 - len(token_ids) + 1 + i, + ), + token_ids[i], + ) + self.assertEqual(self.nft.owner_of(token_id), self.gofp.address) + + def test_random_person_cannot_stake_player_nfts(self): + """ + Tests that a person cannot stake NFTs into a session which they do not own. + Even if the person who owns those NFTs has given their approval to the Garden of Forking Paths + contract. + """ + payment_amount = 232 + uri = "https://example.com/test_random_person_cannot_stake_player_nfts.json" + stages = (5, 5, 3) + is_active = True + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.random_person.address, + len(token_ids) * payment_amount, + {"from": self.owner}, + ) + + self.payment_token.approve( + self.gofp.address, MAX_UINT, {"from": self.random_person} + ) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + self.nft.set_approval_for_all( + self.gofp.address, True, {"from": self.random_person} + ) + + num_staked_0 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_0 = self.nft.balance_of(self.player.address) + num_owned_by_contract_0 = self.nft.balance_of(self.gofp.address) + player_payment_token_balance_0 = self.payment_token.balance_of( + self.player.address + ) + gofp_payment_token_balance_0 = self.payment_token.balance_of(self.gofp.address) + random_person_payment_token_balance_0 = self.payment_token.balance_of( + self.random_person.address + ) + + with self.assertRaises(VirtualMachineError): + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.random_person} + ) + + num_staked_1 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_1 = self.nft.balance_of(self.player.address) + num_owned_by_contract_1 = self.nft.balance_of(self.gofp.address) + player_payment_token_balance_1 = self.payment_token.balance_of( + self.player.address + ) + gofp_payment_token_balance_1 = self.payment_token.balance_of(self.gofp.address) + random_person_payment_token_balance_1 = self.payment_token.balance_of( + self.random_person.address + ) + + self.assertEqual(num_staked_1, num_staked_0) + self.assertEqual(num_owned_by_player_1, num_owned_by_player_0) + self.assertEqual(num_owned_by_contract_1, num_owned_by_contract_0) + self.assertEqual(player_payment_token_balance_1, player_payment_token_balance_0) + self.assertEqual(gofp_payment_token_balance_1, gofp_payment_token_balance_0) + self.assertEqual( + random_person_payment_token_balance_1, random_person_payment_token_balance_0 + ) + + def test_random_person_cannot_unstake_nfts(self): + payment_amount = 1337 + uri = "https://example.com/test_random_person_cannot_unstake_nfts.json" + stages = (5, 5, 3) + is_active = True + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + num_staked_0 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_0 = self.nft.balance_of(self.player.address) + num_owned_by_contract_0 = self.nft.balance_of(self.gofp.address) + num_owned_by_random_0 = self.nft.balance_of(self.random_person.address) + + self.gofp.set_session_active(session_id, False, {"from": self.game_master}) + + with self.assertRaises(VirtualMachineError): + self.gofp.unstake_tokens_from_session( + session_id, token_ids, {"from": self.random_person} + ) + + num_staked_1 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_1 = self.nft.balance_of(self.player.address) + num_owned_by_contract_1 = self.nft.balance_of(self.gofp.address) + num_owned_by_random_1 = self.nft.balance_of(self.random_person.address) + + self.assertEqual(num_staked_1, num_staked_0) + self.assertEqual(num_owned_by_player_1, num_owned_by_player_0) + self.assertEqual(num_owned_by_contract_1, num_owned_by_contract_0) + self.assertEqual(num_owned_by_random_1, num_owned_by_random_0) + + for token_id in token_ids: + staked_session_id, staker = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, session_id) + self.assertEqual(staker, self.player.address) + + def test_player_cannot_stake_into_inactive_session(self): + payment_amount = 133 + uri = "https://example.com/test_player_cannot_stake_into_inactive_session.json" + stages = (5, 5, 3) + is_active = True + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + self.gofp.set_session_active(session_id, False, {"from": self.game_master}) + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + for token_id in token_ids: + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, 0) + self.assertEqual(owner, ZERO_ADDRESS) + + num_staked_0 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_0 = self.nft.balance_of(self.player.address) + num_owned_by_contract_0 = self.nft.balance_of(self.gofp.address) + + with self.assertRaises(VirtualMachineError): + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + num_staked_1 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_1 = self.nft.balance_of(self.player.address) + num_owned_by_contract_1 = self.nft.balance_of(self.gofp.address) + + self.assertEqual(num_staked_1, num_staked_0) + self.assertEqual(num_owned_by_player_1, num_owned_by_player_0) + self.assertEqual(num_owned_by_contract_1, num_owned_by_contract_0) + + for token_id in token_ids: + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, 0) + self.assertEqual(owner, ZERO_ADDRESS) + + for i, token_id in enumerate(token_ids): + self.assertEqual(self.nft.owner_of(token_id), self.player.address) + + def test_player_can_unstake_from_active_session(self): + payment_amount = 135 + uri = "https://example.com/test_player_can_unstake_from_active_session.json" + stages = (5, 5, 3) + is_active = True + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + for token_id in token_ids: + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, 0) + self.assertEqual(owner, ZERO_ADDRESS) + + num_staked_0 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_0 = self.nft.balance_of(self.player.address) + num_owned_by_contract_0 = self.nft.balance_of(self.gofp.address) + + for token_id in token_ids: + self.assertFalse( + self.gofp.get_session_token_stake_guard(session_id, token_id) + ) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + num_staked_1 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_1 = self.nft.balance_of(self.player.address) + num_owned_by_contract_1 = self.nft.balance_of(self.gofp.address) + + self.assertEqual(num_staked_1, num_staked_0 + len(token_ids)) + self.assertEqual(num_owned_by_player_1, num_owned_by_player_0 - len(token_ids)) + self.assertEqual( + num_owned_by_contract_1, num_owned_by_contract_0 + len(token_ids) + ) + + for token_id in token_ids: + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, session_id) + self.assertEqual(owner, self.player.address) + + self.assertTrue( + self.gofp.get_session_token_stake_guard(session_id, token_id) + ) + + for i, token_id in enumerate(token_ids): + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + session_id, + self.player.address, + num_staked_1 - len(token_ids) + 1 + i, + ), + token_ids[i], + ) + self.assertEqual(self.nft.owner_of(token_id), self.gofp.address) + + self.gofp.set_session_active(session_id, True, {"from": self.game_master}) + + unstaked_token_ids = [token_ids[i] for i in range(1, len(token_ids) - 1)] + + self.gofp.unstake_tokens_from_session( + session_id, unstaked_token_ids, {"from": self.player} + ) + + num_staked_2 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_2 = self.nft.balance_of(self.player.address) + num_owned_by_contract_2 = self.nft.balance_of(self.gofp.address) + + self.assertEqual(num_staked_2, num_staked_1 - len(unstaked_token_ids)) + self.assertEqual( + num_owned_by_player_2, num_owned_by_player_1 + len(unstaked_token_ids) + ) + self.assertEqual( + num_owned_by_contract_2, num_owned_by_contract_1 - len(unstaked_token_ids) + ) + + # First and last of the initially staked tokens should still be staked into the session in + # that order + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_ids[0] + ) + self.assertEqual(staked_session_id, session_id) + self.assertEqual(owner, self.player.address) + self.assertTrue(self.gofp.get_session_token_stake_guard(session_id, token_id)) + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + session_id, + self.player.address, + num_staked_0 + 1, + ), + token_ids[0], + ) + self.assertEqual(self.nft.owner_of(token_ids[0]), self.gofp.address) + + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_ids[-1] + ) + self.assertEqual(staked_session_id, session_id) + self.assertEqual(owner, self.player.address) + self.assertTrue(self.gofp.get_session_token_stake_guard(session_id, token_id)) + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + session_id, + self.player.address, + num_staked_0 + 2, + ), + token_ids[-1], + ) + self.assertEqual(self.nft.owner_of(token_ids[-1]), self.gofp.address) + + # Remaining tokens should be successfully unstaked + for i, token_id in enumerate(unstaked_token_ids): + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, 0) + self.assertEqual(owner, ZERO_ADDRESS) + + self.assertTrue( + self.gofp.get_session_token_stake_guard(session_id, token_id) + ) + + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + session_id, + self.player.address, + len(token_ids) - len(unstaked_token_ids) + 1 + i, + ), + 0, + ) + self.assertEqual(self.nft.owner_of(token_id), self.player.address) + + def test_player_can_make_a_choice_with_staked_nfts_at_first_stage(self): + payment_amount = 151 + uri = "https://example.com/test_player_can_make_a_choice_with_staked_nfts_at_first_stage.json" + stages = (5, 5, 3) + is_active = True + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + for token_id in token_ids: + first_stage_path = self.gofp.get_path_choice(session_id, token_id, 1) + self.assertEqual(first_stage_path, 0) + + tx_receipt = self.gofp.choose_current_stage_paths( + session_id, + token_ids, + [token_id % stages[0] + 1 for token_id in token_ids], + {"from": self.player}, + ) + + for token_id in token_ids: + first_stage_path = self.gofp.get_path_choice(session_id, token_id, 1) + self.assertEqual(first_stage_path, token_id % stages[0] + 1) + + # Check if PathChosen events were fired for each token AND that they were fired in the expected + # order. Events should be fired for each token in the order that the token was passed to the + # chooseCurrentStagePaths method on the contract. + events = _fetch_events_chunk( + web3_client, + PATH_CHOSEN_EVENT_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + + self.assertEqual(len(events), len(token_ids)) + for i, event in enumerate(events): + self.assertEqual(event["args"]["sessionId"], session_id) + self.assertEqual(event["args"]["tokenId"], token_ids[i]) + self.assertEqual(event["args"]["stage"], 1) + self.assertEqual(event["args"]["path"], token_ids[i] % stages[0] + 1) + + def test_player_cannot_make_a_choice_if_session_choosing_inactive(self): + payment_amount = 153 + uri = "https://example.com/test_player_cannot_make_a_choice_if_session_choosing_inactive.json" + stages = (5, 5, 3) + is_active = True + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + for token_id in token_ids: + first_stage_path = self.gofp.get_path_choice(session_id, token_id, 1) + self.assertEqual(first_stage_path, 0) + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + + with self.assertRaises(VirtualMachineError): + self.gofp.choose_current_stage_paths( + session_id, + token_ids, + [token_id % stages[0] + 1 for token_id in token_ids], + {"from": self.player}, + ) + + for token_id in token_ids: + first_stage_path = self.gofp.get_path_choice(session_id, token_id, 1) + self.assertEqual(first_stage_path, 0) + + def test_random_person_cannot_make_a_choice_with_player_nfts(self): + payment_amount = 155 + uri = "https://example.com/test_random_person_cannot_make_a_choice_with_player_nfts.json" + stages = (5, 5, 3) + is_active = True + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + for token_id in token_ids: + first_stage_path = self.gofp.get_path_choice(session_id, token_id, 1) + self.assertEqual(first_stage_path, 0) + + with self.assertRaises(VirtualMachineError): + self.gofp.choose_current_stage_paths( + session_id, + token_ids, + [token_id % stages[0] + 1 for token_id in token_ids], + {"from": self.random_person}, + ) + + for token_id in token_ids: + first_stage_path = self.gofp.get_path_choice(session_id, token_id, 1) + self.assertEqual(first_stage_path, 0) + + def test_player_can_make_a_choice_with_only_surviving_staked_nfts_at_second_stage( + self, + ): + payment_amount = 170 + uri = "https://example.com/test_player_can_make_a_choice_with_only_surviving_staked_nfts_at_second_stage.json" + stages = (5, 5, 3) + is_active = True + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + # First half (rounded down) of tokens will make the correct choice. Rest will make an incorrect + # choice. + # Correct path: 3 + first_stage_correct_path = 3 + first_stage_incorrect_path = 2 + num_correct = int(num_nfts / 2) + first_stage_path_choices = [first_stage_correct_path] * num_correct + [ + first_stage_incorrect_path + ] * (num_nfts - num_correct) + + self.gofp.choose_current_stage_paths( + session_id, + token_ids, + first_stage_path_choices, + {"from": self.player}, + ) + + expected_correct_tokens = [] + expected_incorrect_tokens = [] + + for i, token_id in enumerate(token_ids): + first_stage_path = self.gofp.get_path_choice(session_id, token_ids[i], 1) + if i < num_correct: + self.assertEqual(first_stage_path, first_stage_correct_path) + expected_correct_tokens.append(token_id) + else: + self.assertEqual(first_stage_path, first_stage_incorrect_path) + expected_incorrect_tokens.append(token_id) + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + self.gofp.set_correct_path_for_stage( + session_id, 1, first_stage_correct_path, True, {"from": self.game_master} + ) + + # Check that current stage has progressed to stage 2 + self.assertEqual(self.gofp.get_current_stage(session_id), 2) + + self.gofp.choose_current_stage_paths( + session_id, + expected_correct_tokens, + [1 for _ in expected_correct_tokens], + {"from": self.player}, + ) + + for token_id in expected_incorrect_tokens: + with self.assertRaises(VirtualMachineError): + self.gofp.choose_current_stage_paths( + session_id, + [token_id], + [2], + {"from": self.player}, + ) + + for token_id in expected_correct_tokens: + self.assertEqual(self.gofp.get_path_choice(session_id, token_id, 2), 1) + + for token_id in expected_incorrect_tokens: + self.assertEqual(self.gofp.get_path_choice(session_id, token_id, 2), 0) + + def test_forgiving_session_player_can_make_a_choice_with_any_staked_nfts_at_second_stage( + self, + ): + """ + Checks that, in a forgiving session, even NFTs which made the incorrect choice in the previous + stage can make a choice in the current stage. + + Also checks that the NFTs which did make incorrect choices in the previous stage are *not* rewarded + for making a choice in the current stage. + """ + payment_amount = 1170 + uri = "https://example.com/test_forgiving_session_player_can_make_a_choice_with_any_staked_nfts_at_second_stage.json" + stages = (5, 5, 3) + is_active = False + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + True, # this session is forgiving + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # Set stage reward for stage 2 + reward_amount = 7 + self.gofp.set_stage_rewards( + session_id, + [2], + [self.terminus.address], + [self.reward_pool_id], + [reward_amount], + {"from": self.game_master}, + ) + + self.gofp.set_session_active(session_id, True, {"from": self.game_master}) + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + # First half (rounded down) of tokens will make the correct choice. Rest will make an incorrect + # choice. + # Correct path: 3 + first_stage_correct_path = 3 + first_stage_incorrect_path = 2 + num_correct = int(num_nfts / 2) + first_stage_path_choices = [first_stage_correct_path] * num_correct + [ + first_stage_incorrect_path + ] * (num_nfts - num_correct) + + self.gofp.choose_current_stage_paths( + session_id, + token_ids, + first_stage_path_choices, + {"from": self.player}, + ) + + expected_correct_tokens = [] + expected_incorrect_tokens = [] + + for i, token_id in enumerate(token_ids): + first_stage_path = self.gofp.get_path_choice(session_id, token_ids[i], 1) + if i < num_correct: + self.assertEqual(first_stage_path, first_stage_correct_path) + expected_correct_tokens.append(token_id) + else: + self.assertEqual(first_stage_path, first_stage_incorrect_path) + expected_incorrect_tokens.append(token_id) + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + self.gofp.set_correct_path_for_stage( + session_id, 1, first_stage_correct_path, True, {"from": self.game_master} + ) + + # Check that current stage has progressed to stage 2 + self.assertEqual(self.gofp.get_current_stage(session_id), 2) + + player_reward_token_balance_0 = self.terminus.balance_of( + self.player.address, self.reward_pool_id + ) + + self.gofp.choose_current_stage_paths( + session_id, + token_ids, + [1 for _ in token_ids], + {"from": self.player}, + ) + + for token_id in token_ids: + self.assertEqual(self.gofp.get_path_choice(session_id, token_id, 2), 1) + + player_reward_token_balance_1 = self.terminus.balance_of( + self.player.address, self.reward_pool_id + ) + + self.assertEqual( + player_reward_token_balance_1, + player_reward_token_balance_0 + + len(expected_correct_tokens) * reward_amount, + ) + + def test_player_cannot_make_choice_in_inactive_session( + self, + ): + """ + Tests that a player cannot make a choice in an inactive session. + + Sets up a multi-stage session and allows player to make choices at stage 1. + After setting the correct path at stage 1, marks the session as inactive and checks that + player cannot make a choice at stage 2. + Player has at least one token which made the correct stage 1 choice. + """ + payment_amount = 174 + uri = "https://example.com/test_player_cannot_make_choice_in_inactive_session.json" + stages = (5, 5, 3) + is_active = True + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + # First half (rounded down) of tokens will make the correct choice. Rest will make an incorrect + # choice. + # Correct path: 3 + first_stage_correct_path = 3 + first_stage_incorrect_path = 2 + num_correct = int(num_nfts / 2) + first_stage_path_choices = [first_stage_correct_path] * num_correct + [ + first_stage_incorrect_path + ] * (num_nfts - num_correct) + + self.gofp.choose_current_stage_paths( + session_id, + token_ids, + first_stage_path_choices, + {"from": self.player}, + ) + + expected_correct_tokens = [] + expected_incorrect_tokens = [] + + for i, token_id in enumerate(token_ids): + first_stage_path = self.gofp.get_path_choice(session_id, token_ids[i], 1) + if i < num_correct: + self.assertEqual(first_stage_path, first_stage_correct_path) + expected_correct_tokens.append(token_id) + else: + self.assertEqual(first_stage_path, first_stage_incorrect_path) + expected_incorrect_tokens.append(token_id) + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + self.gofp.set_correct_path_for_stage( + session_id, 1, first_stage_correct_path, True, {"from": self.game_master} + ) + + # Check that current stage has progressed to stage 2 + self.assertEqual(self.gofp.get_current_stage(session_id), 2) + + # Mark session as inactive + self.gofp.set_session_active(session_id, False, {"from": self.game_master}) + + with self.assertRaises(VirtualMachineError): + self.gofp.choose_current_stage_paths( + session_id, + expected_correct_tokens, + [1 for _ in expected_correct_tokens], + {"from": self.player}, + ) + + for token_id in expected_correct_tokens: + self.assertEqual(self.gofp.get_path_choice(session_id, token_id, 2), 0) + + def test_player_is_rewarded_for_making_a_choice_in_stages_that_have_rewards( + self, + ): + """ + Tests that reward distribution works correctly when a player chooses a path in a stage that + does have an associated reward. + + Also tests that no rewards are distributed when a player chooses a path in a stage that does + not have an assocaited reward. + + Test actions: + 1. Create inactive session + 2. Associate a reward with stage 2 + 3. Activate session. + 4. Player chooses paths in stage 1 + 5. Check that no ERC1155 Transfer events fired in that transaction + 6. Player chooses paths in stage 2 + 7. Check that appropriate ERC1155 Transfer event fired in that transaction + """ + payment_amount = 175 + uri = "https://example.com/test_player_is_rewarded_for_making_a_choice_in_stages_that_have_rewards.json" + stages = (5, 5, 3) + is_active = False + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + reward_amount = 5 + self.gofp.set_stage_rewards( + session_id, + [2], + [self.terminus.address], + [self.reward_pool_id], + [reward_amount], + {"from": self.game_master}, + ) + + self.gofp.set_session_active(session_id, True, {"from": self.game_master}) + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + # First half (rounded down) of tokens will make the correct choice. Rest will make an incorrect + # choice. + # Correct path: 3 + first_stage_correct_path = 3 + first_stage_incorrect_path = 2 + num_correct = int(num_nfts / 2) + first_stage_path_choices = [first_stage_correct_path] * num_correct + [ + first_stage_incorrect_path + ] * (num_nfts - num_correct) + + first_stage_tx_receipt = self.gofp.choose_current_stage_paths( + session_id, + token_ids, + first_stage_path_choices, + {"from": self.player}, + ) + + first_stage_events = _fetch_events_chunk( + web3_client, + ERC1155_TRANSFER_SINGLE_EVENT, + from_block=first_stage_tx_receipt.block_number, + to_block=first_stage_tx_receipt.block_number, + ) + + self.assertEqual(len(first_stage_events), 0) + + expected_correct_tokens = [] + + for i, token_id in enumerate(token_ids): + if i < num_correct: + expected_correct_tokens.append(token_id) + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + self.gofp.set_correct_path_for_stage( + session_id, 1, first_stage_correct_path, True, {"from": self.game_master} + ) + + # Check that current stage has progressed to stage 2 + self.assertEqual(self.gofp.get_current_stage(session_id), 2) + + second_stage_tx_receipt = self.gofp.choose_current_stage_paths( + session_id, + expected_correct_tokens, + [1 for _ in expected_correct_tokens], + {"from": self.player}, + ) + + second_stage_events = _fetch_events_chunk( + web3_client, + ERC1155_TRANSFER_SINGLE_EVENT, + from_block=second_stage_tx_receipt.block_number, + to_block=second_stage_tx_receipt.block_number, + ) + + self.assertEqual(len(second_stage_events), 1) + + erc1155_transfer_single_event = second_stage_events[0] + self.assertEqual(erc1155_transfer_single_event["args"]["from"], ZERO_ADDRESS) + self.assertEqual( + erc1155_transfer_single_event["args"]["to"], self.player.address + ) + self.assertEqual( + erc1155_transfer_single_event["args"]["id"], self.reward_pool_id + ) + self.assertEqual( + erc1155_transfer_single_event["args"]["value"], + reward_amount * len(expected_correct_tokens), + ) + + def test_player_cannnot_make_a_choice_with_same_token_twice(self): + payment_amount = 176 + uri = "https://example.com/test_player_cannnot_make_a_choice_with_same_token_twice.json" + stages = (5, 5, 3) + is_active = False + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + reward_amount = 6 + self.gofp.set_stage_rewards( + session_id, + [1], + [self.terminus.address], + [self.reward_pool_id], + [reward_amount], + {"from": self.game_master}, + ) + + self.gofp.set_session_active(session_id, True, {"from": self.game_master}) + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + for token_id in token_ids: + first_stage_path = self.gofp.get_path_choice(session_id, token_id, 1) + self.assertEqual(first_stage_path, 0) + + reward_balance_0 = self.terminus.balance_of( + self.player.address, self.reward_pool_id + ) + + self.gofp.choose_current_stage_paths( + session_id, + token_ids, + [token_id % stages[0] + 1 for token_id in token_ids], + {"from": self.player}, + ) + + reward_balance_1 = self.terminus.balance_of( + self.player.address, self.reward_pool_id + ) + self.assertEqual( + reward_balance_1, reward_balance_0 + len(token_ids) * reward_amount + ) + + for token_id in token_ids: + first_stage_path = self.gofp.get_path_choice(session_id, token_id, 1) + self.assertEqual(first_stage_path, token_id % stages[0] + 1) + + with self.assertRaises(VirtualMachineError): + self.gofp.choose_current_stage_paths( + session_id, + token_ids, + [token_id % stages[0] + 1 for token_id in token_ids], + {"from": self.player}, + ) + + reward_balance_2 = self.terminus.balance_of( + self.player.address, self.reward_pool_id + ) + self.assertEqual(reward_balance_2, reward_balance_1) + + def test_player_can_unstake_from_active_session_not_restake_into_same_session_but_stake_into_new_session( + self, + ): + """ + This tests that players can unstake and restake into an inactive session. It tests for regression + of a bug that existed in a development version of this contract. + """ + payment_amount = 177 + uri = "https://example.com/test_player_can_unstake_from_active_session_not_restake_into_same_session_but_stake_into_new_session.json" + stages = (5, 5, 3) + is_active = False + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + reward_amount = 6 + self.gofp.set_stage_rewards( + session_id, + [1], + [self.terminus.address], + [self.reward_pool_id], + [reward_amount], + {"from": self.game_master}, + ) + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + num_nfts = 5 + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + self.gofp.set_session_active(session_id, True, {"from": self.game_master}) + + num_staked_0 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_0 = self.nft.balance_of(self.player.address) + num_owned_by_contract_0 = self.nft.balance_of(self.gofp.address) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + num_staked_1 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_1 = self.nft.balance_of(self.player.address) + num_owned_by_contract_1 = self.nft.balance_of(self.gofp.address) + + self.assertEqual(num_staked_1, num_staked_0 + len(token_ids)) + self.assertEqual(num_owned_by_player_1, num_owned_by_player_0 - len(token_ids)) + self.assertEqual( + num_owned_by_contract_1, num_owned_by_contract_0 + len(token_ids) + ) + + for token_id in token_ids: + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, session_id) + self.assertEqual(owner, self.player.address) + + for i, token_id in enumerate(token_ids): + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + session_id, + self.player.address, + num_staked_1 - len(token_ids) + 1 + i, + ), + token_ids[i], + ) + self.assertEqual(self.nft.owner_of(token_id), self.gofp.address) + + # Now we check that unstaking and restaking doesn't override path choices at previous stage + # 1 is the correct choice + path_choices = [1] + [2 for _ in token_ids[1:]] + self.gofp.choose_current_stage_paths( + session_id, token_ids, path_choices, {"from": self.player} + ) + + self.gofp.unstake_tokens_from_session( + session_id, token_ids, {"from": self.player} + ) + + num_staked_2 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_2 = self.nft.balance_of(self.player.address) + num_owned_by_contract_2 = self.nft.balance_of(self.gofp.address) + + self.assertEqual(num_staked_2, num_staked_0) + self.assertEqual(num_owned_by_player_2, num_owned_by_player_0) + self.assertEqual(num_owned_by_contract_2, num_owned_by_contract_0) + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + session_id, self.player.address, 1 + ), + 0, + ) + + for i, token_id in enumerate(token_ids): + staked_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_session_id, 0) + self.assertEqual(owner, ZERO_ADDRESS) + + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + session_id, + self.player.address, + num_staked_2 + 1 + i, + ), + 0, + ) + self.assertEqual(self.nft.owner_of(token_id), self.player.address) + + with self.assertRaises(VirtualMachineError): + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + num_staked_3 = self.gofp.num_tokens_staked_into_session( + session_id, self.player.address + ) + num_owned_by_player_3 = self.nft.balance_of(self.player.address) + num_owned_by_contract_3 = self.nft.balance_of(self.gofp.address) + + self.assertEqual(num_staked_3, num_staked_2) + self.assertEqual(num_owned_by_player_3, num_owned_by_player_2) + self.assertEqual(num_owned_by_contract_3, num_owned_by_contract_2) + + self.assertEqual(self.gofp.get_path_choice(session_id, token_ids[0], 1), 1) + for token_id in token_ids[1:]: + self.assertEqual(self.gofp.get_path_choice(session_id, token_id, 1), 2) + + self.gofp.create_session( + self.nft.address, + ZERO_ADDRESS, + 0, + True, + "https://example.com/new_session.json", + stages, + True, + {"from": self.game_master}, + ) + + new_session_id = self.gofp.num_sessions() + + num_staked_new_0 = self.gofp.num_tokens_staked_into_session( + new_session_id, self.player.address + ) + num_owned_by_player_new_0 = self.nft.balance_of(self.player.address) + num_owned_by_contract_new_0 = self.nft.balance_of(self.gofp.address) + + self.gofp.stake_tokens_into_session( + new_session_id, token_ids, {"from": self.player} + ) + + num_staked_new_1 = self.gofp.num_tokens_staked_into_session( + new_session_id, self.player.address + ) + num_owned_by_player_new_1 = self.nft.balance_of(self.player.address) + num_owned_by_contract_new_1 = self.nft.balance_of(self.gofp.address) + + self.assertEqual(num_staked_new_1, num_staked_new_0 + len(token_ids)) + self.assertEqual( + num_owned_by_player_new_1, num_owned_by_player_new_0 - len(token_ids) + ) + self.assertEqual( + num_owned_by_contract_new_1, num_owned_by_contract_new_0 + len(token_ids) + ) + + for i, token_id in enumerate(token_ids): + staked_new_session_id, owner = self.gofp.get_staked_token_info( + self.nft.address, token_id + ) + self.assertEqual(staked_new_session_id, new_session_id) + self.assertEqual(owner, self.player.address) + + self.assertEqual( + self.gofp.token_of_staker_in_session_by_index( + new_session_id, + self.player.address, + num_staked_new_1 - len(token_ids) + 1 + i, + ), + token_ids[i], + ) + self.assertEqual(self.nft.owner_of(token_id), self.gofp.address) + + +class TestFullGames(GOFPTestCase): + # TODO(zomglings): Test the following functionality: + # - Test multiplayer game + def test_single_player_game(self): + payment_amount = 337 + uri = "https://example.com/test_single_player_game.json" + # NOTE: The test assumes that there are 3 stages. You can change the number of paths per change, + # but do not change the number of stages. + # The stage numbers also need to be coprime to each other - Chinese Remainder Theorem! + stages = (2, 3, 5) + correct_paths = (2, 1, 3) + is_active = False + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + False, + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # We set rewards for each stage + reward_amounts = [1, 2, 4] + self.gofp.set_stage_rewards( + session_id, + [1, 2, 3], + [self.terminus.address] * 3, + [self.reward_pool_id] * 3, + reward_amounts, + {"from": self.game_master}, + ) + self.gofp.set_session_active(session_id, True, {"from": self.game_master}) + + # We create a second session to ensure that the NFTs are being staked into the right session. + # There was a bug in a development version of the contract in which the staking function was + # checking if the most *recent* session was active instead of the session with the given sessionId. + # This means that, if the most recent function were inactive, players wouldn't be able to stake + # eligible tokens even into an active session. + self.gofp.create_session( + self.random_person.address, + ZERO_ADDRESS, + 0, + False, + "https://example.com/wrong_session.json", + (1,), + False, + {"from": self.game_master}, + ) + + # Player will distribute NFTs evenly across choices at every stage. So we give them enough NFTs + # to have a single winning NFT at the end of the game. + # So if stages = (2, 3, 5), num_nfts = 2 * 3 * 5 = 30. + num_nfts = 1 + for i in stages: + num_nfts *= i + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + # **Stage 1** + first_stage_path_choices = [token_id % stages[0] + 1 for token_id in token_ids] + first_stage_correct_token_ids = [ + token_id + for token_id in token_ids + if token_id % stages[0] + 1 == correct_paths[0] + ] + # Sanity check for test setup + self.assertEqual(len(first_stage_correct_token_ids), int(num_nfts / stages[0])) + + player_reward_token_balance_0 = self.terminus.balance_of( + self.player.address, self.reward_pool_id + ) + + self.gofp.choose_current_stage_paths( + session_id, + token_ids, + first_stage_path_choices, + {"from": self.player}, + ) + + player_reward_token_balance_1 = self.terminus.balance_of( + self.player.address, self.reward_pool_id + ) + self.assertEqual( + player_reward_token_balance_1, + player_reward_token_balance_0 + int(reward_amounts[0] * len(token_ids)), + ) + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + self.gofp.set_correct_path_for_stage( + session_id, 1, correct_paths[0], True, {"from": self.game_master} + ) + + # Check that current stage has progressed to stage 2 + self.assertEqual(self.gofp.get_current_stage(session_id), 2) + + for token_id in token_ids: + if token_id not in first_stage_correct_token_ids: + with self.assertRaises(VirtualMachineError): + self.gofp.choose_current_stage_paths( + session_id, + [token_id], + [1], + {"from": self.player}, + ) + + # **Stage 2** + second_stage_path_choices = [ + token_id % stages[1] + 1 for token_id in first_stage_correct_token_ids + ] + second_stage_correct_token_ids = [ + token_id + for token_id in first_stage_correct_token_ids + if token_id % stages[1] + 1 == correct_paths[1] + ] + + # Sanity check for test setup + self.assertEqual( + len(second_stage_correct_token_ids), int(num_nfts / (stages[0] * stages[1])) + ) + + self.gofp.choose_current_stage_paths( + session_id, + first_stage_correct_token_ids, + second_stage_path_choices, + {"from": self.player}, + ) + + player_reward_token_balance_2 = self.terminus.balance_of( + self.player.address, self.reward_pool_id + ) + self.assertEqual( + player_reward_token_balance_2, + player_reward_token_balance_1 + + int(reward_amounts[1] * len(token_ids) / stages[0]), + ) + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + self.gofp.set_correct_path_for_stage( + session_id, 2, correct_paths[1], True, {"from": self.game_master} + ) + + # Check that current stage has progressed to stage 3 + self.assertEqual(self.gofp.get_current_stage(session_id), 3) + + for token_id in token_ids: + if token_id not in second_stage_correct_token_ids: + with self.assertRaises(VirtualMachineError): + self.gofp.choose_current_stage_paths( + session_id, + [token_id], + [1], + {"from": self.player}, + ) + + # **Stage 3** + third_stage_path_choices = [ + token_id % stages[2] + 1 for token_id in second_stage_correct_token_ids + ] + + third_stage_correct_token_ids = [ + token_id + for token_id in second_stage_correct_token_ids + if token_id % stages[2] + 1 == correct_paths[2] + ] + # Sanity check for test setup + self.assertEqual(len(third_stage_correct_token_ids), 1) + + self.gofp.choose_current_stage_paths( + session_id, + second_stage_correct_token_ids, + third_stage_path_choices, + {"from": self.player}, + ) + + player_reward_token_balance_3 = self.terminus.balance_of( + self.player.address, self.reward_pool_id + ) + self.assertEqual( + player_reward_token_balance_3, + player_reward_token_balance_2 + + int(reward_amounts[2] * len(token_ids) / (stages[0] * stages[1])), + ) + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + self.gofp.set_correct_path_for_stage( + session_id, 3, correct_paths[2], True, {"from": self.game_master} + ) + + # Check that current stage has progressed to stage 4 + self.assertEqual(self.gofp.get_current_stage(session_id), 4) + + for token_id in token_ids: + with self.assertRaises(VirtualMachineError): + self.gofp.choose_current_stage_paths( + session_id, + [token_id], + [1], + {"from": self.player}, + ) + + def test_forgiving_single_player_game(self): + """ + This test works very similarly to test_single_player_game. The tokens that made the incorrect + choice on the previous stage all make the correct choice on the subsequent stage. + """ + payment_amount = 338 + uri = "https://example.com/test_forgiving_single_player_game.json" + # NOTE: The test assumes that there are 3 stages. You can change the number of paths per change, + # but do not change the number of stages. + # The stage numbers also need to be coprime to each other - Chinese Remainder Theorem! + stages = (2, 3, 5) + correct_paths = (2, 1, 3) + is_active = False + + self.gofp.create_session( + self.nft.address, + self.payment_token.address, + payment_amount, + is_active, + uri, + stages, + True, # this session is forgiving + {"from": self.game_master}, + ) + + session_id = self.gofp.num_sessions() + + # We set rewards for each stage + reward_amounts = [1, 2, 4] + self.gofp.set_stage_rewards( + session_id, + [1, 2, 3], + [self.terminus.address] * 3, + [self.reward_pool_id] * 3, + reward_amounts, + {"from": self.game_master}, + ) + self.gofp.set_session_active(session_id, True, {"from": self.game_master}) + + # We create a second session to ensure that the NFTs are being staked into the right session. + # There was a bug in a development version of the contract in which the staking function was + # checking if the most *recent* session was active instead of the session with the given sessionId. + # This means that, if the most recent function were inactive, players wouldn't be able to stake + # eligible tokens even into an active session. + self.gofp.create_session( + self.random_person.address, + ZERO_ADDRESS, + 0, + False, + "https://example.com/wrong_session.json", + (1,), + False, + {"from": self.game_master}, + ) + + # Player will distribute NFTs evenly across choices at every stage. So we give them enough NFTs + # to have a single winning NFT at the end of the game. + # So if stages = (2, 3, 5), num_nfts = 2 * 3 * 5 = 30. + num_nfts = 1 + for i in stages: + num_nfts *= i + + # Mint NFTs to the player + total_nfts = self.nft.total_supply() + + token_ids = [total_nfts + i for i in range(1, num_nfts + 1)] + + for token_id in token_ids: + self.nft.mint(self.player.address, token_id, {"from": self.owner}) + + # Mint num_tokens*payment_amount of payment_token to player + self.payment_token.mint( + self.player.address, len(token_ids) * payment_amount, {"from": self.owner} + ) + + self.payment_token.approve(self.gofp.address, MAX_UINT, {"from": self.player}) + self.nft.set_approval_for_all(self.gofp.address, True, {"from": self.player}) + + self.gofp.stake_tokens_into_session( + session_id, token_ids, {"from": self.player} + ) + + # **Stage 1** + first_stage_path_choices = [token_id % stages[0] + 1 for token_id in token_ids] + first_stage_correct_token_ids = [ + token_id + for token_id in token_ids + if token_id % stages[0] + 1 == correct_paths[0] + ] + # Sanity check for test setup + self.assertEqual(len(first_stage_correct_token_ids), int(num_nfts / stages[0])) + + player_reward_token_balance_0 = self.terminus.balance_of( + self.player.address, self.reward_pool_id + ) + + self.gofp.choose_current_stage_paths( + session_id, + token_ids, + first_stage_path_choices, + {"from": self.player}, + ) + + player_reward_token_balance_1 = self.terminus.balance_of( + self.player.address, self.reward_pool_id + ) + self.assertEqual( + player_reward_token_balance_1, + player_reward_token_balance_0 + int(reward_amounts[0] * len(token_ids)), + ) + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + self.gofp.set_correct_path_for_stage( + session_id, 1, correct_paths[0], True, {"from": self.game_master} + ) + + # Check that current stage has progressed to stage 2 + self.assertEqual(self.gofp.get_current_stage(session_id), 2) + + first_stage_incorrect_token_ids = [ + token_id + for token_id in token_ids + if token_id not in first_stage_correct_token_ids + ] + + # **Stage 2** + second_stage_path_choices = [ + token_id % stages[1] + 1 for token_id in first_stage_correct_token_ids + ] + [correct_paths[1] for _ in first_stage_incorrect_token_ids] + second_stage_correct_token_ids = [ + token_id + for token_id in first_stage_correct_token_ids + if token_id % stages[1] + 1 == correct_paths[1] + ] + first_stage_incorrect_token_ids + + # Sanity check for test setup + self.assertEqual( + len(second_stage_correct_token_ids), + int(num_nfts / (stages[0] * stages[1])) + + len(first_stage_incorrect_token_ids), + ) + + self.gofp.choose_current_stage_paths( + session_id, + first_stage_correct_token_ids + first_stage_incorrect_token_ids, + second_stage_path_choices, + {"from": self.player}, + ) + + player_reward_token_balance_2 = self.terminus.balance_of( + self.player.address, self.reward_pool_id + ) + self.assertEqual( + player_reward_token_balance_2, + player_reward_token_balance_1 + + reward_amounts[1] * int(len(token_ids) / stages[0]), + ) + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + self.gofp.set_correct_path_for_stage( + session_id, 2, correct_paths[1], True, {"from": self.game_master} + ) + + # Check that current stage has progressed to stage 3 + self.assertEqual(self.gofp.get_current_stage(session_id), 3) + + second_stage_incorrect_token_ids = [ + token_id + for token_id in token_ids + if token_id not in second_stage_correct_token_ids + ] + + # **Stage 3** + third_stage_path_choices = [ + token_id % stages[2] + 1 for token_id in second_stage_correct_token_ids + ] + [correct_paths[2] for _ in second_stage_incorrect_token_ids] + + third_stage_correct_token_ids = [ + token_id + for token_id in second_stage_correct_token_ids + if token_id % stages[2] + 1 == correct_paths[2] + ] + second_stage_incorrect_token_ids + # Sanity check for test setup + self.assertEqual( + len(third_stage_correct_token_ids), + int(len(second_stage_correct_token_ids) / stages[2]) + + len(second_stage_incorrect_token_ids), + ) + + self.gofp.choose_current_stage_paths( + session_id, + second_stage_correct_token_ids + second_stage_incorrect_token_ids, + third_stage_path_choices, + {"from": self.player}, + ) + + player_reward_token_balance_3 = self.terminus.balance_of( + self.player.address, self.reward_pool_id + ) + self.assertEqual( + player_reward_token_balance_3, + player_reward_token_balance_2 + + reward_amounts[2] * len(second_stage_correct_token_ids), + ) + + self.gofp.set_session_choosing_active( + session_id, False, {"from": self.game_master} + ) + self.gofp.set_correct_path_for_stage( + session_id, 3, correct_paths[2], True, {"from": self.game_master} + ) + + # Check that current stage has progressed to stage 4 + self.assertEqual(self.gofp.get_current_stage(session_id), 4) + + for token_id in token_ids: + with self.assertRaises(VirtualMachineError): + self.gofp.choose_current_stage_paths( + session_id, + [token_id], + [1], + {"from": self.player}, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cli/enginecli/test_random_lootbox.py b/cli/enginecli/test_random_lootbox.py index 01be0436..d0f33db1 100644 --- a/cli/enginecli/test_random_lootbox.py +++ b/cli/enginecli/test_random_lootbox.py @@ -1,13 +1,10 @@ from enum import Enum import unittest -from brownie import accounts, network +from brownie import accounts from brownie.exceptions import VirtualMachineError -from brownie.network import web3 as web3_client -from chainlink import MockChainlinkCoordinator, MockLinkToken, mock_vrf_oracle -from . import Lootbox, MockTerminus, MockErc20 -from .core import lootbox_item_to_tuple, lootbox_gogogo +from .core import lootbox_item_to_tuple from .test_lootbox import LootboxTestCase, LootboxTypes diff --git a/cli/test.sh b/cli/test.sh index 4ebecb5c..4efcfe63 100755 --- a/cli/test.sh +++ b/cli/test.sh @@ -6,6 +6,8 @@ set -e +GAS_PROFILE=${GAS_PROFILE:-y} + usage() { echo "Usage: $0" [TEST_SPEC ...] echo @@ -28,4 +30,4 @@ cd .. brownie compile cd - set -x -GAS_PROFILE="y" python -m unittest $TEST_COMMAND +GAS_PROFILE="$GAS_PROFILE" python -m unittest $TEST_COMMAND diff --git a/contracts/Dropper/DropperFacet.sol b/contracts/Dropper/DropperFacet.sol index 4a2650c5..985288b5 100644 --- a/contracts/Dropper/DropperFacet.sol +++ b/contracts/Dropper/DropperFacet.sol @@ -254,9 +254,8 @@ contract DropperFacet is LibDropper.DropperStorage storage ds = LibDropper.dropperStorage(); require( - ds.AmountClaimed[msg.sender][dropId] + amount <= - ds.MaxClaimable[dropId], - "Dropper: claim -- Claimant would exceed the maximum claimable number of tokens for this drop." + !ds.DropRequestClaimed[dropId][requestID], + "Dropper.claim: That (dropID, requestID) pair has already been claimed" ); bytes32 hash = claimMessageHash( @@ -281,6 +280,12 @@ contract DropperFacet is amount = claimToken.amount; } + require( + ds.AmountClaimed[msg.sender][dropId] + amount <= + ds.MaxClaimable[dropId], + "Dropper: claim -- Claimant would exceed the maximum claimable number of tokens for this drop." + ); + if (claimToken.tokenType == ERC20_TYPE) { IERC20 erc20Contract = IERC20(claimToken.tokenAddress); erc20Contract.transfer(msg.sender, amount); @@ -322,6 +327,7 @@ contract DropperFacet is } ds.AmountClaimed[msg.sender][dropId] += amount; + ds.DropRequestClaimed[dropId][requestID] = true; emit Claimed(dropId, msg.sender, requestID, amount); } diff --git a/contracts/Dropper/LibDropper.sol b/contracts/Dropper/LibDropper.sol index 37870bfe..f0740b91 100644 --- a/contracts/Dropper/LibDropper.sol +++ b/contracts/Dropper/LibDropper.sol @@ -30,6 +30,8 @@ library LibDropper { mapping(uint256 => uint256) MaxClaimable; // address => dropID => total amount claimed for that drop mapping(address => mapping(uint256 => uint256)) AmountClaimed; + // dropID => requestID => true if claimed and false if not + mapping(uint256 => mapping(uint256 => bool)) DropRequestClaimed; } function dropperStorage() diff --git a/contracts/mechanics/garden-of-forking-paths/GardenOfForkingPaths.sol b/contracts/mechanics/garden-of-forking-paths/GardenOfForkingPaths.sol new file mode 100644 index 00000000..304d59fd --- /dev/null +++ b/contracts/mechanics/garden-of-forking-paths/GardenOfForkingPaths.sol @@ -0,0 +1,824 @@ +// SPDX-License-Identifier: Apache-2.0 + +/** + * Authors: Moonstream Engineering (engineering@moonstream.to) + * GitHub: https://github.com/bugout-dev/engine + */ + +pragma solidity ^0.8.0; + +import {TerminusFacet} from "@moonstream/contracts/terminus/TerminusFacet.sol"; +import {TerminusPermissions} from "@moonstream/contracts/terminus/TerminusPermissions.sol"; +import "@openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import "@openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol"; +import "@openzeppelin-contracts/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "../../diamond/libraries/LibDiamond.sol"; +import "../../diamond/security/DiamondReentrancyGuard.sol"; + +struct Session { + address playerTokenAddress; + address paymentTokenAddress; + uint256 paymentAmount; + bool isActive; // active -> stake if ok, cannot unstake + bool isChoosingActive; // if active -> players can choose path in current stage + string uri; + uint256[] stages; + // In forgiving sessions, making the wrong path choice at the previous stage doesn't prevent a + // player from choosing a path in the next stage. It *does* prevent them from collecting the reward + // for the next stage, though. + bool isForgiving; +} + +/** +StageReward represents the reward an NFT owner can collect by making a choice with their NFT on the +corresponding stage in a given Garden of Forking Paths session. + +The reward must be a Terminus token and the Garden of Forking Paths contract must have minting privileges +on the token pool. + */ +struct StageReward { + address terminusAddress; + uint256 terminusPoolId; + uint256 rewardAmount; +} + +library LibGOFP { + bytes32 constant STORAGE_POSITION = + keccak256("moonstreamdao.eth.storage.mechanics.GardenOfForkingPaths"); + + /** + All implicit arrays (implemented with maps) are 1-indexed. This applies to: + - sessions + - stages + - paths + + This helps us avoid any confusion that stems from 0 being the default value for uint256. + Applying this condition uniformly to all mappings avoids confusion from having to remember which + implicit arrays are 0-indexed and which are 1-indexed. + */ + struct GOFPStorage { + address AdminTerminusAddress; + uint256 AdminTerminusPoolID; + uint256 numSessions; + mapping(uint256 => Session) sessionById; + // session => stage => stageReward + mapping(uint256 => mapping(uint256 => StageReward)) sessionStageReward; + // session => stage => correct path for that stage + mapping(uint256 => mapping(uint256 => uint256)) sessionStagePath; + // nftAddress => tokenId => sessionId + mapping(address => mapping(uint256 => uint256)) stakedTokenSession; + // nftAddress => tokenId => owner + mapping(address => mapping(uint256 => address)) stakedTokenOwner; + // session => owner => numTokensStaked + mapping(uint256 => mapping(address => uint256)) numTokensStakedByOwnerInSession; + // sessionId => tokenId => index in tokensStakedByOwnerInSession + mapping(uint256 => mapping(uint256 => uint256)) stakedTokenIndex; + // session => owner => index => tokenId + // The index refers to the tokens that the given owner has staked into the given sessions. + // The index starts from 1. + mapping(uint256 => mapping(address => mapping(uint256 => uint256))) tokensStakedByOwnerInSession; + // session => tokenId => stage => chosenPath + // This mapping tracks the path chosen by each eligible NFT in a session at each stage + mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) pathChoices; + // session => tokenId => was token ever staked into session? + // This guards against a token being staked into a session multiple times. + mapping(uint256 => mapping(uint256 => bool)) sessionTokenStakeGuard; + } + + function gofpStorage() internal pure returns (GOFPStorage storage gs) { + bytes32 position = STORAGE_POSITION; + assembly { + gs.slot := position + } + } +} + +/** +The GOFPFacet is a smart contract that can either be used standalone or as part of an EIP2535 Diamond +proxy contract. + +It implements the Garden of Forking Paths, a multiplayer choose your own adventure game mechanic. + +Garden of Forking Paths is run in sessions. Each session consists of a given number of stages. Each +stage consists of a given number of paths. + +Everything on the Garden of Forking Paths is 1-indexed. + +There are two kinds of accounts that can interact with the Garden of Forking Paths: +1. Game Masters +2. Players + +Game Masters are accounts which hold an admin badge as defined by LibGOFP.AdminTerminusAddress and +LibGOFP.AdminTerminusPoolID. The badge is expected to be a Terminus badge (non-transferable token). + +Game Masters can: +- [x] Create sessions +- [x] Mark sessions as active or inactive +- [x] Mark sessions as active or inactive for the purposes of NFTs choosing a path in a the current stage +- [x] Register the correct path for the current stage +- [x] Update the metadata for a session +- [x] Set a reward (Terminus token mint) for NFT holders who make a choice with an NFT in each stage + +Players can: +- [x] Stake their NFTs into a sesssion if the correct first stage path has not been chosen +- [x] Pay to stake their NFTs +- [x] Unstake their NFTs from a session at any time +- [x] Have one of their NFTs choose a path in the current stage PROVIDED THAT the current stage is the first +stage OR that the NFT chose the correct path in the previous stage +- [x] Collect their reward (Terminus token mint) for making a choice with an NFT in the current stage of a session + +Anybody can: +- [x] View details of a session +- [x] View the correct path for a given stage +- [x] View how many tokens a given owner has staked into a given session +- [x] View the token ID of the th token that a given owner has staked into a given session for any valid + value of n + */ +contract GOFPFacet is + ERC721Holder, + ERC1155Holder, + TerminusPermissions, + DiamondReentrancyGuard +{ + modifier onlyGameMaster() { + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + require( + _holdsPoolToken(gs.AdminTerminusAddress, gs.AdminTerminusPoolID, 1), + "GOFPFacet.onlyGameMaster: The address is not an authorized game master" + ); + _; + } + + event SessionCreated( + uint256 sessionId, + address indexed playerTokenAddress, + address indexed paymentTokenAddress, + uint256 paymentAmount, + string uri, + bool active, + bool isForgiving + ); + event SessionActivated(uint256 indexed sessionId, bool isActive); + event SessionChoosingActivated( + uint256 indexed sessionId, + bool isChoosingActive + ); + event StageRewardChanged( + uint256 indexed sessionId, + uint256 indexed stage, + address terminusAddress, + uint256 terminusPoolId, + uint256 rewardAmount + ); + event SessionUriChanged(uint256 indexed sessionId, string uri); + event PathRegistered( + uint256 indexed sessionId, + uint256 stage, + uint256 path + ); + event PathChosen( + uint256 indexed sessionId, + uint256 indexed tokenId, + uint256 indexed stage, + uint256 path + ); + + function init(address adminTerminusAddress, uint256 adminTerminusPoolID) + external + { + LibDiamond.enforceIsContractOwner(); + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + gs.AdminTerminusAddress = adminTerminusAddress; + gs.AdminTerminusPoolID = adminTerminusPoolID; + } + + function getSession(uint256 sessionId) + external + view + returns (Session memory) + { + return LibGOFP.gofpStorage().sessionById[sessionId]; + } + + function adminTerminusInfo() external view returns (address, uint256) { + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + return (gs.AdminTerminusAddress, gs.AdminTerminusPoolID); + } + + function numSessions() external view returns (uint256) { + return LibGOFP.gofpStorage().numSessions; + } + + /** + Creates a Garden of Forking Paths session. The session is configured with: + - playerTokenAddress - this is the address of ERC721 tokens that can participate in the session + - paymentTokenAddress - this is the address of the ERC20 token that each NFT must pay to enter the session + - paymentAmount - this is the amount of the payment token that each NFT must pay to enter the session + - isActive - this determines if the session is active as soon as it is created or not + - isChoosingActive - this determines if NFTs can choose a path in the current stage or not, and is true + by default when the session is created + - uri - metadata uri describing the session + - stages - an array describing the number of path choices at each stage of the session + */ + function createSession( + address playerTokenAddress, + address paymentTokenAddress, + uint256 paymentAmount, + bool isActive, + string memory uri, + uint256[] memory stages, + bool isForgiving + ) external onlyGameMaster { + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + gs.numSessions++; + + require( + gs.sessionById[gs.numSessions].playerTokenAddress == address(0), + "GOFPFacet.createSession: Session already registered" + ); + + require( + playerTokenAddress != address(0), + "GOFPFacet.createSession: playerTokenAddress can't be zero address" + ); + + require( + paymentTokenAddress != address(0) || paymentAmount == 0, + "GOFPFacet.createSession: If paymentTokenAddress is the 0 address, paymentAmount should also be 0" + ); + + gs.sessionById[gs.numSessions] = Session({ + playerTokenAddress: playerTokenAddress, + paymentTokenAddress: paymentTokenAddress, + paymentAmount: paymentAmount, + isActive: isActive, + isChoosingActive: true, + uri: uri, + stages: stages, + isForgiving: isForgiving + }); + emit SessionCreated( + gs.numSessions, + playerTokenAddress, + paymentTokenAddress, + paymentAmount, + uri, + isActive, + isForgiving + ); + emit SessionActivated(gs.numSessions, isActive); + emit SessionChoosingActivated(gs.numSessions, true); + emit SessionUriChanged(gs.numSessions, uri); + } + + function getStageReward(uint256 sessionId, uint256 stage) + external + view + returns (StageReward memory) + { + return LibGOFP.gofpStorage().sessionStageReward[sessionId][stage]; + } + + function setStageRewards( + uint256 sessionId, + uint256[] calldata stages, + address[] calldata terminusAddresses, + uint256[] calldata terminusPoolIds, + uint256[] calldata rewardAmounts + ) external onlyGameMaster { + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + require( + stages.length == terminusAddresses.length, + "GOFPFacet.setStageRewards: terminusAddresses must have same length as stages" + ); + require( + stages.length == terminusPoolIds.length, + "GOFPFacet.setStageRewards: terminusPoolIds must have same length as stages" + ); + require( + stages.length == rewardAmounts.length, + "GOFPFacet.setStageRewards: rewardAmounts must have same length as stages" + ); + + Session storage session = gs.sessionById[sessionId]; + require( + !session.isActive, + "GOFPFacet.setStageRewards: Cannot set stage rewards on active session" + ); + + for (uint256 i = 0; i < stages.length; i++) { + require( + (1 <= stages[i]) && (stages[i] <= session.stages.length), + "GOFPFacet.setStageRewards: Invalid stage" + ); + gs.sessionStageReward[sessionId][stages[i]] = StageReward({ + terminusAddress: terminusAddresses[i], + terminusPoolId: terminusPoolIds[i], + rewardAmount: rewardAmounts[i] + }); + emit StageRewardChanged( + sessionId, + stages[i], + terminusAddresses[i], + terminusPoolIds[i], + rewardAmounts[i] + ); + } + } + + function setSessionActive(uint256 sessionId, bool isActive) + external + onlyGameMaster + { + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + require( + sessionId <= gs.numSessions, + "GOFPFacet.setSessionActive: Invalid session ID" + ); + gs.sessionById[sessionId].isActive = isActive; + emit SessionActivated(sessionId, isActive); + } + + function getCorrectPathForStage(uint256 sessionId, uint256 stage) + external + view + returns (uint256) + { + require( + stage > 0, + "GOFPFacet.getCorrectPathForStage: Stages are 1-indexed, 0 is not a valid stage" + ); + return LibGOFP.gofpStorage().sessionStagePath[sessionId][stage]; + } + + function setCorrectPathForStage( + uint256 sessionId, + uint256 stage, + uint256 path, + bool setIsChoosingActive + ) external onlyGameMaster { + require( + stage > 0, + "GOFPFacet.setCorrectPathForStage: Stages are 1-indexed, 0 is not a valid stage" + ); + uint256 stageIndex = stage - 1; + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + require( + sessionId <= gs.numSessions, + "GOFPFacet.setCorrectPathForStage: Invalid session" + ); + require( + stageIndex < gs.sessionById[sessionId].stages.length, + "GOFPFacet.setCorrectPathForStage: Invalid stage" + ); + require( + !gs.sessionById[sessionId].isChoosingActive, + "GOFPFacet.setCorrectPathForStage: Deactivate isChoosingActive before setting the correct path" + ); + // Paths are 1-indexed to avoid possible confusion involving default value of 0 + require( + path >= 1 && path <= gs.sessionById[sessionId].stages[stageIndex], + "GOFPFacet.setCorrectPathForStage: Invalid path" + ); + // We use the default value of 0 as a guard to check that path has not already been set for that + // stage. No changes allowed for a given stage after the path was already chosen. + require( + gs.sessionStagePath[sessionId][stage] == 0, + "GOFPFacet.setCorrectPathForStage: Path has already been chosen for that stage" + ); + // You cannot set the path for a stage if the path for its previous stage has not been previously + // set. + // We use the stageIndex to access the path because stageIndex = stage - 1. This is just a + // convenience. It would be more correct to access the "stage - 1" key in the mapping. + require( + stage <= 1 || gs.sessionStagePath[sessionId][stageIndex] != 0, + "GOFPFacet.setCorrectPathForStage: Path not set for previous stage" + ); + gs.sessionStagePath[sessionId][stage] = path; + gs.sessionById[sessionId].isChoosingActive = setIsChoosingActive; + + emit PathRegistered(sessionId, stage, path); + emit SessionChoosingActivated(sessionId, setIsChoosingActive); + } + + function setSessionChoosingActive(uint256 sessionId, bool isChoosingActive) + external + onlyGameMaster + { + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + require( + sessionId <= gs.numSessions, + "GOFPFacet.setSessionChoosingActive: Invalid session ID" + ); + gs.sessionById[sessionId].isChoosingActive = isChoosingActive; + emit SessionChoosingActivated(sessionId, isChoosingActive); + } + + function setSessionUri(uint256 sessionId, string memory uri) + external + onlyGameMaster + { + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + require( + sessionId <= gs.numSessions, + "GOFPFacet.setSessionChoosingActive: Invalid session ID" + ); + gs.sessionById[sessionId].uri = uri; + emit SessionUriChanged(sessionId, uri); + } + + /** + For a given NFT, specified by the `nftAddress` and `tokenId`, this view function returns: + 1. The sessionId of the session into which the NFT is staked + 2. The address of the staker + + If the token is not currently staked in the Garden of Forking Paths contract, this method returns + 0 for the sessionId and the 0 address as the staker. + */ + function getStakedTokenInfo(address nftAddress, uint256 tokenId) + external + view + returns (uint256, address) + { + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + return ( + gs.stakedTokenSession[nftAddress][tokenId], + gs.stakedTokenOwner[nftAddress][tokenId] + ); + } + + function getSessionTokenStakeGuard(uint256 sessionId, uint256 tokenId) + external + view + returns (bool) + { + return LibGOFP.gofpStorage().sessionTokenStakeGuard[sessionId][tokenId]; + } + + function numTokensStakedIntoSession(uint256 sessionId, address staker) + external + view + returns (uint256) + { + return + LibGOFP.gofpStorage().numTokensStakedByOwnerInSession[sessionId][ + staker + ]; + } + + function tokenOfStakerInSessionByIndex( + uint256 sessionId, + address staker, + uint256 index + ) external view returns (uint256) { + return + LibGOFP.gofpStorage().tokensStakedByOwnerInSession[sessionId][ + staker + ][index]; + } + + /** + Returns the path chosen by the given tokenId in the given session and stage. + + Recall: sessions and stages are 1-indexed. + */ + function getPathChoice( + uint256 sessionId, + uint256 tokenId, + uint256 stage + ) external view returns (uint256) { + return LibGOFP.gofpStorage().pathChoices[sessionId][tokenId][stage]; + } + + function _addTokenToEnumeration( + uint256 sessionId, + address owner, + address nftAddress, + uint256 tokenId + ) internal { + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + + require( + gs.stakedTokenSession[nftAddress][tokenId] == 0, + "GOFPFacet._addTokenToEnumeration: Token is already associated with a session on this contract" + ); + require( + gs.stakedTokenOwner[nftAddress][tokenId] == address(0), + "GOFPFacet._addTokenToEnumeration: Token is already associated with an owner on this contract" + ); + require( + gs.stakedTokenIndex[sessionId][tokenId] == 0, + "GOFPFacet._addTokenToEnumeration: Token was already added to enumeration" + ); + + gs.stakedTokenSession[nftAddress][tokenId] = sessionId; + gs.stakedTokenOwner[nftAddress][tokenId] = owner; + + uint256 currStaked = gs.numTokensStakedByOwnerInSession[sessionId][ + owner + ]; + gs.tokensStakedByOwnerInSession[sessionId][owner][ + currStaked + 1 + ] = tokenId; + gs.stakedTokenIndex[sessionId][tokenId] = currStaked + 1; + gs.numTokensStakedByOwnerInSession[sessionId][owner]++; + } + + function _removeTokenFromEnumeration( + uint256 sessionId, + address owner, + address nftAddress, + uint256 tokenId + ) internal { + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + require( + gs.stakedTokenSession[nftAddress][tokenId] == sessionId, + "GOFPFacet._removeTokenFromEnumeration: Token is not associated with the given session" + ); + require( + gs.stakedTokenOwner[nftAddress][tokenId] == owner, + "GOFPFacet._removeTokenFromEnumeration: Token is not associated with the given owner" + ); + require( + gs.stakedTokenIndex[sessionId][tokenId] != 0, + "GOFPFacet._removeTokenFromEnumeration: Token wasn't added to enumeration" + ); + + delete gs.stakedTokenSession[nftAddress][tokenId]; + delete gs.stakedTokenOwner[nftAddress][tokenId]; + + uint256 currStaked = gs.numTokensStakedByOwnerInSession[sessionId][ + owner + ]; + uint256 currIndex = gs.stakedTokenIndex[sessionId][tokenId]; + uint256 lastToken = gs.tokensStakedByOwnerInSession[sessionId][owner][ + currStaked + ]; + require( + currIndex <= currStaked && + gs.tokensStakedByOwnerInSession[sessionId][owner][currIndex] == + tokenId, + "GOFPFacet._removeTokenFromEnumeration: Token wasn't staked by the given owner" + ); + //swapping last element with element at given index + gs.tokensStakedByOwnerInSession[sessionId][owner][ + currIndex + ] = lastToken; + //updating last token's index + gs.stakedTokenIndex[sessionId][lastToken] = currIndex; + //deleting old lastToken + // TODO(zomglings): Test stake -> unstake -> restake + delete gs.stakedTokenIndex[sessionId][tokenId]; + delete gs.tokensStakedByOwnerInSession[sessionId][owner][currStaked]; + //updating staked count + gs.numTokensStakedByOwnerInSession[sessionId][owner]--; + } + + function stakeTokensIntoSession( + uint256 sessionId, + uint256[] calldata tokenIds + ) external diamondNonReentrant { + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + require( + sessionId <= gs.numSessions, + "GOFPFacet.stakeTokensIntoSession: Invalid session ID" + ); + + address paymentTokenAddress = gs + .sessionById[sessionId] + .paymentTokenAddress; + if (paymentTokenAddress != address(0)) { + IERC20 paymentToken = IERC20(paymentTokenAddress); + uint256 paymentAmount = gs.sessionById[sessionId].paymentAmount * + tokenIds.length; + bool paymentSuccessful = paymentToken.transferFrom( + msg.sender, + address(this), + paymentAmount + ); + require( + paymentSuccessful, + "GOFPFacet.stakeTokensIntoSession: Session requires payment but payment was unsuccessful" + ); + } + + require( + gs.sessionById[sessionId].isActive, + "GOFPFacet.stakeTokensIntoSession: Cannot stake tokens into inactive session" + ); + + require( + gs.sessionStagePath[sessionId][1] == 0, + "GOFPFacet.stakeTokensIntoSession: The first stage for this session has already been resolved" + ); + + address nftAddress = gs.sessionById[sessionId].playerTokenAddress; + IERC721 token = IERC721(nftAddress); + for (uint256 i = 0; i < tokenIds.length; i++) { + // TODO(zomglings): Currently, Garden of Forking Paths does not allow even someone who is *approved* to transfer + // NFTs on behalf of their owners to stake those NFTs into a session. + // We may want to change this in the future. Perhaps the more correct thing would be to check if the msg.sender + // was approved by the NFT owner on the ERC721 contract. + // We should check if approvals are intended to compose transitively on ERC721. + // Just because person A gives person B approval to transfer ERC721 tokens, doesn't mean they want them to + // have permission to instigate *another* address with transfer approval to make a transfer. + require( + token.ownerOf(tokenIds[i]) == msg.sender, + "GOFPFacet.stakeTokensIntoSession: Cannot stake a token into session which is not owned by message sender" + ); + require( + !gs.sessionTokenStakeGuard[sessionId][tokenIds[i]], + "GOFPFacet.stakeTokensIntoSession: Token was previously staked into session" + ); + token.safeTransferFrom(msg.sender, address(this), tokenIds[i]); + gs.sessionTokenStakeGuard[sessionId][tokenIds[i]] = true; + _addTokenToEnumeration( + sessionId, + msg.sender, + nftAddress, + tokenIds[i] + ); + } + } + + function unstakeTokensFromSession( + uint256 sessionId, + uint256[] calldata tokenIds + ) external diamondNonReentrant { + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + require( + sessionId <= gs.numSessions, + "GOFPFacet.unstakeTokensFromSession: Invalid session ID" + ); + + Session storage session = gs.sessionById[sessionId]; + address nftAddress = session.playerTokenAddress; + IERC721 token = IERC721(nftAddress); + for (uint256 i = 0; i < tokenIds.length; i++) { + _removeTokenFromEnumeration( + sessionId, + msg.sender, + nftAddress, + tokenIds[i] + ); + token.safeTransferFrom(address(this), msg.sender, tokenIds[i]); + } + } + + /** + Returns the number of the current stage. + */ + function getCurrentStage(uint256 sessionId) + external + view + returns (uint256) + { + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + require( + sessionId <= gs.numSessions, + "GOFPFacet.getCurrentStage: Invalid session ID" + ); + + Session storage session = gs.sessionById[sessionId]; + + uint256 lastStage = 0; + + for (uint256 i = 1; i <= session.stages.length; i++) { + if (gs.sessionStagePath[sessionId][i] > 0) { + lastStage = i; + } else { + break; + } + } + + return lastStage + 1; + } + + /** + For the current stage of the session with the given sessionId, a player may make a choice of paths + for each of their tokenIds. + + The tokenIds array is expected to be the same length as the paths array. + + A choice may only be made if choosing is currently active for the given session. + + If the current stage is not the first stage, it is expected that each of the tokens specified by + tokenIds made the correct choice in the previous stage. + */ + function chooseCurrentStagePaths( + uint256 sessionId, + uint256[] memory tokenIds, + uint256[] memory paths + ) external diamondNonReentrant { + require( + tokenIds.length == paths.length, + "GOFPFacet.chooseCurrentStagePaths: tokenIds and paths arrays must be of the same length" + ); + LibGOFP.GOFPStorage storage gs = LibGOFP.gofpStorage(); + + require( + sessionId <= gs.numSessions, + "GOFPFacet.chooseCurrentStagePaths: Invalid session ID" + ); + + Session storage session = gs.sessionById[sessionId]; + // This prevents players from claiming rewards for path choice in inactive sessions. + // It is a form of protection for game masters - they can shut down all reward claims from the + // Garden of Forking Paths session my marking that session as inactive. + require( + session.isActive, + "GOFPFacet.chooseCurrentStagePaths: Cannot choose paths in inactive session" + ); + require( + session.isChoosingActive, + "GOFPFacet.chooseCurrentStagePaths: Cannot choose paths in a session for which choosing is not active" + ); + + uint256 i = 0; + + uint256 lastStage = 0; + uint256 lastStageCorrectPath = 0; + + for (i = 1; i <= session.stages.length; i++) { + if (gs.sessionStagePath[sessionId][i] > 0) { + lastStage = i; + lastStageCorrectPath = gs.sessionStagePath[sessionId][i]; + } else { + break; + } + } + + require( + lastStage < session.stages.length, + "GOFPFacet.chooseCurrentStagePaths: This session has ended" + ); + + uint256 currentStage = lastStage + 1; + // lastStage has no semantic meaning below. It would be more correct to say currentStage - 1. + // This is just for convenience, saving one subtraction. + uint256 numPaths = session.stages[lastStage]; + + uint256 rewardAmount = 0; + StageReward storage stageReward = gs.sessionStageReward[sessionId][ + currentStage + ]; + + for (i = 0; i < tokenIds.length; i++) { + // BEWARE: Setting tokenId and path variables resuls in a "Stack too deep" error message + // when compiling this contract. Using calldata array arguments restricts the amount of + // stack variables available to us inside the function body. + require( + gs.stakedTokenIndex[sessionId][tokenIds[i]] > 0, + "GOFPFacet.chooseCurrentStagePaths: Token not currently staked into session" + ); + require( + gs.stakedTokenOwner[session.playerTokenAddress][tokenIds[i]] == + msg.sender, + "GOFPFacet.chooseCurrentStagePaths: Message not sent by token owner" + ); + require( + (lastStage == 0) || + (session.isForgiving) || + (gs.pathChoices[sessionId][tokenIds[i]][lastStage] == + lastStageCorrectPath), + "GOFPFacet.chooseCurrentStagePaths: Session is not forgiving and token did not choose correct path in last stage" + ); + require( + gs.pathChoices[sessionId][tokenIds[i]][currentStage] == 0, + "GOFPFacet.chooseCurrentStagePaths: Token has already chosen a path in the current stage" + ); + require( + (paths[i] >= 1) && (paths[i] <= numPaths), + "GOFPFacet.chooseCurrentStagePaths: Invalid path" + ); + gs.pathChoices[sessionId][tokenIds[i]][currentStage] = paths[i]; + emit PathChosen(sessionId, tokenIds[i], currentStage, paths[i]); + + // Calculate number of correct choices on last stage for reward distribution, but only if + // there is a stage reward. + if (stageReward.terminusAddress != address(0)) { + if (lastStage == 0) { + rewardAmount += stageReward.rewardAmount; + } else if ( + gs.pathChoices[sessionId][tokenIds[i]][lastStage] == + lastStageCorrectPath + ) { + rewardAmount += stageReward.rewardAmount; + } + } + } + + if (stageReward.terminusAddress != address(0)) { + TerminusFacet rewardTerminus = TerminusFacet( + stageReward.terminusAddress + ); + rewardTerminus.mint( + msg.sender, + stageReward.terminusPoolId, + rewardAmount, + "" + ); + } + } +} diff --git a/packages/moonstream-components/src/Theme/theme.js b/packages/moonstream-components/src/Theme/theme.js index 6269fd18..5d2c582f 100644 --- a/packages/moonstream-components/src/Theme/theme.js +++ b/packages/moonstream-components/src/Theme/theme.js @@ -62,9 +62,9 @@ const theme = extendTheme({ }, fonts: { - heading: '"Work Sans", sans-serif', - body: '"Work Sans", sans-serif', - mono: '"Work Sans", monospace', + heading: '"Space Grotesk", sans-serif', + body: '"Space Grotesk", sans-serif', + mono: '"Space Grotesk", monospace', }, fontSizes: { xs: "0.625rem", //10px @@ -165,6 +165,7 @@ const theme = extendTheme({ 700: "#fd7835", 800: "#fd671b", 900: "#FD5602", + 1000: "#F56646", }, green: { @@ -184,6 +185,8 @@ const theme = extendTheme({ black: { 100: "#333399", 200: "#111442", + 300: "#1A1D22", + 400: "#292929", }, }, }); diff --git a/packages/moonstream-components/src/components/ChainSelectorPlay.js b/packages/moonstream-components/src/components/ChainSelectorPlay.js new file mode 100644 index 00000000..bf388aa0 --- /dev/null +++ b/packages/moonstream-components/src/components/ChainSelectorPlay.js @@ -0,0 +1,145 @@ +import React, { useContext } from "react"; + +import { + Menu, + MenuItem, + MenuList, + Image, + MenuButton, + Button, + Icon, +} from "@chakra-ui/react"; +import { ChevronDownIcon } from "@chakra-ui/icons"; +import { MdOutlineLaptopMac } from "react-icons/md"; +import Web3Context from "../core/providers/Web3Provider/context"; +const ChainSelector = () => { + const web3Provider = useContext(Web3Context); + return ( + + } + leftIcon={ + + } + color="white" + variant="outline" + fontSize="16px" + > + {web3Provider.targetChain?.name ?? "Chain selector"} + + + { + web3Provider.changeChain("ethereum"); + }} + > + + Ethereum + + { + web3Provider.changeChain("polygon"); + }} + > + + Polygon + + { + web3Provider.changeChain("mumbai"); + }} + > + + Mumbai + + { + web3Provider.changeChain("localhost"); + }} + > + + Localhost + + + + ); +}; +export default ChainSelector; diff --git a/packages/moonstream-components/src/components/ClaimCardPlay.js b/packages/moonstream-components/src/components/ClaimCardPlay.js new file mode 100644 index 00000000..a228bc56 --- /dev/null +++ b/packages/moonstream-components/src/components/ClaimCardPlay.js @@ -0,0 +1,146 @@ +import React, { useContext } from "react"; +import { + chakra, + Flex, + Heading, + Spinner, + UnorderedList, + ListItem, + Button, +} from "@chakra-ui/react"; +import useClaim from "../core/hooks/dropper/useClaim"; +import Web3Context from "../core/providers/Web3Provider/context"; +import { useDropperContract } from "../core/hooks/dropper"; + +const _ClaimCard = ({ drop, children, ...props }) => { + const web3Provider = useContext(Web3Context); + + const claimer = useClaim({ + dropperAddress: drop.dropper_contract_address, + ctx: web3Provider, + claimId: drop.dropper_claim_id, + userAccess: true, + claimantAddress: web3Provider.account, + }); + + const dropperContract = useDropperContract({ + dropperAddress: drop.dropper_contract_address, + ctx: web3Provider, + claimId: drop.claim_id, + }); + + if ( + claimer.claimStatus?.data && + (claimer.claimStatus?.data.status > 0 || + claimer.claimStatus?.data.claim[0] == "0") + ) + if ( + claimer.isLoadingClaim || + dropperContract.contractState.data?.isLoading || + dropperContract.claimState.isLoading || + claimer.signature.isLoading + ) + return ; + + return ( + + + {drop.title} + + + {drop.description} + + + + + + + Deadline: + {drop.block_deadline} + + + Dropper: {drop.dropper_contract_address} + + + Amount: {drop.amount} + + + Reward Type:{" "} + {dropperContract.claimState.data?.claim.tokenType} + + + Reward address:{" "} + + {dropperContract.claimState.data?.claim.tokenAddress} + + + + Token id:{" "} + {dropperContract.claimState.data?.claim.tokenId} + + claimd id: {drop.claim_id} + + + + {/* */} + + + + + {children && children} + + ); +}; +const ClaimCard = chakra(_ClaimCard); +export default ClaimCard; diff --git a/packages/moonstream-components/src/components/CryptoUnicorns/LootboxCard.tsx b/packages/moonstream-components/src/components/CryptoUnicorns/LootboxCard.tsx index 6ea1de7e..e7ea03ad 100644 --- a/packages/moonstream-components/src/components/CryptoUnicorns/LootboxCard.tsx +++ b/packages/moonstream-components/src/components/CryptoUnicorns/LootboxCard.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import { Flex, Image, @@ -14,24 +14,31 @@ const _LootboxCard = ({ imageUrl, lootboxBalance, showQuantity = true, + isVideo = false, + grayedOut = false, ...props }: { displayName: string; imageUrl: string; lootboxBalance: number; showQuantity?: boolean; + isVideo?: boolean; + grayedOut?: boolean; }) => { return ( CU Common Lootbox - + {displayName} {showQuantity && ( diff --git a/packages/moonstream-components/src/components/CryptoUnicorns/LootboxCardPlay.tsx b/packages/moonstream-components/src/components/CryptoUnicorns/LootboxCardPlay.tsx new file mode 100644 index 00000000..554f3d6d --- /dev/null +++ b/packages/moonstream-components/src/components/CryptoUnicorns/LootboxCardPlay.tsx @@ -0,0 +1,59 @@ +import React from "react"; +import { Flex, Image, Text, chakra, Spacer, VStack } from "@chakra-ui/react"; + +const _LootboxCard = ({ + displayName, + imageUrl, + lootboxBalance, + showQuantity = true, + isVideo = false, + grayedOut = false, + ...props +}: { + displayName: string; + imageUrl: string; + lootboxBalance: number; + showQuantity?: boolean; + isVideo?: boolean; + grayedOut?: boolean; +}) => { + return ( + + + CU Inventory Item + + + {displayName} + + {showQuantity && ( + + Quantity + + {lootboxBalance} + + )} + + + + ); +}; + +const LootboxCard = chakra(_LootboxCard); +export default LootboxCard; diff --git a/packages/moonstream-components/src/components/CryptoUnicorns/MostActiveUsers.tsx b/packages/moonstream-components/src/components/CryptoUnicorns/MostActiveUsers.tsx new file mode 100644 index 00000000..f76ad91a --- /dev/null +++ b/packages/moonstream-components/src/components/CryptoUnicorns/MostActiveUsers.tsx @@ -0,0 +1,167 @@ +import React, { useState } from "react"; +import { useQuery } from "react-query"; +import { + Flex, + Text, + Spinner, + Spacer, + Select, + Link, + HStack, +} from "@chakra-ui/react"; +import RadioFilter from "../RadioFilter"; +import http from "moonstream-components/src/core/utils/http"; +import queryCacheProps from "moonstream-components/src/core/hooks/hookCommon"; +import { + Period, + AssetType, +} from "moonstream-components/src/core/types/DashboardTypes"; +import { + UNICORN_CONTRACT_ADDRESS, + LAND_CONTRACT_ADDRESS, + SHADOWCORN_CONTRACT_ADDRESS, +} from "../../core/cu/constants"; + +const MostActiveUsers = ({ dataApi }: { dataApi: string }) => { + const [volumePeriod, setVolumePeriod] = useState(Period.Day); + const [userType, setUserType] = useState("seller"); + const [userAssetType, setUserAssetType] = useState( + AssetType.Unicorn + ); + + const getAssetAddress = () => { + if (userAssetType == AssetType.Unicorn) return UNICORN_CONTRACT_ADDRESS; + else if (userAssetType == AssetType.Land) return LAND_CONTRACT_ADDRESS; + else return SHADOWCORN_CONTRACT_ADDRESS; + }; + + const activeUsersEndpoint = () => { + const endpoint = `${dataApi}most_active_${userType}s/${getAssetAddress()}/${volumePeriod}/data.json`; + return endpoint; + }; + + const fetchActiveUsers = async () => { + return http( + { + method: "GET", + url: activeUsersEndpoint(), + }, + true + ); + }; + + const activeUsersData = useQuery( + ["sales", volumePeriod, userType, userAssetType], + () => { + return fetchActiveUsers().then((res) => { + console.log(res); + return res.data.data; + }); + }, + { + ...queryCacheProps, + onSuccess: () => {}, + } + ); + + const buildOpenseaLink = (userId: string) => { + return `https://opensea.io/${userId}`; + }; + + const handleChange = (value: string) => { + if (value == "Shadowcorns") setUserAssetType(AssetType.Shadowcorn); + else if (value == "Lands") setUserAssetType(AssetType.Land); + else setUserAssetType(AssetType.Unicorn); + }; + + return ( + + + + Most Active Users + + + + + + + + + + + + Address + + + Units {userType == "seller" ? "sold" : "bought"} + + + {activeUsersData.data ? ( + + {activeUsersData.data.map((item: any, idx: number) => { + return ( + + + {item[userType]} + + + {item["sale_count"]} + + + + ); + })} + + ) : ( + + )} + + ); +}; + +export default MostActiveUsers; diff --git a/packages/moonstream-components/src/components/CryptoUnicorns/RecentSales.tsx b/packages/moonstream-components/src/components/CryptoUnicorns/RecentSales.tsx new file mode 100644 index 00000000..60543459 --- /dev/null +++ b/packages/moonstream-components/src/components/CryptoUnicorns/RecentSales.tsx @@ -0,0 +1,147 @@ +import React, { useState } from "react"; +import { useQuery } from "react-query"; +import { Flex, Text, Spinner, Link, HStack, Spacer } from "@chakra-ui/react"; +import RadioFilter from "../RadioFilter"; +import http from "moonstream-components/src/core/utils/http"; +import queryCacheProps from "moonstream-components/src/core/hooks/hookCommon"; +import { AssetType } from "moonstream-components/src/core/types/DashboardTypes"; +import { + UNICORN_CONTRACT_ADDRESS, + LAND_CONTRACT_ADDRESS, + SHADOWCORN_CONTRACT_ADDRESS, +} from "../../core/cu/constants"; + +const RecentSales = ({ dataApi }: { dataApi: string }) => { + const [salesAssetType, setSalesAssetType] = useState( + AssetType.Unicorn + ); + + const getAssetAddress = () => { + if (salesAssetType == AssetType.Unicorn) return UNICORN_CONTRACT_ADDRESS; + else if (salesAssetType == AssetType.Land) return LAND_CONTRACT_ADDRESS; + else return SHADOWCORN_CONTRACT_ADDRESS; + }; + + const salesEndpoint = () => { + const endpoint = `${dataApi}most_recent_sale/${getAssetAddress()}/10/data.json`; + return endpoint; + }; + + const fetchSales = async () => { + return http( + { + method: "GET", + url: salesEndpoint(), + }, + true + ); + }; + + const salesData = useQuery( + ["sales", salesAssetType], + () => { + return fetchSales().then((res) => { + return res.data.data; + }); + }, + { + ...queryCacheProps, + onSuccess: () => {}, + } + ); + + const displayTimeGap = (timestamp: number) => { + var currentSeconds = new Date().getTime() / 1000; + var timePassed = currentSeconds - timestamp; + if (timePassed < 120) { + // less than 2 minutes + return "just now"; + } else if (timePassed < 7200) { + // less than 2 hours + var minutesPassed = Math.floor(timePassed / 60); + return `${minutesPassed} minutes ago`; + } else if (timePassed < 172800) { + // less than 2 days + var hoursPassed = Math.floor(timePassed / 3600); + return `${hoursPassed} hours ago`; + } else { + // more than 2 days + var daysPassed = Math.floor(timePassed / 86400); + return `${daysPassed} days ago`; + } + }; + + // var now = new Date().getTime() / 1000; + // console.log(displayTimeGap(now - 65)); + // console.log(displayTimeGap(now - 135)); + // console.log(displayTimeGap(now - 4500)); + // console.log(displayTimeGap(now - 11000)); + // console.log(displayTimeGap(now - 100000)); + // console.log(displayTimeGap(1661700846)); + + const handleChange = (value: string) => { + if (value == "Shadowcorns") setSalesAssetType(AssetType.Shadowcorn); + else if (value == "Lands") setSalesAssetType(AssetType.Land); + else setSalesAssetType(AssetType.Unicorn); + }; + + const buildOpenseaLink = (tokenId: string) => { + return `https://opensea.io/assets/matic/${getAssetAddress()}/${tokenId}`; + }; + + return ( + + + Recent Sales + + + + Item id + + Sale time + + {salesData.data ? ( + + {salesData.data.map((item: any, idx: number) => { + return ( + + + {item.token_id} + + {displayTimeGap(item.block_timestamp)} + + + ); + })} + + ) : ( + + )} + + ); +}; + +export default RecentSales; diff --git a/cli/enginecli/test_dropper_api.py b/packages/moonstream-components/src/components/CryptoUnicorns/TerminusSupply.tsx similarity index 100% rename from cli/enginecli/test_dropper_api.py rename to packages/moonstream-components/src/components/CryptoUnicorns/TerminusSupply.tsx diff --git a/packages/moonstream-components/src/components/CryptoUnicorns/TotalSupply.tsx b/packages/moonstream-components/src/components/CryptoUnicorns/TotalSupply.tsx new file mode 100644 index 00000000..f100eae4 --- /dev/null +++ b/packages/moonstream-components/src/components/CryptoUnicorns/TotalSupply.tsx @@ -0,0 +1,150 @@ +import React, { useContext } from "react"; +import { useQuery } from "react-query"; +import { Flex, HStack, Text, Spacer, Spinner } from "@chakra-ui/react"; +import Web3Context from "moonstream-components/src/core/providers/Web3Provider/context"; +import { useERC20 } from "moonstream-components/src/core/hooks"; +import queryCacheProps from "moonstream-components/src/core/hooks/hookCommon"; +import http from "moonstream-components/src/core/utils/http"; +import { + UNICORN_CONTRACT_ADDRESS, + LAND_CONTRACT_ADDRESS, + SHADOWCORN_CONTRACT_ADDRESS, +} from "../../core/cu/constants"; + +const TotalSupply = ({ dataApi }: { dataApi: string }) => { + const web3ctx = useContext(Web3Context); + + const rbw = useERC20({ + contractAddress: "0x431CD3C9AC9Fc73644BF68bF5691f4B83F9E104f", + spender: "0x0000000000000000000000000000000000000000", + ctx: web3ctx, + account: "0x0000000000000000000000000000000000000000", + }); + + const unim = useERC20({ + contractAddress: "0x64060aB139Feaae7f06Ca4E63189D86aDEb51691", + spender: "0x0000000000000000000000000000000000000000", + ctx: web3ctx, + account: "0x0000000000000000000000000000000000000000", + }); + + const unicorns = useQuery( + ["unicorns"], + async () => { + return http( + { + method: "GET", + url: `${dataApi}total_supply_erc721/${UNICORN_CONTRACT_ADDRESS}/data.json`, + }, + true + ).then((res: any) => { + return res.data.data; + }); + }, + { + ...queryCacheProps, + onSuccess: () => {}, + } + ); + + const lands = useQuery( + ["lands"], + async () => { + return http( + { + method: "GET", + url: `${dataApi}total_supply_erc721/${LAND_CONTRACT_ADDRESS}/data.json`, + }, + true + ).then((res: any) => { + return res.data.data; + }); + }, + { + ...queryCacheProps, + onSuccess: () => {}, + } + ); + + const shadowcorns = useQuery( + ["shadowcorns"], + async () => { + return http( + { + method: "GET", + url: `${dataApi}total_supply_erc721/${SHADOWCORN_CONTRACT_ADDRESS}/data.json`, + }, + true + ).then((res: any) => { + return res.data.data; + }); + }, + { + ...queryCacheProps, + onSuccess: () => {}, + } + ); + + return ( + + + Total Supply + + + RBW + + {rbw.tokenState.data ? ( + + {Math.floor( + Number(rbw.tokenState.data.totalSupply) * Math.pow(10, -18) + )} + + ) : ( + + )} + + + UNIM + + {unim.tokenState.data ? ( + + {Math.floor( + Number(unim.tokenState.data.totalSupply) * Math.pow(10, -18) + )} + + ) : ( + + )} + + + Unicorns + + {unicorns.data ? ( + {unicorns.data[0].total_supply} + ) : ( + + )} + + + Lands + + {lands.data ? ( + {lands.data[0].total_supply} + ) : ( + + )} + + + Shadowcorns + + {shadowcorns.data ? ( + {shadowcorns.data[0].total_supply} + ) : ( + + )} + + + ); +}; + +export default TotalSupply; diff --git a/packages/moonstream-components/src/components/DropperContractPlay.js b/packages/moonstream-components/src/components/DropperContractPlay.js new file mode 100644 index 00000000..d8f3f184 --- /dev/null +++ b/packages/moonstream-components/src/components/DropperContractPlay.js @@ -0,0 +1,61 @@ +import React from "react"; +import { + chakra, + Flex, + Heading, + UnorderedList, + ListItem, +} from "@chakra-ui/react"; + +const _DropList = ({ contractResource, children, ...props }) => { + return ( + + + {contractResource.title ?? "unnamed dropper contract"} + + + {contractResource.description ?? "this contract has no description"} + + + + + + + Contract: {contractResource.address} + + chain: {contractResource.blockchain} + + + + + {children && children} + + ); +}; + +const DropList = chakra(_DropList); +export default DropList; diff --git a/packages/moonstream-components/src/components/FeatureCard.js b/packages/moonstream-components/src/components/FeatureCard.js index 07c1953f..6c09d534 100644 --- a/packages/moonstream-components/src/components/FeatureCard.js +++ b/packages/moonstream-components/src/components/FeatureCard.js @@ -45,7 +45,7 @@ const FeatureCard = ({ {...props} transition={"1s"} spacing={1} - px={1} + px={5} alignItems="center" borderRadius="12px" borderColor="gray.100" diff --git a/packages/moonstream-components/src/components/FeatureCardPlay.js b/packages/moonstream-components/src/components/FeatureCardPlay.js new file mode 100644 index 00000000..5aa05171 --- /dev/null +++ b/packages/moonstream-components/src/components/FeatureCardPlay.js @@ -0,0 +1,89 @@ +import React from "react"; +import Link from "next/link"; +import { + Stack, + Image as ChakraImage, + Heading, + chakra, + Link as ChakraLink, +} from "@chakra-ui/react"; +// export interface FeatureCardArgs extends ChakraProps { +// text: string; +// heading: string; +// link: string; +// imageUrl: string; +// textColor: string; +// alt: string; +// level?: any; +// } + +const FeatureCard = ({ + text, + heading, + link, + imageUrl, + textColorP, + imgH, + alt, + level, + imgPading, + isExternal, + ...props +}) => { + const Wrapper = (wrapperProps) => { + if (props.disabled) return wrapperProps.children; + return ( + + {wrapperProps.children} + + ); + }; + return ( + + + {imageUrl && ( + + )} + + {heading} + + + {text} + + + + ); +}; + +export default chakra(FeatureCard); diff --git a/packages/moonstream-components/src/components/LandingNavbar.js b/packages/moonstream-components/src/components/LandingNavbar.js index ed169b3b..21e8fd0f 100644 --- a/packages/moonstream-components/src/components/LandingNavbar.js +++ b/packages/moonstream-components/src/components/LandingNavbar.js @@ -25,9 +25,10 @@ import MoonstreamContext from "../core/providers/MoonstreamProvider/context"; import ChainSelector from "./ChainSelector"; const LandingNavbar = () => { - const { SITEMAP, WHITE_LOGO_W_TEXT_URL } = useContext(MoonstreamContext); + const { SITEMAP, PRIMARY_MOON_LOGO_URL } = useContext(MoonstreamContext); const ui = useContext(UIContext); const web3Provider = useContext(Web3Context); + return ( <> {ui.isMobileView && ( @@ -44,10 +45,8 @@ const LandingNavbar = () => {