From 759b38fb711ad22934df37ddca371bc60808fb4c Mon Sep 17 00:00:00 2001 From: cha0sg0d <0xcha0sg0d@gmail.com> Date: Tue, 20 Sep 2022 17:36:18 +0100 Subject: [PATCH 01/55] test file --- eth/test/DFToken.test.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 eth/test/DFToken.test.ts diff --git a/eth/test/DFToken.test.ts b/eth/test/DFToken.test.ts new file mode 100644 index 00000000..d8a41f9f --- /dev/null +++ b/eth/test/DFToken.test.ts @@ -0,0 +1 @@ +// TODO: Implement ERC-1155 Tests for 721(Artifact, Spaceship) 20(Silver) From 5ed378d6b0154ef48eb81c7297183805bffd5a3d Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Fri, 23 Sep 2022 21:57:10 +0100 Subject: [PATCH 02/55] feat: load SolidStateERC1155 --- eth/contracts/facets/DFArtifactFacet.sol | 3 +- eth/package.json | 1 + eth/test/DFERC1155.test.ts | 19 +++++++ package-lock.json | 68 ++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 eth/test/DFERC1155.test.ts diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 35f93bc7..78d89ea8 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; // Contract imports import {SolidStateERC721} from "@solidstate/contracts/token/ERC721/SolidStateERC721.sol"; import {ERC721BaseStorage} from "@solidstate/contracts/token/ERC721/base/ERC721BaseStorage.sol"; +import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; @@ -19,7 +20,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs} from "../DFTypes.sol"; -contract DFArtifactFacet is WithStorage, SolidStateERC721 { +contract DFArtifactFacet is WithStorage, SolidStateERC721, SolidStateERC1155 { using ERC721BaseStorage for ERC721BaseStorage.Layout; event PlanetProspected(address player, uint256 loc); diff --git a/eth/package.json b/eth/package.json index de7bb054..e7b15a1a 100644 --- a/eth/package.json +++ b/eth/package.json @@ -20,6 +20,7 @@ "@projectsophon/workspace": "^2.0.0", "@solidstate/contracts": "^0.0.41", "@solidstate/hardhat-4byte-uploader": "^1.0.2", + "@solidstate/spec": "^0.0.41", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.3", "@types/chai": "^4.2.14", diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts new file mode 100644 index 00000000..37f64756 --- /dev/null +++ b/eth/test/DFERC1155.test.ts @@ -0,0 +1,19 @@ +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { defaultWorldFixture, World } from './utils/TestWorld'; + +describe('ERC1155', function () { + let world: World; + + async function worldFixture() { + const world = await loadFixture(defaultWorldFixture); + return world; + } + + beforeEach('load fixture', async function () { + world = await loadFixture(worldFixture); + }); + it('has correct functions', async function () { + // world.contract.mint(); + console.log(world.contract.address); + }); +}); diff --git a/package-lock.json b/package-lock.json index 3a964e28..38345b42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -225,6 +225,7 @@ "@projectsophon/workspace": "^2.0.0", "@solidstate/contracts": "^0.0.41", "@solidstate/hardhat-4byte-uploader": "^1.0.2", + "@solidstate/spec": "^0.0.41", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.3", "@types/chai": "^4.2.14", @@ -7113,6 +7114,24 @@ } } }, + "node_modules/@solidstate/library": { + "version": "0.0.41", + "resolved": "https://registry.npmjs.org/@solidstate/library/-/library-0.0.41.tgz", + "integrity": "sha512-JWn6wkN4QI/054XEjnzZe/ooNg0q2yq9kHe3EZEQyONSWzLYAFINr/c23dhUu4CnuR1gh2HeOpyOIufpIYSRvw==", + "dev": true, + "dependencies": { + "eth-permit": "^0.1.10" + } + }, + "node_modules/@solidstate/spec": { + "version": "0.0.41", + "resolved": "https://registry.npmjs.org/@solidstate/spec/-/spec-0.0.41.tgz", + "integrity": "sha512-yBKDVFr/tymCncdkJ0lpW9mvH3JXRD9VK1jjWg8nVio7CVXL3B5UWL5s/3Z1WQUGuyTJ4QpxT2jgbEohIgz0ZQ==", + "dev": true, + "dependencies": { + "@solidstate/library": "^0.0.41" + } + }, "node_modules/@spectrum-web-components/action-button": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@spectrum-web-components/action-button/-/action-button-0.7.3.tgz", @@ -15439,6 +15458,15 @@ "resolved": "eth", "link": true }, + "node_modules/eth-permit": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/eth-permit/-/eth-permit-0.1.10.tgz", + "integrity": "sha512-cMAjWvGEaCQoWSlKWBqkBMQvhCtXzVlkTCnYBxGRsCxznbBZSiYuOsHpTAb4EerRwQJkRgwd3vPvcZh3OA7Siw==", + "dev": true, + "dependencies": { + "utf8": "^3.0.0" + } + }, "node_modules/ethereum-cryptography": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", @@ -31100,6 +31128,12 @@ "node": ">=6.14.2" } }, + "node_modules/utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "dev": true + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -37487,6 +37521,24 @@ } } }, + "@solidstate/library": { + "version": "0.0.41", + "resolved": "https://registry.npmjs.org/@solidstate/library/-/library-0.0.41.tgz", + "integrity": "sha512-JWn6wkN4QI/054XEjnzZe/ooNg0q2yq9kHe3EZEQyONSWzLYAFINr/c23dhUu4CnuR1gh2HeOpyOIufpIYSRvw==", + "dev": true, + "requires": { + "eth-permit": "^0.1.10" + } + }, + "@solidstate/spec": { + "version": "0.0.41", + "resolved": "https://registry.npmjs.org/@solidstate/spec/-/spec-0.0.41.tgz", + "integrity": "sha512-yBKDVFr/tymCncdkJ0lpW9mvH3JXRD9VK1jjWg8nVio7CVXL3B5UWL5s/3Z1WQUGuyTJ4QpxT2jgbEohIgz0ZQ==", + "dev": true, + "requires": { + "@solidstate/library": "^0.0.41" + } + }, "@spectrum-web-components/action-button": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@spectrum-web-components/action-button/-/action-button-0.7.3.tgz", @@ -43713,6 +43765,7 @@ "@projectsophon/workspace": "^2.0.0", "@solidstate/contracts": "^0.0.41", "@solidstate/hardhat-4byte-uploader": "^1.0.2", + "@solidstate/spec": "^0.0.41", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.3", "@types/chai": "^4.2.14", @@ -43843,6 +43896,15 @@ } } }, + "eth-permit": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/eth-permit/-/eth-permit-0.1.10.tgz", + "integrity": "sha512-cMAjWvGEaCQoWSlKWBqkBMQvhCtXzVlkTCnYBxGRsCxznbBZSiYuOsHpTAb4EerRwQJkRgwd3vPvcZh3OA7Siw==", + "dev": true, + "requires": { + "utf8": "^3.0.0" + } + }, "ethereum-cryptography": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", @@ -55351,6 +55413,12 @@ "node-gyp-build": "^4.3.0" } }, + "utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "dev": true + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", From 9d240e7705782009272b337bc48bdeeb74646307 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Fri, 23 Sep 2022 22:36:19 +0100 Subject: [PATCH 03/55] temp: noodling around --- eth/contracts/DFToken.sol | 6 ++++++ eth/contracts/facets/DFArtifactFacet.sol | 3 +-- eth/test/DFERC1155.test.ts | 24 +++++++++++------------- 3 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 eth/contracts/DFToken.sol diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol new file mode 100644 index 00000000..b711cdae --- /dev/null +++ b/eth/contracts/DFToken.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; + +contract DFToken is SolidStateERC1155 {} diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 78d89ea8..35f93bc7 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.0; // Contract imports import {SolidStateERC721} from "@solidstate/contracts/token/ERC721/SolidStateERC721.sol"; import {ERC721BaseStorage} from "@solidstate/contracts/token/ERC721/base/ERC721BaseStorage.sol"; -import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; @@ -20,7 +19,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs} from "../DFTypes.sol"; -contract DFArtifactFacet is WithStorage, SolidStateERC721, SolidStateERC1155 { +contract DFArtifactFacet is WithStorage, SolidStateERC721 { using ERC721BaseStorage for ERC721BaseStorage.Layout; event PlanetProspected(address player, uint256 loc); diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index 37f64756..0b4f7924 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -1,19 +1,17 @@ -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { defaultWorldFixture, World } from './utils/TestWorld'; +import { DFToken, DFToken__factory } from '@dfdao/contracts/typechain'; +import { ethers } from 'hardhat'; -describe('ERC1155', function () { - let world: World; +const tokenURI = 'ERC1155Metadata.tokenURI'; - async function worldFixture() { - const world = await loadFixture(defaultWorldFixture); - return world; - } +describe('SolidStateERC1155', function () { + let token: DFToken; - beforeEach('load fixture', async function () { - world = await loadFixture(worldFixture); + beforeEach(async function () { + const [deployer] = await ethers.getSigners(); + token = await new DFToken__factory(deployer).deploy(); }); - it('has correct functions', async function () { - // world.contract.mint(); - console.log(world.contract.address); + + it('mints', async function () { + console.log('not yet!'); }); }); From 832b27db667a4e8a7cc2f0c57c2825cca89d3a58 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sat, 24 Sep 2022 14:43:46 +0100 Subject: [PATCH 04/55] test: working mint and uri tests --- eth/contracts/DFToken.sol | 23 ++++++++++++++++++++++- eth/test/DFERC1155.test.ts | 38 ++++++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol index b711cdae..28b1e849 100644 --- a/eth/contracts/DFToken.sol +++ b/eth/contracts/DFToken.sol @@ -3,4 +3,25 @@ pragma solidity ^0.8.0; import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; -contract DFToken is SolidStateERC1155 {} +// Note: We can call _mint and _setTokenUri directly in DFArtifactFacet, but I like having a wrapper +// This makes it more obvious when we are using the DFToken functions + +contract DFToken is SolidStateERC1155 { + function mint( + address account, + uint256 id, + uint256 amount, + bytes memory data + ) public { + _mint(account, id, amount, data); + } + + /** + * @notice set per-token metadata URI + * @param tokenId token whose metadata URI to set + * @param tokenURI per-token URI + */ + function setTokenURI(uint256 tokenId, string memory tokenURI) public { + _setTokenURI(tokenId, tokenURI); + } +} diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index 0b4f7924..7187dbeb 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -1,17 +1,47 @@ import { DFToken, DFToken__factory } from '@dfdao/contracts/typechain'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { expect } from 'chai'; import { ethers } from 'hardhat'; -const tokenURI = 'ERC1155Metadata.tokenURI'; +/** + * Once again, a friendly reminder that in ERC1155 tokenId refers to a collection of 1 or more + * tokens, NOT guaranteed to be unique instances of tokens. + */ describe('SolidStateERC1155', function () { let token: DFToken; + let deployer: SignerWithAddress; + let user1: SignerWithAddress; + const addressZero = ethers.constants.AddressZero; + const collectionId = ethers.constants.Zero; + const amount = ethers.constants.Two; + const tokenURI = 'ERC1155Metadata.tokenURI/{id}.json'; beforeEach(async function () { - const [deployer] = await ethers.getSigners(); + const signers = await ethers.getSigners(); + deployer = signers[0]; token = await new DFToken__factory(deployer).deploy(); + user1 = signers[1]; }); - it('mints', async function () { - console.log('not yet!'); + it('mints tokens for tokenId zero', async function () { + await expect(token.mint(user1.address, collectionId, amount, ethers.constants.HashZero)) + .to.emit(token, 'TransferSingle') + .withArgs(deployer.address, addressZero, user1.address, collectionId, amount); + + await expect( + token.connect(user1).mint(user1.address, collectionId, amount, ethers.constants.HashZero) + ) + .to.emit(token, 'TransferSingle') + .withArgs(user1.address, addressZero, user1.address, collectionId, amount); + // Each mint created 2 tokens in the same collection. Two mints = 4 tokens. + expect(await token.balanceOf(user1.address, collectionId)).to.equal(amount.mul(2)); + }); + it('sets tokenURI for tokenId zero', async function () { + await expect(token.setTokenURI(collectionId, tokenURI)) + .to.emit(token, 'URI') + .withArgs(tokenURI, collectionId); + + expect(await token.uri(collectionId)).to.equal(tokenURI); }); }); From 00000fb784bede506073ad0d80f60bc1c0821e38 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sat, 24 Sep 2022 16:57:06 +0100 Subject: [PATCH 05/55] chore: comment --- eth/test/DFERC1155.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index 7187dbeb..de609018 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -34,6 +34,7 @@ describe('SolidStateERC1155', function () { ) .to.emit(token, 'TransferSingle') .withArgs(user1.address, addressZero, user1.address, collectionId, amount); + // Each mint created 2 tokens in the same collection. Two mints = 4 tokens. expect(await token.balanceOf(user1.address, collectionId)).to.equal(amount.mul(2)); }); From 204c9ed10b7ca83c6e901b4d57e73cdd64b06fc6 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sun, 25 Sep 2022 15:29:51 +0100 Subject: [PATCH 06/55] feat: sample bit pack --- eth/contracts/DFToken.sol | 57 ++++++++++++++++++++++++++++++++++++++ eth/test/DFERC1155.test.ts | 9 ++++++ 2 files changed, 66 insertions(+) diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol index 28b1e849..aa204d75 100644 --- a/eth/contracts/DFToken.sol +++ b/eth/contracts/DFToken.sol @@ -2,11 +2,25 @@ pragma solidity ^0.8.0; import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; +import "hardhat/console.sol"; // Note: We can call _mint and _setTokenUri directly in DFArtifactFacet, but I like having a wrapper // This makes it more obvious when we are using the DFToken functions contract DFToken is SolidStateERC1155 { + enum ArtifactInfo { + Unknown, + Level, + ArtifactType, + Biome + } + + struct Artifact { + uint8 level; + uint8 artifactType; + uint8 biome; + } + function mint( address account, uint256 id, @@ -24,4 +38,47 @@ contract DFToken is SolidStateERC1155 { function setTokenURI(uint256 tokenId, string memory tokenURI) public { _setTokenURI(tokenId, tokenURI); } + + /** + * @notice calculate amount of bits to shift left + * @param index number of 1 byte words to shift from left + * @return shift length of left shift + */ + function calcBitShift(uint8 index) internal pure returns (uint8) { + uint8 maxVal = 32; + + require(index <= maxVal, "shift index is too high"); + require(index > 0, "shift index is too low"); + + uint256 bin = 8; + uint256 shift = 256; + return uint8(shift - (bin * index)); + } + + function encodeArtifact( + uint256 _level, + uint256 _artifactType, + uint256 _biome + ) public pure returns (uint256) { + uint256 level = _level << calcBitShift(uint8(ArtifactInfo.Level)); + uint256 artifactType = _artifactType << calcBitShift(uint8(ArtifactInfo.ArtifactType)); + uint256 biome = _biome << calcBitShift(uint8(ArtifactInfo.Biome)); + return level + artifactType + biome; + } + + function decodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { + bytes memory _b = abi.encodePacked(artifactId); + // 0 is left most element. 0 is given the property Unknown in ArtifactInfo. + + // Note: Bit shifting requires an index greater than zero. This is why the ArtifactInfo has + // Unknown as the zero property, so calcBitShift(ArtifactInfo.Level) is correct. + // As a consequence, we need to + // offset fetching the relevant byte from the artifactId by 1. + // However + uint8 level = uint8(_b[uint8(ArtifactInfo.Level) - 1]); + uint8 artifactType = uint8(_b[uint8(ArtifactInfo.ArtifactType) - 1]); + uint8 biome = uint8(_b[uint8(ArtifactInfo.Biome) - 1]); + return Artifact(level, artifactType, biome); + // return level + artifactType + biome; + } } diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index de609018..38342ce9 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -45,4 +45,13 @@ describe('SolidStateERC1155', function () { expect(await token.uri(collectionId)).to.equal(tokenURI); }); + it.only('logs bits', async function () { + const _level = '0xff'; + const _artifactType = '0x01'; + const _biome = '0xab'; + const res = await token.encodeArtifact(_level, _artifactType, _biome); + console.log(res._hex); + const { level, biome, artifactType } = await token.decodeArtifact(res); + console.log(level, biome, artifactType); + }); }); From 4341dfddf43b1aed0be1304ea311e562f573e11e Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Tue, 27 Sep 2022 10:28:47 +0100 Subject: [PATCH 07/55] feat: not working artifact refactor --- eth/contracts/DFToken.sol | 46 ++-- eth/contracts/DFTypes.sol | 21 ++ eth/contracts/facets/DFArtifactFacet.sol | 61 ++--- eth/contracts/facets/DFGetterFacet.sol | 226 +++++++++---------- eth/contracts/libraries/LibArtifactUtils.sol | 31 +-- eth/contracts/libraries/LibGameUtils.sol | 2 +- eth/test/DFERC1155.test.ts | 15 +- 7 files changed, 220 insertions(+), 182 deletions(-) diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol index aa204d75..4224fa0f 100644 --- a/eth/contracts/DFToken.sol +++ b/eth/contracts/DFToken.sol @@ -2,25 +2,12 @@ pragma solidity ^0.8.0; import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; +import {ArtifactProperties, ArtifactInfo, CollectionType, ArtifactType, ArtifactRarity, Biome} from "./DFTypes.sol"; import "hardhat/console.sol"; // Note: We can call _mint and _setTokenUri directly in DFArtifactFacet, but I like having a wrapper // This makes it more obvious when we are using the DFToken functions - contract DFToken is SolidStateERC1155 { - enum ArtifactInfo { - Unknown, - Level, - ArtifactType, - Biome - } - - struct Artifact { - uint8 level; - uint8 artifactType; - uint8 biome; - } - function mint( address account, uint256 id, @@ -55,18 +42,29 @@ contract DFToken is SolidStateERC1155 { return uint8(shift - (bin * index)); } + /** + * @notice Create the collection ID for a given artifact + * @param _collectionType type of artifact + * @param _rarity rarity of artifact + * @param _artifactType of artifact + * @param _biome of artifact + * @notice this is not a struct because I can't figure out how to bit shift a uint in a struct. + */ function encodeArtifact( - uint256 _level, + uint256 _collectionType, + uint256 _rarity, uint256 _artifactType, uint256 _biome ) public pure returns (uint256) { - uint256 level = _level << calcBitShift(uint8(ArtifactInfo.Level)); + uint256 collectionType = _collectionType << + calcBitShift(uint8(ArtifactInfo.CollectionType)); + uint256 rarity = _rarity << calcBitShift(uint8(ArtifactInfo.ArtifactRarity)); uint256 artifactType = _artifactType << calcBitShift(uint8(ArtifactInfo.ArtifactType)); uint256 biome = _biome << calcBitShift(uint8(ArtifactInfo.Biome)); - return level + artifactType + biome; + return collectionType + rarity + artifactType + biome; } - function decodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { + function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { bytes memory _b = abi.encodePacked(artifactId); // 0 is left most element. 0 is given the property Unknown in ArtifactInfo. @@ -75,10 +73,16 @@ contract DFToken is SolidStateERC1155 { // As a consequence, we need to // offset fetching the relevant byte from the artifactId by 1. // However - uint8 level = uint8(_b[uint8(ArtifactInfo.Level) - 1]); + uint8 collectionType = uint8(_b[uint8(ArtifactInfo.CollectionType) - 1]); + uint8 rarity = uint8(_b[uint8(ArtifactInfo.ArtifactRarity) - 1]); uint8 artifactType = uint8(_b[uint8(ArtifactInfo.ArtifactType) - 1]); uint8 biome = uint8(_b[uint8(ArtifactInfo.Biome) - 1]); - return Artifact(level, artifactType, biome); - // return level + artifactType + biome; + return + ArtifactProperties({ + collectionType: CollectionType(collectionType), + rarity: ArtifactRarity(rarity), + artifactType: ArtifactType(artifactType), + biome: Biome(biome) + }); } } diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 34c55522..0fd461b7 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -263,3 +263,24 @@ enum Biome { Lava, Corrupted } + +enum ArtifactInfo { + Unknown, + CollectionType, // Each bin of tokens gets an id (spaceships, artifacts, etc...) + ArtifactRarity, + ArtifactType, + Biome +} + +enum CollectionType { + Unknown, + Artifact, + Spaceship +} + +struct ArtifactProperties { + CollectionType collectionType; + ArtifactRarity rarity; + ArtifactType artifactType; + Biome biome; +} diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 35f93bc7..a3ac8abf 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -2,10 +2,9 @@ pragma solidity ^0.8.0; // Contract imports -import {SolidStateERC721} from "@solidstate/contracts/token/ERC721/SolidStateERC721.sol"; -import {ERC721BaseStorage} from "@solidstate/contracts/token/ERC721/base/ERC721BaseStorage.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; +import {DFToken} from "../DFToken.sol"; // Library Imports import {LibPermissions} from "../libraries/LibPermissions.sol"; @@ -17,11 +16,9 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs} from "../DFTypes.sol"; - -contract DFArtifactFacet is WithStorage, SolidStateERC721 { - using ERC721BaseStorage for ERC721BaseStorage.Layout; +import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; +contract DFArtifactFacet is WithStorage, DFToken { event PlanetProspected(address player, uint256 loc); event ArtifactFound(address player, uint256 artifactId, uint256 loc); event ArtifactDeposited(address player, uint256 artifactId, uint256 loc); @@ -71,7 +68,8 @@ contract DFArtifactFacet is WithStorage, SolidStateERC721 { { require(args.tokenId >= 1, "artifact id must be positive"); - _mint(args.owner, args.tokenId); + // Account, Id, Amount, Data + _mint(args.owner, args.tokenId, 1, ""); Artifact memory newArtifact = Artifact( true, @@ -94,44 +92,51 @@ contract DFArtifactFacet is WithStorage, SolidStateERC721 { return newArtifact; } - function getArtifact(uint256 tokenId) public view returns (Artifact memory) { - return gs().artifacts[tokenId]; + function getArtifact(uint256 tokenId) public view returns (ArtifactProperties memory) { + return DFToken.decodeArtifact(tokenId); + //return gs().artifacts[tokenId]; } - function getArtifactAtIndex(uint256 idx) public view returns (Artifact memory) { - return gs().artifacts[tokenByIndex(idx)]; - } + // function getArtifactAtIndex(uint256 idx) public view returns (Artifact memory) { + // return gs().artifacts[tokenByIndex(idx)]; + // } - function getPlayerArtifactIds(address playerId) public view returns (uint256[] memory) { - uint256 balance = balanceOf(playerId); - uint256[] memory results = new uint256[](balance); + // function getPlayerArtifactIds(address playerId) public view returns (uint256[] memory) { + // uint256 balance = balanceOf(playerId); + // uint256[] memory results = new uint256[](balance); - for (uint256 idx = 0; idx < balance; idx++) { - results[idx] = tokenOfOwnerByIndex(playerId, idx); - } + // for (uint256 idx = 0; idx < balance; idx++) { + // results[idx] = tokenOfOwnerByIndex(playerId, idx); + // } - return results; - } + // return results; + // } - function transferArtifact(uint256 tokenId, address newOwner) public onlyAdminOrCore { + function transferArtifact( + uint256 tokenId, + address owner, + address newOwner + ) public onlyAdminOrCore { if (newOwner == address(0)) { - _burn(tokenId); + // account, id, amount. + _burn(owner, tokenId, 1); } else { - _transfer(ownerOf(tokenId), newOwner, tokenId); + // operator sender receiver id amount data + _transfer(owner, owner, newOwner, tokenId, 1, ""); } } - function updateArtifact(Artifact memory updatedArtifact) public onlyAdminOrCore { + function updateArtifact(Artifact memory updatedArtifact, address owner) public onlyAdminOrCore { require( - ERC721BaseStorage.layout().exists(updatedArtifact.id), + doesArtifactExist(owner, updatedArtifact.id), "you cannot update an artifact that doesn't exist" ); gs().artifacts[updatedArtifact.id] = updatedArtifact; } - function doesArtifactExist(uint256 tokenId) public view returns (bool) { - return ERC721BaseStorage.layout().exists(tokenId); + function doesArtifactExist(address owner, uint256 tokenId) public view returns (bool) { + return balanceOf(owner, tokenId) > 0; } function findArtifact( @@ -287,7 +292,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC721 { function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { Artifact memory artifact = createArtifact(args); - transferArtifact(artifact.id, address(this)); + transferArtifact(artifact.id, address(this), address(this)); LibGameUtils._putArtifactOnPlanet(artifact.id, args.planetId); emit ArtifactFound(args.owner, artifact.id, args.planetId); diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 93ff1c38..f8188b1e 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -315,91 +315,91 @@ contract DFGetterFacet is WithStorage { return ret; } - function getArtifactById(uint256 artifactId) - public - view - returns (ArtifactWithMetadata memory ret) - { - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactId); - - address owner; - - try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { - owner = addr; - } catch Error(string memory) { - // artifact is probably burned / owned by 0x0, so owner is 0x0 - } catch (bytes memory) { - // this shouldn't happen - } - - ret = ArtifactWithMetadata({ - artifact: artifact, - upgrade: LibGameUtils._getUpgradeForArtifact(artifact), - timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), - owner: owner, - locationId: gs().artifactIdToPlanetId[artifact.id], - voyageId: gs().artifactIdToVoyageId[artifact.id] - }); - } - - function getArtifactsOnPlanet(uint256 locationId) - public - view - returns (ArtifactWithMetadata[] memory ret) - { - uint256[] memory artifactIds = gs().planetArtifacts[locationId]; - ret = new ArtifactWithMetadata[](artifactIds.length); - for (uint256 i = 0; i < artifactIds.length; i++) { - ret[i] = getArtifactById(artifactIds[i]); - } - return ret; - } - - function bulkGetPlanetArtifacts(uint256[] calldata planetIds) - public - view - returns (ArtifactWithMetadata[][] memory) - { - ArtifactWithMetadata[][] memory ret = new ArtifactWithMetadata[][](planetIds.length); - - for (uint256 i = 0; i < planetIds.length; i++) { - uint256[] memory planetOwnedArtifactIds = gs().planetArtifacts[planetIds[i]]; - ret[i] = bulkGetArtifactsByIds(planetOwnedArtifactIds); - } - - return ret; - } - - function bulkGetArtifactsByIds(uint256[] memory ids) - public - view - returns (ArtifactWithMetadata[] memory ret) - { - ret = new ArtifactWithMetadata[](ids.length); - - for (uint256 i = 0; i < ids.length; i++) { - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(ids[i]); - - address owner; - - try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { - owner = addr; - } catch Error(string memory) { - // artifact is probably burned or owned by 0x0, so owner is 0x0 - } catch (bytes memory) { - // this shouldn't happen - } - - ret[i] = ArtifactWithMetadata({ - artifact: artifact, - upgrade: LibGameUtils._getUpgradeForArtifact(artifact), - timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), - owner: owner, - locationId: gs().artifactIdToPlanetId[artifact.id], - voyageId: gs().artifactIdToVoyageId[artifact.id] - }); - } - } + // function getArtifactById(uint256 artifactId) + // public + // view + // returns (ArtifactWithMetadata memory ret) + // { + // Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactId); + + // address owner; + + // try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { + // owner = addr; + // } catch Error(string memory) { + // // artifact is probably burned / owned by 0x0, so owner is 0x0 + // } catch (bytes memory) { + // // this shouldn't happen + // } + + // ret = ArtifactWithMetadata({ + // artifact: artifact, + // upgrade: LibGameUtils._getUpgradeForArtifact(artifact), + // timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), + // owner: owner, + // locationId: gs().artifactIdToPlanetId[artifact.id], + // voyageId: gs().artifactIdToVoyageId[artifact.id] + // }); + // } + + // function getArtifactsOnPlanet(uint256 locationId) + // public + // view + // returns (ArtifactWithMetadata[] memory ret) + // { + // uint256[] memory artifactIds = gs().planetArtifacts[locationId]; + // ret = new ArtifactWithMetadata[](artifactIds.length); + // for (uint256 i = 0; i < artifactIds.length; i++) { + // ret[i] = getArtifactById(artifactIds[i]); + // } + // return ret; + // } + + // function bulkGetPlanetArtifacts(uint256[] calldata planetIds) + // public + // view + // returns (ArtifactWithMetadata[][] memory) + // { + // ArtifactWithMetadata[][] memory ret = new ArtifactWithMetadata[][](planetIds.length); + + // for (uint256 i = 0; i < planetIds.length; i++) { + // uint256[] memory planetOwnedArtifactIds = gs().planetArtifacts[planetIds[i]]; + // ret[i] = bulkGetArtifactsByIds(planetOwnedArtifactIds); + // } + + // return ret; + // } + + // function bulkGetArtifactsByIds(uint256[] memory ids) + // public + // view + // returns (ArtifactWithMetadata[] memory ret) + // { + // ret = new ArtifactWithMetadata[](ids.length); + + // for (uint256 i = 0; i < ids.length; i++) { + // Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(ids[i]); + + // address owner; + + // try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { + // owner = addr; + // } catch Error(string memory) { + // // artifact is probably burned or owned by 0x0, so owner is 0x0 + // } catch (bytes memory) { + // // this shouldn't happen + // } + + // ret[i] = ArtifactWithMetadata({ + // artifact: artifact, + // upgrade: LibGameUtils._getUpgradeForArtifact(artifact), + // timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), + // owner: owner, + // locationId: gs().artifactIdToPlanetId[artifact.id], + // voyageId: gs().artifactIdToVoyageId[artifact.id] + // }); + // } + // } /** * Get a group or artifacts based on their index, fetch all between startIdx & endIdx. @@ -408,32 +408,32 @@ contract DFGetterFacet is WithStorage { * @param startIdx index of the first element to get * @param endIdx index of the last element to get */ - function bulkGetArtifacts(uint256 startIdx, uint256 endIdx) - public - view - returns (ArtifactWithMetadata[] memory ret) - { - ret = new ArtifactWithMetadata[](endIdx - startIdx); - - for (uint256 i = startIdx; i < endIdx; i++) { - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifactAtIndex(i); - address owner = address(0); - - try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { - owner = addr; - } catch Error(string memory) { - // artifact is probably burned or owned by 0x0, so owner is 0x0 - } catch (bytes memory) { - // this shouldn't happen - } - ret[i - startIdx] = ArtifactWithMetadata({ - artifact: artifact, - upgrade: LibGameUtils._getUpgradeForArtifact(artifact), - timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), - owner: owner, - locationId: gs().artifactIdToPlanetId[artifact.id], - voyageId: gs().artifactIdToVoyageId[artifact.id] - }); - } - } + // function bulkGetArtifacts(uint256 startIdx, uint256 endIdx) + // public + // view + // returns (ArtifactWithMetadata[] memory ret) + // { + // ret = new ArtifactWithMetadata[](endIdx - startIdx); + + // for (uint256 i = startIdx; i < endIdx; i++) { + // Artifact memory artifact = DFArtifactFacet(address(this)).getArtifactAtIndex(i); + // address owner = address(0); + + // try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { + // owner = addr; + // } catch Error(string memory) { + // // artifact is probably burned or owned by 0x0, so owner is 0x0 + // } catch (bytes memory) { + // // this shouldn't happen + // } + // ret[i - startIdx] = ArtifactWithMetadata({ + // artifact: artifact, + // upgrade: LibGameUtils._getUpgradeForArtifact(artifact), + // timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), + // owner: owner, + // locationId: gs().artifactIdToPlanetId[artifact.id], + // voyageId: gs().artifactIdToVoyageId[artifact.id] + // }); + // } + // } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 9df35d15..740016d7 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -11,7 +11,7 @@ import {LibGameUtils} from "./LibGameUtils.sol"; import {LibStorage, GameStorage, GameConstants} from "./LibStorage.sol"; // Type imports -import {Biome, Planet, PlanetType, Artifact, ArtifactType, ArtifactRarity, DFPFindArtifactArgs, DFTCreateArtifactArgs} from "../DFTypes.sol"; +import {Biome, Planet, PlanetType, Artifact, ArtifactType, ArtifactRarity, DFPFindArtifactArgs, DFTCreateArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; library LibArtifactUtils { function gs() internal pure returns (GameStorage storage) { @@ -105,12 +105,12 @@ library LibArtifactUtils { DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( artifactSeed, - msg.sender, + msg.sender, // discoverer args.planetId, LibGameUtils.artifactRarityFromPlanetLevel(levelBonus + planet.planetLevel), biome, artifactType, - args.coreAddress, + args.coreAddress, // owner address(0) ); @@ -252,12 +252,14 @@ library LibArtifactUtils { if (shouldDeactivateAndBurn) { artifact.lastDeactivated = block.timestamp; // immediately deactivate - DFArtifactFacet(address(this)).updateArtifact(artifact); // save artifact state immediately, because _takeArtifactOffPlanet will access pull it from tokens contract + // artifact, owner + DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); // save artifact state immediately, because _takeArtifactOffPlanet will access pull it from tokens contract emit ArtifactDeactivated(msg.sender, artifactId, locationId); // burn it after use. will be owned by contract but not on a planet anyone can control LibGameUtils._takeArtifactOffPlanet(artifactId, locationId); } else { - DFArtifactFacet(address(this)).updateArtifact(artifact); + // artifact, owner + DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); } // this is fine even tho some artifacts are immediately deactivated, because @@ -282,7 +284,7 @@ library LibArtifactUtils { artifact.lastDeactivated = block.timestamp; artifact.wormholeTo = 0; emit ArtifactDeactivated(msg.sender, artifact.id, locationId); - DFArtifactFacet(address(this)).updateArtifact(artifact); + DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); bool shouldBurn = artifact.artifactType == ArtifactType.PlanetaryShield || artifact.artifactType == ArtifactType.PhotoidCannon; @@ -304,12 +306,12 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.planetType == PlanetType.TRADING_POST, "can only deposit on trading posts"); require( - DFArtifactFacet(address(this)).ownerOf(artifactId) == msg.sender, + DFArtifactFacet(address(this)).balanceOf(msg.sender, artifactId) > 0, "you can only deposit artifacts you own" ); require(planet.owner == msg.sender, "you can only deposit on a planet you own"); - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactId); + ArtifactProperties memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactId); require( planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to deposit this artifact" @@ -319,8 +321,8 @@ library LibArtifactUtils { require(gs().planetArtifacts[locationId].length < 5, "too many artifacts on this planet"); LibGameUtils._putArtifactOnPlanet(artifactId, locationId); - - DFArtifactFacet(address(this)).transferArtifact(artifactId, coreAddress); + // artifactId, curr owner, new owner + DFArtifactFacet(address(this)).transferArtifact(artifactId, msg.sender, coreAddress); } function withdrawArtifact(uint256 locationId, uint256 artifactId) public { @@ -342,7 +344,8 @@ library LibArtifactUtils { require(!isSpaceship(artifact.artifactType), "cannot withdraw spaceships"); LibGameUtils._takeArtifactOffPlanet(artifactId, locationId); - DFArtifactFacet(address(this)).transferArtifact(artifactId, msg.sender); + // artifactId, curr owner, new owner + DFArtifactFacet(address(this)).transferArtifact(artifactId, address(this), msg.sender); } function prospectPlanet(uint256 locationId) public { @@ -364,9 +367,11 @@ library LibArtifactUtils { uint256[] memory artifactIds = gs().planetArtifacts[locationId]; for (uint256 i = 0; i < artifactIds.length; i++) { - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactIds[i]); + ArtifactProperties memory artifact = DFArtifactFacet(address(this)).getArtifact( + artifactIds[i] + ); if ( - artifact.artifactType == ArtifactType.ShipGear && msg.sender == artifact.controller + artifact.artifactType == ArtifactType.ShipGear // && msg.sender == artifact.controller ) { return true; } diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index ded7d9d3..814f125f 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -10,7 +10,7 @@ import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; -import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; +import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactProperties, ArtifactType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; library LibGameUtils { function gs() internal pure returns (GameStorage storage) { diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index 38342ce9..5e7d7d3b 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -45,13 +45,16 @@ describe('SolidStateERC1155', function () { expect(await token.uri(collectionId)).to.equal(tokenURI); }); - it.only('logs bits', async function () { - const _level = '0xff'; + it('logs bits', async function () { + const _collectionType = '0x01'; + const _rarity = '0xff'; const _artifactType = '0x01'; const _biome = '0xab'; - const res = await token.encodeArtifact(_level, _artifactType, _biome); - console.log(res._hex); - const { level, biome, artifactType } = await token.decodeArtifact(res); - console.log(level, biome, artifactType); + const res = await token.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); + const { collectionType, rarity, biome, artifactType } = await token.decodeArtifact(res); + expect(collectionType).to.equal(Number(_collectionType)); + expect(rarity).to.equal(Number(_rarity)); + expect(biome).to.equal(Number(_biome)); + expect(artifactType).to.equal(Number(_artifactType)); }); }); From 977ddd8fb4101c803d19110b0c2b55be7460d1a6 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Tue, 27 Sep 2022 16:15:45 +0100 Subject: [PATCH 08/55] temp: working artifact activate and deactivate --- eth/contracts/DFToken.sol | 61 +- eth/contracts/DFTypes.sol | 5 +- eth/contracts/facets/DFAdminFacet.sol | 13 +- eth/contracts/facets/DFArtifactFacet.sol | 14 +- eth/contracts/facets/DFGetterFacet.sol | 36 +- eth/contracts/facets/DFMoveFacet.sol | 118 +-- eth/contracts/libraries/LibArtifactUtils.sol | 147 ++- eth/contracts/libraries/LibGameUtils.sol | 69 +- eth/contracts/libraries/LibPlanet.sol | 3 +- eth/contracts/libraries/LibStorage.sol | 4 +- eth/hardhat.config.ts | 1 + eth/package.json | 1 + eth/tasks/deploy.ts | 12 +- eth/test/DFERC1155.test.ts | 23 +- eth/test/NewDFArtifacts.test.ts | 903 +++++++++++++++++++ eth/test/utils/TestUtils.ts | 21 +- eth/test/utils/WorldConstants.ts | 8 +- packages/types/src/index.ts | 1 + packages/types/src/token.ts | 18 + 19 files changed, 1259 insertions(+), 199 deletions(-) create mode 100644 eth/test/NewDFArtifacts.test.ts create mode 100644 packages/types/src/token.ts diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol index 4224fa0f..4b304c67 100644 --- a/eth/contracts/DFToken.sol +++ b/eth/contracts/DFToken.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; -import {ArtifactProperties, ArtifactInfo, CollectionType, ArtifactType, ArtifactRarity, Biome} from "./DFTypes.sol"; +import {ArtifactProperties, TokenInfo, CollectionType, ArtifactType, ArtifactRarity, Biome} from "./DFTypes.sol"; import "hardhat/console.sol"; // Note: We can call _mint and _setTokenUri directly in DFArtifactFacet, but I like having a wrapper @@ -55,34 +55,51 @@ contract DFToken is SolidStateERC1155 { uint256 _rarity, uint256 _artifactType, uint256 _biome - ) public pure returns (uint256) { - uint256 collectionType = _collectionType << - calcBitShift(uint8(ArtifactInfo.CollectionType)); - uint256 rarity = _rarity << calcBitShift(uint8(ArtifactInfo.ArtifactRarity)); - uint256 artifactType = _artifactType << calcBitShift(uint8(ArtifactInfo.ArtifactType)); - uint256 biome = _biome << calcBitShift(uint8(ArtifactInfo.Biome)); + ) public view returns (uint256) { + uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); + uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); + uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); + uint256 biome = _biome << calcBitShift(uint8(TokenInfo.Biome)); return collectionType + rarity + artifactType + biome; } - function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { + // Collection Type should be Spaceship. ArtifactType should be type of ship. + function encodeSpaceship(uint256 _collectionType, uint256 _artifactType) + public + view + returns (uint256) + { + uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); + uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); + return collectionType + artifactType; + } + + function decodeArtifact(uint256 artifactId) public view returns (ArtifactProperties memory) { bytes memory _b = abi.encodePacked(artifactId); - // 0 is left most element. 0 is given the property Unknown in ArtifactInfo. + // 0 is left most element. 0 is given the property Unknown in TokenInfo. - // Note: Bit shifting requires an index greater than zero. This is why the ArtifactInfo has - // Unknown as the zero property, so calcBitShift(ArtifactInfo.Level) is correct. + // Note: Bit shifting requires an index greater than zero. This is why the TokenInfo has + // Unknown as the zero property, so calcBitShift(TokenInfo.Level) is correct. // As a consequence, we need to // offset fetching the relevant byte from the artifactId by 1. // However - uint8 collectionType = uint8(_b[uint8(ArtifactInfo.CollectionType) - 1]); - uint8 rarity = uint8(_b[uint8(ArtifactInfo.ArtifactRarity) - 1]); - uint8 artifactType = uint8(_b[uint8(ArtifactInfo.ArtifactType) - 1]); - uint8 biome = uint8(_b[uint8(ArtifactInfo.Biome) - 1]); - return - ArtifactProperties({ - collectionType: CollectionType(collectionType), - rarity: ArtifactRarity(rarity), - artifactType: ArtifactType(artifactType), - biome: Biome(biome) - }); + uint8 collectionType = uint8(_b[uint8(TokenInfo.CollectionType) - 1]); + uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); + uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); + uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); + // console.log("collectionType %s", collectionType); + // console.log("rarity %s", rarity); + // console.log("artifactType %s", artifactType); + // console.log("biome %s", biome); + + ArtifactProperties memory a = ArtifactProperties({ + id: artifactId, + collectionType: CollectionType(collectionType), + rarity: ArtifactRarity(rarity), + artifactType: ArtifactType(artifactType), + planetBiome: Biome(biome) + }); + + return a; } } diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 0fd461b7..9ce896e8 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -264,7 +264,7 @@ enum Biome { Corrupted } -enum ArtifactInfo { +enum TokenInfo { Unknown, CollectionType, // Each bin of tokens gets an id (spaceships, artifacts, etc...) ArtifactRarity, @@ -279,8 +279,9 @@ enum CollectionType { } struct ArtifactProperties { + uint256 id; CollectionType collectionType; ArtifactRarity rarity; ArtifactType artifactType; - Biome biome; + Biome planetBiome; } diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 19efb8fa..971b3d6b 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -10,14 +10,17 @@ import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; // Storage imports import {WithStorage} from "../libraries/LibStorage.sol"; +import {DFArtifactFacet} from "./DFArtifactFacet.sol"; + // Type imports -import {SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, Artifact, ArtifactType, Player, Planet} from "../DFTypes.sol"; +import {SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, Artifact, ArtifactType, Player, Planet} from "../DFTypes.sol"; contract DFAdminFacet is WithStorage { event AdminOwnershipChanged(uint256 loc, address newOwner); event AdminPlanetCreated(uint256 loc); event AdminGiveSpaceship(uint256 loc, address owner, ArtifactType artifactType); event PauseStateChanged(bool paused); + event AdminArtifactCreated(address player, uint256 artifactId, uint256 loc); ///////////////////////////// /// Administrative Engine /// @@ -161,4 +164,12 @@ contract DFAdminFacet is WithStorage { function setPlanetTransferEnabled(bool enabled) public onlyAdmin { gameConstants().PLANET_TRANSFER_ENABLED = enabled; } + + function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { + Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact(args); + // TODO: Remove this redundant logic ? + DFArtifactFacet(address(this)).transferArtifact(artifact.id, address(this), address(this)); + LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); + emit AdminArtifactCreated(args.owner, artifact.id, args.planetId); + } } diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index a3ac8abf..0b2bc6ad 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -17,6 +17,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; +import "hardhat/console.sol"; contract DFArtifactFacet is WithStorage, DFToken { event PlanetProspected(address player, uint256 loc); @@ -162,6 +163,7 @@ contract DFArtifactFacet is WithStorage, DFToken { ); } + console.log("finding artifact..."); uint256 foundArtifactId = LibArtifactUtils.findArtifact( DFPFindArtifactArgs(planetId, biomebase, address(this)) ); @@ -177,7 +179,7 @@ contract DFArtifactFacet is WithStorage, DFToken { LibArtifactUtils.depositArtifact(locationId, artifactId, address(this)); - emit ArtifactDeposited(msg.sender, artifactId, locationId); + emit ArtifactDeposited(msg.sender, locationId, artifactId); } // withdraws the given artifact from the given planet. you must own the planet, @@ -187,7 +189,7 @@ contract DFArtifactFacet is WithStorage, DFToken { LibArtifactUtils.withdrawArtifact(locationId, artifactId); - emit ArtifactWithdrawn(msg.sender, artifactId, locationId); + emit ArtifactWithdrawn(msg.sender, locationId, artifactId); } // activates the given artifact on the given planet. the artifact must have @@ -289,12 +291,4 @@ contract DFArtifactFacet is WithStorage, DFToken { gs().players[msg.sender].claimedShips = true; } - - function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { - Artifact memory artifact = createArtifact(args); - transferArtifact(artifact.id, address(this), address(this)); - LibGameUtils._putArtifactOnPlanet(artifact.id, args.planetId); - - emit ArtifactFound(args.owner, artifact.id, args.planetId); - } } diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index f8188b1e..008a36ee 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -12,7 +12,8 @@ import {LibGameUtils} from "../libraries/LibGameUtils.sol"; import {WithStorage, SnarkConstants, GameConstants} from "../libraries/LibStorage.sol"; // Type imports -import {RevealedCoords, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, ArtifactWithMetadata, Upgrade, Artifact} from "../DFTypes.sol"; +import {RevealedCoords, ArtifactProperties, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, ArtifactWithMetadata, Upgrade, Artifact} from "../DFTypes.sol"; +import "hardhat/console.sol"; contract DFGetterFacet is WithStorage { // FIRST-LEVEL GETTERS - mirrors the solidity autogenerated toplevel getters, but for GameStorage @@ -342,18 +343,27 @@ contract DFGetterFacet is WithStorage { // }); // } - // function getArtifactsOnPlanet(uint256 locationId) - // public - // view - // returns (ArtifactWithMetadata[] memory ret) - // { - // uint256[] memory artifactIds = gs().planetArtifacts[locationId]; - // ret = new ArtifactWithMetadata[](artifactIds.length); - // for (uint256 i = 0; i < artifactIds.length; i++) { - // ret[i] = getArtifactById(artifactIds[i]); - // } - // return ret; - // } + function getArtifactsOnPlanet(uint256 locationId) + public + view + returns (ArtifactProperties[] memory ret) + { + uint256[] memory artifactIds = gs().planetArtifacts[locationId]; + ret = new ArtifactProperties[](artifactIds.length); + for (uint256 i = 0; i < artifactIds.length; i++) { + ret[i] = DFArtifactFacet(address(this)).decodeArtifact(artifactIds[i]); + } + return ret; + } + + function getActiveArtifactOnPlanet(uint256 locationId) + public + view + returns (ArtifactProperties memory ret) + { + uint256 artifactId = gs().planetActiveArtifact[locationId]; + return DFArtifactFacet(address(this)).getArtifact(artifactId); + } // function bulkGetPlanetArtifacts(uint256[] calldata planetIds) // public diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index f358fb79..042f4239 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -111,18 +111,20 @@ contract DFMoveFacet is WithStorage { ArrivalType arrivalType = ArrivalType.Normal; Upgrade memory temporaryUpgrade = LibGameUtils.defaultUpgrade(); - (bool wormholePresent, uint256 distModifier) = _checkWormhole(args); - if (wormholePresent) { - effectiveDistTimesHundred /= distModifier; - arrivalType = ArrivalType.Wormhole; - } + // TODO: Add back wormhole + // (bool wormholePresent, uint256 distModifier) = _checkWormhole(args); + // if (wormholePresent) { + // effectiveDistTimesHundred /= distModifier; + // arrivalType = ArrivalType.Wormhole; + // } if (!_isSpaceshipMove(args)) { - (bool photoidPresent, Upgrade memory newTempUpgrade) = _checkPhotoid(args); - if (photoidPresent) { - temporaryUpgrade = newTempUpgrade; - arrivalType = ArrivalType.Photoid; - } + // TODO: Add back photoid + // (bool photoidPresent, Upgrade memory newTempUpgrade) = _checkPhotoid(args); + // if (photoidPresent) { + // temporaryUpgrade = newTempUpgrade; + // arrivalType = ArrivalType.Photoid; + // } } _removeSpaceshipEffectsFromOriginPlanet(args); @@ -276,59 +278,59 @@ contract DFMoveFacet is WithStorage { return the modified distance between the origin and target planet. */ - function _checkWormhole(DFPMoveArgs memory args) - private - view - returns (bool wormholePresent, uint256 effectiveDistModifier) - { - Artifact memory relevantWormhole; - Artifact memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); - Artifact memory activeArtifactTo = LibGameUtils.getActiveArtifact(args.newLoc); - - // TODO: take the greater rarity of these, or disallow wormholes between planets that - // already have a wormhole between them - if ( - activeArtifactFrom.isInitialized && - activeArtifactFrom.artifactType == ArtifactType.Wormhole && - activeArtifactFrom.wormholeTo == args.newLoc - ) { - relevantWormhole = activeArtifactFrom; - } else if ( - activeArtifactTo.isInitialized && - activeArtifactTo.artifactType == ArtifactType.Wormhole && - activeArtifactTo.wormholeTo == args.oldLoc - ) { - relevantWormhole = activeArtifactTo; - } - - if (relevantWormhole.isInitialized) { - wormholePresent = true; - uint256[6] memory speedBoosts = [uint256(1), 2, 4, 8, 16, 32]; - effectiveDistModifier = speedBoosts[uint256(relevantWormhole.rarity)]; - } - } + // function _checkWormhole(DFPMoveArgs memory args) + // private + // view + // returns (bool wormholePresent, uint256 effectiveDistModifier) + // { + // Artifact memory relevantWormhole; + // Artifact memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); + // Artifact memory activeArtifactTo = LibGameUtils.getActiveArtifact(args.newLoc); + + // // TODO: take the greater rarity of these, or disallow wormholes between planets that + // // already have a wormhole between them + // if ( + // activeArtifactFrom.isInitialized && + // activeArtifactFrom.artifactType == ArtifactType.Wormhole && + // activeArtifactFrom.wormholeTo == args.newLoc + // ) { + // relevantWormhole = activeArtifactFrom; + // } else if ( + // activeArtifactTo.isInitialized && + // activeArtifactTo.artifactType == ArtifactType.Wormhole && + // activeArtifactTo.wormholeTo == args.oldLoc + // ) { + // relevantWormhole = activeArtifactTo; + // } + + // if (relevantWormhole.isInitialized) { + // wormholePresent = true; + // uint256[6] memory speedBoosts = [uint256(1), 2, 4, 8, 16, 32]; + // effectiveDistModifier = speedBoosts[uint256(relevantWormhole.rarity)]; + // } + // } /** If an active photoid cannon is present, return the upgrade that should be applied to the origin planet. */ - function _checkPhotoid(DFPMoveArgs memory args) - private - returns (bool photoidPresent, Upgrade memory temporaryUpgrade) - { - Artifact memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); - if ( - activeArtifactFrom.isInitialized && - activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && - block.timestamp - activeArtifactFrom.lastActivated >= - gameConstants().PHOTOID_ACTIVATION_DELAY - ) { - photoidPresent = true; - LibArtifactUtils.deactivateArtifact(args.oldLoc); - temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); - } - } + // function _checkPhotoid(DFPMoveArgs memory args) + // private + // returns (bool photoidPresent, Upgrade memory temporaryUpgrade) + // { + // Artifact memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); + // if ( + // activeArtifactFrom.isInitialized && + // activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && + // block.timestamp - activeArtifactFrom.lastActivated >= + // gameConstants().PHOTOID_ACTIVATION_DELAY + // ) { + // photoidPresent = true; + // LibArtifactUtils.deactivateArtifact(args.oldLoc); + // temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); + // } + // } function _abandonPlanet(DFPMoveArgs memory args) private @@ -436,7 +438,7 @@ contract DFMoveFacet is WithStorage { }); if (args.movedArtifactId != 0) { - LibGameUtils._takeArtifactOffPlanet(args.movedArtifactId, args.oldLoc); + LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); gs().artifactIdToVoyageId[args.movedArtifactId] = gs().planetEventsCount; } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 740016d7..b0b77c9d 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -11,7 +11,8 @@ import {LibGameUtils} from "./LibGameUtils.sol"; import {LibStorage, GameStorage, GameConstants} from "./LibStorage.sol"; // Type imports -import {Biome, Planet, PlanetType, Artifact, ArtifactType, ArtifactRarity, DFPFindArtifactArgs, DFTCreateArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; +import {Biome, Planet, PlanetType, Artifact, ArtifactType, ArtifactRarity, CollectionType, DFPFindArtifactArgs, DFTCreateArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; +import "hardhat/console.sol"; library LibArtifactUtils { function gs() internal pure returns (GameStorage storage) { @@ -63,9 +64,14 @@ library LibArtifactUtils { require(shipType <= ArtifactType.ShipTitan && shipType >= ArtifactType.ShipMothership); uint256 id = uint256(keccak256(abi.encodePacked(planetId, gs().miscNonce++))); - + uint256 tokenId = DFArtifactFacet(address(this)).encodeArtifact( + uint8(CollectionType.Spaceship), + uint8(ArtifactRarity.Unknown), + uint8(shipType), + uint8(Biome.Unknown) + ); DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( - id, + tokenId, msg.sender, planetId, ArtifactRarity.Unknown, @@ -78,7 +84,7 @@ library LibArtifactUtils { Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( createArtifactArgs ); - LibGameUtils._putArtifactOnPlanet(foundArtifact.id, planetId); + LibGameUtils._putArtifactOnPlanet(planetId, foundArtifact.id); return id; } @@ -103,11 +109,27 @@ library LibArtifactUtils { (ArtifactType artifactType, uint256 levelBonus) = LibGameUtils ._randomArtifactTypeAndLevelBonus(artifactSeed, biome, planet.spaceType); + ArtifactRarity rarity = LibGameUtils.artifactRarityFromPlanetLevel( + levelBonus + planet.planetLevel + ); + + // console.log("LAU: collectionType %s", uint8(CollectionType.Artifact)); + // console.log("LAU: rarity %s", uint8(rarity)); + // console.log("artifactType %s", uint8(artifactType)); + // console.log("biome %s", uint8(biome)); + + uint256 tokenId = DFArtifactFacet(address(this)).encodeArtifact( + uint8(CollectionType.Artifact), + uint8(rarity), + uint8(artifactType), + uint8(biome) + ); + DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( - artifactSeed, + tokenId, msg.sender, // discoverer args.planetId, - LibGameUtils.artifactRarityFromPlanetLevel(levelBonus + planet.planetLevel), + rarity, biome, artifactType, args.coreAddress, // owner @@ -118,7 +140,7 @@ library LibArtifactUtils { createArtifactArgs ); - LibGameUtils._putArtifactOnPlanet(foundArtifact.id, args.planetId); + LibGameUtils._putArtifactOnPlanet(args.planetId, foundArtifact.id); planet.hasTriedFindingArtifact = true; gs().players[msg.sender].score += gameConstants().ARTIFACT_POINT_VALUES[ @@ -134,7 +156,9 @@ library LibArtifactUtils { uint256 wormholeTo ) public { Planet storage planet = gs().planets[locationId]; - Artifact storage artifact = gs().artifacts[artifactId]; + ArtifactProperties memory artifact = DFArtifactFacet(address(this)).decodeArtifact( + artifactId + ); require( LibGameUtils.isArtifactOnPlanet(locationId, artifactId), @@ -142,12 +166,13 @@ library LibArtifactUtils { ); if (isSpaceship(artifact.artifactType)) { - activateSpaceshipArtifact(locationId, artifactId, planet, artifact); + // This breaks Crescent functionality + // activateSpaceshipArtifact(locationId, artifactId, planet, artifact); } else { activateNonSpaceshipArtifact(locationId, artifactId, wormholeTo, planet, artifact); } - artifact.activations++; + // artifact.activations++; } function activateSpaceshipArtifact( @@ -184,7 +209,7 @@ library LibArtifactUtils { } planet.planetType = PlanetType.SILVER_MINE; - emit ArtifactActivated(msg.sender, artifactId, locationId); + emit ArtifactActivated(msg.sender, locationId, artifactId); } } @@ -193,47 +218,57 @@ library LibArtifactUtils { uint256 artifactId, uint256 wormholeTo, Planet storage planet, - Artifact memory artifact + ArtifactProperties memory artifact ) private { + console.log("activating %s on %s", locationId, artifactId); require( planet.owner == msg.sender, "you must own the planet you are activating an artifact on" ); require( - !LibGameUtils.getActiveArtifact(locationId).isInitialized, + LibGameUtils.getActiveArtifact(locationId).collectionType == CollectionType.Unknown, "there is already an active artifact on this planet" ); require(!planet.destroyed, "planet is destroyed"); - require(artifact.isInitialized, "this artifact is not on this planet"); + uint256 length = gs().planetArtifacts[locationId].length; + console.log("artifacts on %s: %s", locationId, length); + require( + LibGameUtils.getPlanetArtifact(locationId, artifactId).collectionType != + CollectionType.Unknown, + "this artifact is not on this planet" + ); // Unknown is the 0th one, Monolith is the 1st, and so on. // TODO v0.6: consider photoid canon uint256[10] memory artifactCooldownsHours = [uint256(24), 0, 0, 0, 0, 4, 4, 24, 24, 24]; - - require( - artifact.lastDeactivated + - artifactCooldownsHours[uint256(artifact.artifactType)] * - 60 * - 60 < - block.timestamp, - "this artifact is on a cooldown" - ); + // TODO: Cooldown is broken + // require( + // artifact.lastDeactivated + + // artifactCooldownsHours[uint256(artifact.artifactType)] * + // 60 * + // 60 < + // block.timestamp, + // "this artifact is on a cooldown" + // ); bool shouldDeactivateAndBurn = false; - artifact.lastActivated = block.timestamp; - emit ArtifactActivated(msg.sender, artifactId, locationId); - - if (artifact.artifactType == ArtifactType.Wormhole) { - require(wormholeTo != 0, "you must provide a wormholeTo to activate a wormhole"); - require( - gs().planets[wormholeTo].owner == msg.sender, - "you can only create a wormhole to a planet you own" - ); - require(!gs().planets[wormholeTo].destroyed, "planet destroyed"); - artifact.wormholeTo = wormholeTo; - } else if (artifact.artifactType == ArtifactType.BloomFilter) { + // artifact.lastActivated = block.timestamp; + gs().planetActiveArtifact[locationId] = artifactId; + emit ArtifactActivated(msg.sender, locationId, artifactId); + + // TODO: Wormhole is broken + + // if (artifact.artifactType == ArtifactType.Wormhole) { + // require(wormholeTo != 0, "you must provide a wormholeTo to activate a wormhole"); + // require( + // gs().planets[wormholeTo].owner == msg.sender, + // "you can only create a wormhole to a planet you own" + // ); + // require(!gs().planets[wormholeTo].destroyed, "planet destroyed"); + // artifact.wormholeTo = wormholeTo; + if (artifact.artifactType == ArtifactType.BloomFilter) { require( 2 * uint256(artifact.rarity) >= planet.planetLevel, "artifact is not powerful enough to apply effect to this planet level" @@ -251,17 +286,19 @@ library LibArtifactUtils { } if (shouldDeactivateAndBurn) { - artifact.lastDeactivated = block.timestamp; // immediately deactivate + // artifact.lastDeactivated = block.timestamp; // immediately deactivate // artifact, owner - DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); // save artifact state immediately, because _takeArtifactOffPlanet will access pull it from tokens contract - emit ArtifactDeactivated(msg.sender, artifactId, locationId); + // TODO: We aren't updating the artifact beacuse there are no properties to change. + // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); // save artifact state immediately, because _takeArtifactOffPlanet will access pull it from tokens contract + emit ArtifactDeactivated(msg.sender, locationId, artifactId); // burn it after use. will be owned by contract but not on a planet anyone can control - LibGameUtils._takeArtifactOffPlanet(artifactId, locationId); + LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); } else { + // TODO: We aren't updating the artifact beacuse there are no properties to change. // artifact, owner - DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); + // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); } - + console.log("buffing planet"); // this is fine even tho some artifacts are immediately deactivated, because // those artifacts do not buff the planet. LibGameUtils._buffPlanet(locationId, LibGameUtils._getUpgradeForArtifact(artifact)); @@ -277,14 +314,19 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); - Artifact memory artifact = LibGameUtils.getActiveArtifact(locationId); + ArtifactProperties memory artifact = LibGameUtils.getActiveArtifact(locationId); - require(artifact.isInitialized, "this artifact is not activated on this planet"); + require( + artifact.collectionType != CollectionType.Unknown, + "this artifact is not activated on this planet" + ); - artifact.lastDeactivated = block.timestamp; - artifact.wormholeTo = 0; + // artifact.lastDeactivated = block.timestamp; + // artifact.wormholeTo = 0; + gs().planetActiveArtifact[locationId] = 0; emit ArtifactDeactivated(msg.sender, artifact.id, locationId); - DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); + // TODO: Figure out update artifact + // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); bool shouldBurn = artifact.artifactType == ArtifactType.PlanetaryShield || artifact.artifactType == ArtifactType.PhotoidCannon; @@ -320,7 +362,7 @@ library LibArtifactUtils { require(gs().planetArtifacts[locationId].length < 5, "too many artifacts on this planet"); - LibGameUtils._putArtifactOnPlanet(artifactId, locationId); + LibGameUtils._putArtifactOnPlanet(locationId, artifactId); // artifactId, curr owner, new owner DFArtifactFacet(address(this)).transferArtifact(artifactId, msg.sender, coreAddress); } @@ -334,15 +376,19 @@ library LibArtifactUtils { ); require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.owner == msg.sender, "you can only withdraw from a planet you own"); - Artifact memory artifact = LibGameUtils.getPlanetArtifact(locationId, artifactId); - require(artifact.isInitialized, "this artifact is not on this planet"); + ArtifactProperties memory artifact = LibGameUtils.getPlanetArtifact(locationId, artifactId); + // TODO: Write is initialized function. + require( + artifact.collectionType != CollectionType.Unknown, + "this artifact is not on this planet" + ); require( planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to withdraw this artifact" ); require(!isSpaceship(artifact.artifactType), "cannot withdraw spaceships"); - LibGameUtils._takeArtifactOffPlanet(artifactId, locationId); + LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); // artifactId, curr owner, new owner DFArtifactFacet(address(this)).transferArtifact(artifactId, address(this), msg.sender); @@ -371,6 +417,7 @@ library LibArtifactUtils { artifactIds[i] ); if ( + // TODO: Gear is broken artifact.artifactType == ArtifactType.ShipGear // && msg.sender == artifact.controller ) { return true; diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 814f125f..081346b3 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -10,7 +10,8 @@ import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; -import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactProperties, ArtifactType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; +import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactProperties, ArtifactType, CollectionType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; +import "hardhat/console.sol"; library LibGameUtils { function gs() internal pure returns (GameStorage storage) { @@ -267,7 +268,11 @@ library LibGameUtils { }); } - function _getUpgradeForArtifact(Artifact memory artifact) public pure returns (Upgrade memory) { + function _getUpgradeForArtifact(ArtifactProperties memory artifact) + public + pure + returns (Upgrade memory) + { if (artifact.artifactType == ArtifactType.PlanetaryShield) { uint256[6] memory defenseMultipliersPerRarity = [uint256(100), 150, 200, 300, 450, 650]; @@ -382,11 +387,14 @@ library LibGameUtils { // planets can have multiple artifacts on them. this function updates all the // internal contract book-keeping to reflect that the given artifact was // put on. note that this function does not transfer the artifact. - function _putArtifactOnPlanet(uint256 artifactId, uint256 locationId) public { - gs().artifactIdToPlanetId[artifactId] = locationId; + function _putArtifactOnPlanet(uint256 locationId, uint256 artifactId) public { + console.log("putting %s on %s", locationId, artifactId); gs().planetArtifacts[locationId].push(artifactId); + uint256 length = gs().planetArtifacts[locationId].length; + console.log("new planet artifact id", gs().planetArtifacts[locationId][length - 1]); } + // TODO: Why not burn ? // planets can have multiple artifacts on them. this function updates all the // internal contract book-keeping to reflect that the given artifact was // taken off the given planet. note that this function does not transfer the @@ -394,18 +402,23 @@ library LibGameUtils { // // if the given artifact is not on the given planet, reverts // if the given artifact is currently activated, reverts - function _takeArtifactOffPlanet(uint256 artifactId, uint256 locationId) public { + + /** + * Should remove artifactId from planet with locationId if artifactId exists AND is not active. + */ + + function _takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) public { uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; bool hadTheArtifact = false; for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact( + ArtifactProperties memory artifact = DFArtifactFacet(address(this)).getArtifact( gs().planetArtifacts[locationId][i] ); require( - !isActivated(artifact), + !isActivatedERC1155(locationId, artifactId), "you cannot take an activated artifact off a planet" ); @@ -419,7 +432,6 @@ library LibGameUtils { } require(hadTheArtifact, "this artifact was not present on this planet"); - gs().artifactIdToPlanetId[artifactId] = 0; gs().planetArtifacts[locationId].pop(); } @@ -431,6 +443,10 @@ library LibGameUtils { return artifact.lastDeactivated < artifact.lastActivated; } + function isActivatedERC1155(uint256 locationId, uint256 artifactId) public view returns (bool) { + return (gs().planetActiveArtifact[locationId] > 0); + } + function isArtifactOnPlanet(uint256 locationId, uint256 artifactId) public returns (bool) { for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { @@ -446,31 +462,31 @@ library LibGameUtils { function getPlanetArtifact(uint256 locationId, uint256 artifactId) public view - returns (Artifact memory) + returns (ArtifactProperties memory) { + console.log("searching for %s on %s", locationId, artifactId); + console.log( + "%s artifacts on planet %s", + gs().planetArtifacts[locationId].length, + locationId + ); for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { + console.log("found %s ", gs().planetArtifacts[locationId][i]); if (gs().planetArtifacts[locationId][i] == artifactId) { return DFArtifactFacet(address(this)).getArtifact(artifactId); } } - return _nullArtifact(); + return _nullArtifactProperties(); } // if the given planet has an activated artifact on it, then return the artifact // otherwise, return a 'null artifact' - function getActiveArtifact(uint256 locationId) public view returns (Artifact memory) { - for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact( - gs().planetArtifacts[locationId][i] - ); - - if (isActivated(artifact)) { - return artifact; - } - } + function getActiveArtifact(uint256 locationId) public view returns (ArtifactProperties memory) { + uint256 artifactId = gs().planetActiveArtifact[locationId]; + if (artifactId != 0) return DFArtifactFacet(address(this)).decodeArtifact(artifactId); - return _nullArtifact(); + return _nullArtifactProperties(); } // the space junk that a planet starts with @@ -501,6 +517,17 @@ library LibGameUtils { ); } + function _nullArtifactProperties() private pure returns (ArtifactProperties memory) { + return + ArtifactProperties( + 0, + CollectionType.Unknown, + ArtifactRarity.Unknown, + ArtifactType.Unknown, + Biome.Unknown + ); + } + function _buffPlanet(uint256 location, Upgrade memory upgrade) public { Planet storage planet = gs().planets[location]; diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index acf3c307..7d503526 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -13,6 +13,7 @@ import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStora // Type imports import {Artifact, ArtifactType, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; +import "hardhat/console.sol"; library LibPlanet { function gs() internal pure returns (GameStorage storage) { @@ -346,7 +347,7 @@ library LibPlanet { for (uint256 i = 0; i < 12; i++) { if (artifactIdsToAddToPlanet[i] != 0) { gs().artifactIdToVoyageId[artifactIdsToAddToPlanet[i]] = 0; - LibGameUtils._putArtifactOnPlanet(artifactIdsToAddToPlanet[i], location); + LibGameUtils._putArtifactOnPlanet(location, artifactIdsToAddToPlanet[i]); } } } diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index fb375c08..67157ca7 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -45,8 +45,10 @@ struct GameStorage { mapping(uint256 => PlanetEventMetadata[]) planetEvents; // maps event id to arrival data mapping(uint256 => ArrivalData) planetArrivals; - mapping(uint256 => uint256[]) planetArtifacts; // Artifact stuff + mapping(uint256 => uint256[]) planetArtifacts; + // TODO: Make this an array + mapping(uint256 => uint256) planetActiveArtifact; mapping(uint256 => Artifact) artifacts; // Capture Zones uint256 nextChangeBlock; diff --git a/eth/hardhat.config.ts b/eth/hardhat.config.ts index fc7dee6e..20a56f9d 100644 --- a/eth/hardhat.config.ts +++ b/eth/hardhat.config.ts @@ -219,6 +219,7 @@ const config: HardhatUserConfig = { // This plugin will combine all ABIs from any Smart Contract with `Facet` in the name or path and output it as `DarkForest.json` name: 'DarkForest', include: ['Facet', 'DFDiamond'], + exclude: ['Old'], // We explicitly set `strict` to `true` because we want to validate our facets don't accidentally provide overlapping functions strict: true, // We use our diamond utils to filter some functions we ignore from the combined ABI diff --git a/eth/package.json b/eth/package.json index e7b15a1a..45969814 100644 --- a/eth/package.json +++ b/eth/package.json @@ -64,6 +64,7 @@ "node": ">=16" }, "scripts": { + "hardhat": "hardhat", "test": "hardhat test && npm run subgraph:template:dev", "lint": "eslint .", "format": "prettier --write .", diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index e7486cc1..f9df2f50 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -309,15 +309,11 @@ export async function deployAndCut( return [diamond, diamondInit, initReceipt] as const; } -export async function deployGetterFacet( - {}, - { LibGameUtils }: Libraries, - hre: HardhatRuntimeEnvironment -) { +export async function deployGetterFacet({}, {}: Libraries, hre: HardhatRuntimeEnvironment) { const factory = await hre.ethers.getContractFactory('DFGetterFacet', { - libraries: { - LibGameUtils, - }, + // libraries: { + // LibGameUtils, + // }, }); const contract = await factory.deploy(); await contract.deployTransaction.wait(); diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index 5e7d7d3b..c23ae107 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -1,4 +1,5 @@ import { DFToken, DFToken__factory } from '@dfdao/contracts/typechain'; +import { ArtifactRarity, ArtifactType, Biome } from '@dfdao/types'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; @@ -45,16 +46,26 @@ describe('SolidStateERC1155', function () { expect(await token.uri(collectionId)).to.equal(tokenURI); }); - it('logs bits', async function () { + it.skip('logs bits for artifact', async function () { + // Must be valid options const _collectionType = '0x01'; - const _rarity = '0xff'; - const _artifactType = '0x01'; - const _biome = '0xab'; + const _rarity = ArtifactRarity.Legendary; + const _artifactType = ArtifactType.Colossus; + const _biome = Biome.DESERT; const res = await token.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); - const { collectionType, rarity, biome, artifactType } = await token.decodeArtifact(res); + const { collectionType, rarity, planetBiome, artifactType } = await token.decodeArtifact(res); expect(collectionType).to.equal(Number(_collectionType)); expect(rarity).to.equal(Number(_rarity)); - expect(biome).to.equal(Number(_biome)); + expect(planetBiome).to.equal(Number(_biome)); + expect(artifactType).to.equal(Number(_artifactType)); + }); + it.only('logs bits for spaceship', async function () { + // Must be valid options + const _collectionType = '0x02'; // TODO: add CollectionType to @dfdao/types + const _artifactType = ArtifactType.ShipGear; + const res = await token.encodeArtifact(_collectionType, 0, _artifactType, 0); + const { collectionType, artifactType } = await token.decodeArtifact(res); + expect(collectionType).to.equal(Number(_collectionType)); expect(artifactType).to.equal(Number(_artifactType)); }); }); diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts new file mode 100644 index 00000000..2b21cf9a --- /dev/null +++ b/eth/test/NewDFArtifacts.test.ts @@ -0,0 +1,903 @@ +import { ArtifactRarity, ArtifactType, Biome } from '@dfdao/types'; +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { expect } from 'chai'; +import { BigNumberish } from 'ethers'; +import hre from 'hardhat'; +import { TestLocation } from './utils/TestLocation'; +import { + conquerUnownedPlanet, + createArtifactOnPlanet, + getArtifactsOwnedBy, + getCurrentTime, + getStatSum, + hexToBigNumber, + increaseBlockchainTime, + makeFindArtifactArgs, + makeInitArgs, + makeMoveArgs, + prettyPrintToken, + user1MintArtifactPlanet, + ZERO_ADDRESS, +} from './utils/TestUtils'; +import { defaultWorldFixture, World } from './utils/TestWorld'; +import { + ARTIFACT_PLANET_1, + LVL0_PLANET, + LVL0_PLANET_DEAD_SPACE, + LVL3_SPACETIME_1, + LVL3_SPACETIME_2, + LVL3_SPACETIME_3, + LVL3_UNOWNED_NEBULA, + LVL4_UNOWNED_DEEP_SPACE, + LVL6_SPACETIME, + SPACE_PERLIN, + SPAWN_PLANET_1, + SPAWN_PLANET_2, +} from './utils/WorldConstants'; + +describe('DarkForestArtifacts', function () { + let world: World; + + async function worldFixture() { + const world = await loadFixture(defaultWorldFixture); + + // Initialize player + await world.user1Core.initializePlayer(...makeInitArgs(SPAWN_PLANET_1)); + await world.user1Core.giveSpaceShips(SPAWN_PLANET_1.id); + await world.user2Core.initializePlayer(...makeInitArgs(SPAWN_PLANET_2)); + + // Conquer initial planets + //// Player 1 + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, ARTIFACT_PLANET_1); + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + //// Player 2 + await conquerUnownedPlanet(world, world.user2Core, SPAWN_PLANET_2, LVL3_SPACETIME_2); + await increaseBlockchainTime(); + + // Move the Gear ship into position + const gearShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( + (artifact) => artifact.artifactType === ArtifactType.ShipGear + ); + console.log(`gearId`, gearShip?.id._hex); + const gearId = gearShip?.id; + await world.user1Core.move( + ...makeMoveArgs(SPAWN_PLANET_1, ARTIFACT_PLANET_1, 100, 0, 0, gearId) + ); + await increaseBlockchainTime(); + const tx = await world.user1Core.refreshPlanet(ARTIFACT_PLANET_1.id); + await tx.wait(); + + const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + prettyPrintToken(artifactsOnPlanet[0]); + // Expect gear to be on planet. + expect(artifactsOnPlanet.length).to.be.equal(1); + + // Conquer another planet for artifact storage + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL0_PLANET_DEAD_SPACE); + + return world; + } + + beforeEach('load fixture', async function () { + this.timeout(0); + world = await loadFixture(worldFixture); + }); + + async function getArtifactsOnPlanet(world: World, locationId: BigNumberish) { + return await world.contract.getArtifactsOnPlanet(locationId); + } + + it.only('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { + const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + + const newId = await user1MintArtifactPlanet(world.user1Core); + console.log('new id', newId._hex); + const res = await world.user1Core.decodeArtifact(newId); + prettyPrintToken(res); + const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + + // artifact and gear should be on planet. Gear is 0 and Artifact is 1. + const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + expect(artifactsOnPlanet.length).to.be.equal(2); + + // artifact should be owned by contract + artifactsOnPlanet.map(async (a) => { + expect(await world.contract.balanceOf(world.contract.address, a.id)).to.equal(1); + }); + + // expect(artifactsOnPlanet[0].discoverer).to.eq(world.user1.address); + + // let's update the planet to be one of the basic artifacts, so that + // we know it's definitely going to buff the planet in some way. also, + // this prevents the artifact from being one that requires valid parameter + // in order to activate + // const updatedArtifact = Object.assign({}, artifactsOnPlanet[0]); + // updatedArtifact.artifactType = 0; + // await world.contract.updateArtifact(updatedArtifact); + + // // planet should be buffed after discovered artifact + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[1].id, 0); + const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(ARTIFACT_PLANET_1.id); + prettyPrintToken(activeArtifact); + const statSumAfterActivation = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + + // // planet buff should be removed after artifact deactivated + await world.user1Core.deactivateArtifact(ARTIFACT_PLANET_1.id); + const statSumAfterDeactivate = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + + expect(statSumAfterActivation).to.not.be.within(statSumInitial - 5, statSumInitial + 5); + expect(statSumAfterActivation).to.not.be.within( + statSumAfterDeactivate - 5, + statSumAfterDeactivate + 5 + ); + expect(statSumAfterDeactivate).to.be.within(statSumInitial - 5, statSumInitial + 5); + expect(statSumAfterFound).to.be.within(statSumInitial - 5, statSumInitial + 5); + }); + + it('cannot prospect multiple times, cannot find artifact more than 256 blocks after prospecting', async function () { + await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); + + await expect(world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id)).to.be.revertedWith( + 'this planet has already been prospected' + ); + + for (let i = 0; i < 256; i++) { + await increaseBlockchainTime(); + } + + await expect( + world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) + ).to.be.revertedWith('planet prospect expired'); + }); + + it('should return a correct token uri for a minted artifact', async function () { + await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); + await increaseBlockchainTime(); + await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); + + const artifactsOnPlanet = await world.contract.planetArtifacts(ARTIFACT_PLANET_1.id); + const tokenUri = await world.contract.tokenURI(artifactsOnPlanet[0]); + + const networkId = hre.network.config.chainId; + const contractAddress = world.contract.address; + + expect(tokenUri).to.eq( + `https://nft-test.zkga.me/token-uri/artifact/${networkId}-${contractAddress}/` + + artifactsOnPlanet[0] + ); + }); + + it("should not be able to deposit an artifact you don't own", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + // user1 moves artifact and withdraws + await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) + ); + + world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); + + // user2 should not be able to deposit artifact + await expect( + world.user2Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) + ).to.be.revertedWith('you can only deposit artifacts you own'); + }); + + it('should be able to move an artifact from a planet you own', async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + let artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + let artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + + // ruins should have artifact, spawn planet should not. + expect(artifactsOnRuins.length).to.eq(1); + expect(artifactsOnSpawn.length).to.eq(0); + + // after finding artifact, planet's popCap might get buffed + // so let it fill up again + await increaseBlockchainTime(); + + // move artifact; check that artifact is placed on voyage + const moveTx = await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) + ); + const moveReceipt = await moveTx.wait(); + const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued + const artifactPreArrival = await world.contract.getArtifactById(newArtifactId); + expect(artifactPreArrival.voyageId).to.eq(voyageId); + expect(artifactPreArrival.locationId).to.eq(0); + + // when moving, both the ruins and the spawn planet should not have artifacts + artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + expect(artifactsOnRuins.length).to.eq(0); + expect(artifactsOnSpawn.length).to.eq(0); + + // fast forward to arrival + await increaseBlockchainTime(); + await world.user1Core.refreshPlanet(SPAWN_PLANET_1.id); + + // check artifact is on the new planet + const artifactPostArrival = await world.contract.getArtifactById(newArtifactId); + expect(artifactPostArrival.voyageId).to.eq(0); + expect(artifactPostArrival.locationId).to.eq(SPAWN_PLANET_1.id); + artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + expect(artifactsOnRuins.length).to.eq(0); + expect(artifactsOnSpawn.length).to.eq(1); + }); + + it('should not be able to move more than some max amount of artifacts to a planet', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + + const maxArtifactsOnPlanet = 4; + for (let i = 0; i <= maxArtifactsOnPlanet; i++) { + // place an artifact on the trading post + const newTokenId = hexToBigNumber(i + 1 + ''); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, + rarity: 1, + biome: 1, + artifactType: 5, + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + + // wait for the planet to fill up and download its stats + await increaseBlockchainTime(); + await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); + const tradingPost2Planet = await world.user1Core.planets(LVL3_SPACETIME_1.id); + + if (i > maxArtifactsOnPlanet) { + await expect( + world.user1Core.move( + ...makeMoveArgs( + LVL3_SPACETIME_1, + LVL0_PLANET_DEAD_SPACE, + 0, + tradingPost2Planet.population.toNumber() - 1, + 0, + newTokenId + ) + ) + ).to.be.revertedWith( + 'the planet you are moving an artifact to can have at most 5 artifacts on it' + ); + } else { + // move the artifact from the trading post + await world.user1Core.move( + ...makeMoveArgs( + LVL3_SPACETIME_1, + LVL0_PLANET_DEAD_SPACE, + 0, + tradingPost2Planet.population.toNumber() - 1, + 0, + newTokenId + ) + ); + await increaseBlockchainTime(); + await world.user1Core.refreshPlanet(LVL0_PLANET_DEAD_SPACE.id); + const artifactsOnPlanet = await getArtifactsOnPlanet(world, LVL0_PLANET_DEAD_SPACE.id); + expect(artifactsOnPlanet.length).to.eq(i + 1); + } + } + }); + + it("should be able to conquer another player's planet and move their artifact", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + // after finding artifact, planet's popCap might get buffed + // so let it fill up again + await increaseBlockchainTime(); + + const artifactPlanetPopCap = ( + await world.contract.planets(ARTIFACT_PLANET_1.id) + ).populationCap.toNumber(); + + await world.user1Core.move( + ...makeMoveArgs( + ARTIFACT_PLANET_1, + SPAWN_PLANET_1, + 10, + Math.floor(artifactPlanetPopCap * 0.999), // if only 0.99 it's still untakeable, bc high def + 0 + ) + ); + + // steal planet + await world.user2Core.move(...makeMoveArgs(SPAWN_PLANET_2, ARTIFACT_PLANET_1, 0, 50000, 0)); + await increaseBlockchainTime(); + + // move artifact + await world.user2Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_2, 0, 50000, 0, newArtifactId) + ); + await increaseBlockchainTime(); + + // verify that artifact was moved + await world.user2Core.withdrawArtifact(LVL3_SPACETIME_2.id, newArtifactId); + const artifacts = await getArtifactsOwnedBy(world.contract, world.user2.address); + + expect(artifacts.length).to.be.equal(1); + }); + + it('not be able to prospect for an artifact on planets that are not ruins', async function () { + await expect(world.user1Core.prospectPlanet(SPAWN_PLANET_1.id)).to.be.revertedWith( + "you can't find an artifact on this planet" + ); + }); + + it('should mint randomly', async function () { + // This can take upwards of 90000ms in CI + this.timeout(0); + + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + + /* eslint-disable @typescript-eslint/no-explicit-any */ + let artifacts: any; + let prevLocation = SPAWN_PLANET_1; + + for (let i = 0; i < 20; i++) { + // byte #8 is 18_16 = 24_10 so it's a ruins planet + const randomHex = + `00007c2512896efb182d462faee0000fb33d58930eb9e6b4fbae6d048e9c44` + + (i >= 10 ? i.toString()[0] : 0) + + '' + + (i % 10); + + const planetWithArtifactLoc = new TestLocation({ + hex: randomHex, + perlin: SPACE_PERLIN, + distFromOrigin: 1998, + }); + + await world.contract.adminInitializePlanet( + planetWithArtifactLoc.id, + planetWithArtifactLoc.perlin + ); + + await world.contract.adminGiveSpaceShip( + planetWithArtifactLoc.id, + world.user1.address, + ArtifactType.ShipGear + ); + + await increaseBlockchainTime(); + + await world.user1Core.move(...makeMoveArgs(prevLocation, planetWithArtifactLoc, 0, 80000, 0)); // move 80000 from asteroids but 160000 from ruins since ruins are higher level + await increaseBlockchainTime(); + + await world.user1Core.prospectPlanet(planetWithArtifactLoc.id); + await increaseBlockchainTime(); + + await world.user1Core.findArtifact(...makeFindArtifactArgs(planetWithArtifactLoc)); + await increaseBlockchainTime(); + + const artifactsOnPlanet = await getArtifactsOnPlanet(world, planetWithArtifactLoc.id); + const artifactId = artifactsOnPlanet[0].id; + + await world.user1Core.move( + ...makeMoveArgs(planetWithArtifactLoc, LVL3_SPACETIME_1, 0, 40000, 0, artifactId) + ); + await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, artifactId); + artifacts = await getArtifactsOwnedBy(world.contract, world.user1.address); + + expect(artifacts[artifacts.length - 1].planetBiome).to.eq(4); // tundra + expect(artifacts[artifacts.length - 1].discoverer).to.eq(world.user1.address); + expect(artifacts[artifacts.length - 1].rarity).to.be.at.least(1); + + prevLocation = planetWithArtifactLoc; + } + + const artifactTypeSet = new Set(); + + for (let i = 0; i < artifacts.length; i++) { + artifactTypeSet.add(artifacts[i].artifactType); + } + + expect(artifactTypeSet.size).to.be.greaterThan(1); + }); + + it('should not mint an artifact on the same planet twice', async function () { + await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); + await increaseBlockchainTime(); + await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); + await increaseBlockchainTime(); + await expect( + world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) + ).to.be.revertedWith('artifact already minted from this planet'); + }); + + it('should not be able to move an activated artifact', async function () { + const artifactId = await createArtifactOnPlanet( + world.contract, + world.user1.address, + ARTIFACT_PLANET_1, + ArtifactType.Monolith + ); + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); + + await expect( + world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, artifactId) + ) + ).to.be.revertedWith('you cannot take an activated artifact off a planet'); + }); + + it("should not be able to move an artifact from a planet it's not on", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + // after finding artifact, planet's popCap might get buffed + // so let it fill up again + await increaseBlockchainTime(); + + // move artifact + world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) + ); + + // try moving artifact again; should fail + await expect( + world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) + ) + ).to.be.revertedWith('this artifact was not present on this planet'); + + // try moving nonexistent artifact + await expect( + world.user1Core.move(...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, 12345)) + ).to.be.revertedWith('this artifact was not present on this planet'); + }); + + describe('trading post', function () { + it('should be able to withdraw from / deposit onto trading posts you own', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_3); + await increaseBlockchainTime(); + + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + // move artifact to LVL3_SPACETIME_1 + await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) + ); + await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); + + // artifact should be on LVL3_SPACETIME_1 + let artifact = await world.contract.getArtifactById(newArtifactId); + let artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); + let artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); + await expect(artifact.locationId).to.eq(LVL3_SPACETIME_1.id); + await expect(artifactsOnTP1.length).to.eq(1); + await expect(artifactsOnTP2.length).to.eq(0); + + // withdraw from LVL3_SPACETIME_1 + await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); + + // artifact should be on voyage + artifact = await world.contract.getArtifactById(newArtifactId); + artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); + artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); + await expect(artifact.locationId).to.eq(0); + await expect(artifactsOnTP1.length).to.eq(0); + await expect(artifactsOnTP2.length).to.eq(0); + + // deposit onto LVL3_SPACETIME_3 + await world.user1Core.depositArtifact(LVL3_SPACETIME_3.id, newArtifactId); + + // artifact should be on LVL3_SPACETIME_3 + artifact = await world.contract.getArtifactById(newArtifactId); + artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); + artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); + await expect(artifact.locationId).to.eq(LVL3_SPACETIME_3.id); + await expect(artifactsOnTP1.length).to.eq(0); + await expect(artifactsOnTP2.length).to.eq(1); + }); + + it("should not be able to withdraw from / deposit onto trading post you don't own", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + // move artifact + await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) + ); + + // user2 should not be able to withdraw from LVL3_SPACETIME_1 + await expect( + world.user2Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId) + ).to.be.revertedWith('you can only withdraw from a planet you own'); + + // user1 should not be able to deposit onto LVL3_SPACETIME_2 + world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); + await expect( + world.user1Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) + ).to.be.revertedWith('you can only deposit on a planet you own'); + }); + + it('should not be able to withdraw an artifact from a trading post that is not on the trading post', async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + // should not be able to withdraw newArtifactId from LVL3_SPACETIME_1 + await expect( + world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId) + ).to.be.revertedWith('this artifact is not on this planet'); + }); + + it('should not be able to withdraw/deposit onto a planet that is not a trading post', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL0_PLANET); + await increaseBlockchainTime(); + + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + // should not be able to withdraw from ruins (which are not trading posts) + await expect( + world.user2Core.withdrawArtifact(ARTIFACT_PLANET_1.id, newArtifactId) + ).to.be.revertedWith('can only withdraw from trading posts'); + + // move artifact and withdraw + await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) + ); + world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); + + // should not be able to deposit onto LVL0_PLANET (which is regular planet and not trading post) + await expect( + world.user1Core.depositArtifact(LVL0_PLANET.id, newArtifactId) + ).to.be.revertedWith('can only deposit on trading posts'); + }); + + it('should not be able to withdraw/deposit a high level artifact onto low level trading post', async function () { + await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL6_SPACETIME); + await increaseBlockchainTime(); // allow planets to fill up energy again + + const newTokenId = hexToBigNumber('1'); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, // planet id + rarity: 4, // rarity + biome: 1, // biome + artifactType: 1, + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + // deposit fails on low level trading post, succeeds on high level trading post + await expect( + world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId) + ).to.be.revertedWith('spacetime rip not high enough level to deposit this artifact'); + world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); + + // withdraw fails on low level trading post + await world.user1Core.move( + ...makeMoveArgs(LVL6_SPACETIME, LVL3_SPACETIME_1, 0, 250000000, 0, newTokenId) + ); + await expect( + world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newTokenId) + ).to.be.revertedWith('spacetime rip not high enough level to withdraw this artifact'); + + // withdraw succeeds on high level post + await world.user1Core.move( + ...makeMoveArgs(LVL3_SPACETIME_1, LVL6_SPACETIME, 0, 500000, 0, newTokenId) + ); + await world.user1Core.withdrawArtifact(LVL6_SPACETIME.id, newTokenId); + }); + }); + + describe('wormhole', function () { + it('should increase movement speed, in both directions', async function () { + // This can take an upwards of 32000ms + this.timeout(0); + + const from = SPAWN_PLANET_1; + const to = LVL0_PLANET; + await conquerUnownedPlanet(world, world.user1Core, from, LVL3_UNOWNED_NEBULA); + await conquerUnownedPlanet(world, world.user1Core, LVL3_UNOWNED_NEBULA, LVL6_SPACETIME); + await conquerUnownedPlanet(world, world.user1Core, from, LVL0_PLANET); + + const dist = 50; + const shipsSent = 10000; + const silverSent = 0; + + const artifactRarities = [1, 2, 3, 4, 5]; // 0 is unknown, so we start at 1 + const wormholeSpeedups = [2, 4, 8, 16, 32]; + + for (let i = 0; i < artifactRarities.length; i++) { + const artifactId = await createArtifactOnPlanet( + world.contract, + world.user1.address, + from, + ArtifactType.Wormhole, + { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } + ); + await world.user1Core.activateArtifact(from.id, artifactId, to.id); + + // move from planet with artifact to its wormhole destination + await increaseBlockchainTime(); + await world.user1Core.move(...makeMoveArgs(from, to, dist, shipsSent, silverSent)); + const fromPlanet = await world.contract.planets(from.id); + const planetArrivals = await world.contract.getPlanetArrivals(to.id); + const arrival = planetArrivals[0]; + const expectedTime = Math.floor( + Math.floor((dist * 100) / wormholeSpeedups[i]) / fromPlanet.speed.toNumber() + ); + + expect(arrival.arrivalTime.sub(arrival.departureTime)).to.be.equal(expectedTime); + + // move from the wormhole destination planet back to the planet whose wormhole is pointing at + // it + await increaseBlockchainTime(); + await world.user1Core.move(...makeMoveArgs(to, from, dist, shipsSent, silverSent)); + const fromPlanetInverted = await world.contract.planets(to.id); + const planetArrivalsInverted = await world.contract.getPlanetArrivals(from.id); + const arrivalInverted = planetArrivalsInverted[0]; + const expectedTimeInverted = Math.floor( + Math.floor((dist * 100) / wormholeSpeedups[i]) / fromPlanetInverted.speed.toNumber() + ); + + expect(arrivalInverted.arrivalTime.sub(arrivalInverted.departureTime)).to.be.equal( + expectedTimeInverted + ); + + await world.user1Core.deactivateArtifact(from.id); + } + }); + + it("shouldn't transfer energy to planets that aren't owned by the sender", async function () { + const from = SPAWN_PLANET_1; + const to = LVL0_PLANET; + + // user 2 takes over a larger planet + await conquerUnownedPlanet(world, world.user2Core, SPAWN_PLANET_2, LVL3_UNOWNED_NEBULA); + + // user 1 takes over the 2nd planet + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, to); + await world.user1Core.refreshPlanet(to.id); + const toPlanet = await world.contract.planets(to.id); + expect(toPlanet.owner).to.eq(world.user1.address); + + // create a wormhole + const newTokenId = hexToBigNumber('5'); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, // planet id + rarity: 1, + biome: 1, // biome + artifactType: 5, // wormhole + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + const userArtifacts = await world.contract.getPlayerArtifactIds(world.user1.address); + expect(userArtifacts[0]).to.eq(newTokenId); + + // activate the wormhole to the 2nd planet + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + await world.user1Core.move( + ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, newTokenId) + ); + await world.user1Core.activateArtifact(from.id, newTokenId, to.id); + + const dist = 50; + const shipsSent = 10000; + const silverSent = 0; + + await increaseBlockchainTime(); + + // user 2 takes over the wormhole's destination + const largePlanet = await world.contract.planets(LVL3_UNOWNED_NEBULA.id); + await world.user2Core.move( + ...makeMoveArgs(LVL3_UNOWNED_NEBULA, to, 10, largePlanet.populationCap.div(2), 0) + ); + await increaseBlockchainTime(); + await world.user1Core.refreshPlanet(to.id); + const toPlanetOwnedBySecond = await world.contract.planets(to.id); + expect(toPlanetOwnedBySecond.owner).to.eq(world.user2.address); + + // ok, now for the test: move from the planet with the wormhole to its wormhole target + await increaseBlockchainTime(); + await world.user1Core.move(...makeMoveArgs(from, to, dist, shipsSent, silverSent)); + + // check that the move is sped up + const fromPlanet = await world.contract.planets(from.id); + const planetArrivals = await world.contract.getPlanetArrivals(to.id); + const arrival = planetArrivals[0]; + const expectedTime = Math.floor((Math.floor(dist / 2) * 100) / fromPlanet.speed.toNumber()); + expect(arrival.arrivalTime.sub(arrival.departureTime)).to.be.equal(expectedTime); + + // fast forward to the time that the arrival is scheduled to arrive + const currentTime = await getCurrentTime(); + await increaseBlockchainTime(arrival.arrivalTime.toNumber() - currentTime - 5); + await world.user1Core.refreshPlanet(to.id); + const planetPreArrival = await world.contract.planets(to.id); + const arrivalsPreArrival = await world.contract.getPlanetArrivals(to.id); + + await increaseBlockchainTime(6); + await world.user1Core.refreshPlanet(to.id); + const planetPostArrival = await world.contract.planets(to.id); + const arrivalsPostArrival = await world.contract.getPlanetArrivals(to.id); + + // expect that the arrival transfered precisely zero energy. + expect(planetPreArrival.population).to.eq(planetPostArrival.population); + expect(arrivalsPreArrival.length).to.eq(1); + expect(arrivalsPostArrival.length).to.eq(0); + }); + }); + + describe('bloom filter', function () { + it('is burnt after usage, and should fill energy and silver', async function () { + const from = SPAWN_PLANET_1; + const dist = 50; + const shipsSent = 10000; + const silverSent = 0; + + await world.user1Core.move(...makeMoveArgs(from, LVL0_PLANET, dist, shipsSent, silverSent)); + + const planetBeforeBloomFilter = await world.user1Core.planets(from.id); + expect(planetBeforeBloomFilter.population.toNumber()).to.be.lessThan( + planetBeforeBloomFilter.populationCap.toNumber() + ); + expect(planetBeforeBloomFilter.silver).to.eq(0); + + const newTokenId = hexToBigNumber('1'); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, // planet id + rarity: 1, // rarity + biome: 1, // biome + artifactType: 8, // bloom filter + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + await increaseBlockchainTime(); // so that trading post can fill up to max energy + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + await world.user1Core.move( + ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, newTokenId) + ); + await world.user1Core.activateArtifact(from.id, newTokenId, 0); + + const planetAfterBloomFilter = await world.user1Core.planets(from.id); + expect(planetAfterBloomFilter.population).to.eq(planetAfterBloomFilter.populationCap); + expect(planetAfterBloomFilter.silver).to.eq(planetAfterBloomFilter.silverCap); + + const bloomFilterPostActivation = await world.contract.getArtifactById(newTokenId); + + // bloom filter is immediately deactivated after activation + expect(bloomFilterPostActivation.artifact.lastActivated).to.eq( + bloomFilterPostActivation.artifact.lastDeactivated + ); + + // bloom filter is no longer on a planet (is instead owned by contract), and so is effectively burned + expect(bloomFilterPostActivation.locationId.toString()).to.eq('0'); + }); + + it("can't be used on a planet of too high level", async function () { + this.timeout(1000 * 60); + await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE); + const from = SPAWN_PLANET_1; + + const dist = 50; + const shipsSent = 10000; + const silverSent = 0; + + await world.user1Core.move(...makeMoveArgs(from, LVL0_PLANET, dist, shipsSent, silverSent)); + + const planetBeforeBloomFilter = await world.user1Core.planets(from.id); + expect(planetBeforeBloomFilter.population.toNumber()).to.be.lessThan( + planetBeforeBloomFilter.populationCap.toNumber() + ); + expect(planetBeforeBloomFilter.silver).to.eq(0); + + const newTokenId = hexToBigNumber('1'); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, // planet id + rarity: 1, // rarity + biome: 1, // biome + artifactType: 9, // bloom filter + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + await increaseBlockchainTime(); // so that trading post can fill up to max energy + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + await world.user1Core.move( + ...makeMoveArgs(LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE, 0, 500000, 0, newTokenId) + ); + await expect( + world.user1Core.activateArtifact(LVL4_UNOWNED_DEEP_SPACE.id, newTokenId, 0) + ).to.be.revertedWith('artifact is not powerful enough to apply effect to this planet level'); + }); + }); + + describe('black domain', function () { + it('is burnt after usage, and prevents moves from being made to it and from it', async function () { + const to = LVL0_PLANET; + const dist = 50; + const shipsSent = 10000; + const silverSent = 0; + + await world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, to, dist, shipsSent, silverSent)); + await increaseBlockchainTime(); + + await world.user1Core.refreshPlanet(to.id); + const conqueredSecondPlanet = await world.user1Core.planets(to.id); + expect(conqueredSecondPlanet.owner).to.eq(world.user1.address); + + const newTokenId = hexToBigNumber('1'); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, // planet id + rarity: 1, // rarity + biome: 1, // biome + artifactType: 9, // black domain + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + await world.user1Core.move(...makeMoveArgs(LVL3_SPACETIME_1, to, 0, 500000, 0, newTokenId)); + await world.user1Core.activateArtifact(to.id, newTokenId, 0); + + // black domain is no longer on a planet (is instead owned by contract), and so is effectively burned + const blackDomainPostActivation = await world.contract.getArtifactById(newTokenId); + expect(blackDomainPostActivation.locationId.toString()).to.eq('0'); + + // check the planet is destroyed + const newPlanet = await world.user1Core.planets(to.id); + expect(newPlanet.destroyed).to.eq(true); + + await increaseBlockchainTime(); + + // moves to destroyed planets don't work + await expect( + world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, to, dist, shipsSent, silverSent)) + ).to.be.revertedWith('planet is destroyed'); + + // moves from destroyed planets also don't work + await expect( + world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, to, dist, shipsSent, silverSent)) + ).to.be.revertedWith('planet is destroyed'); + }); + + it("can't be used on a planet of too high level", async function () { + this.timeout(1000 * 60); + await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE); + const from = SPAWN_PLANET_1; + const dist = 50; + const shipsSent = 10000; + const silverSent = 0; + + await world.user1Core.move(...makeMoveArgs(from, LVL0_PLANET, dist, shipsSent, silverSent)); + + const planetBeforeBlackDomain = await world.user1Core.planets(from.id); + expect(planetBeforeBlackDomain.population.toNumber()).to.be.lessThan( + planetBeforeBlackDomain.populationCap.toNumber() + ); + expect(planetBeforeBlackDomain.silver).to.eq(0); + + const newTokenId = hexToBigNumber('1'); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, // planet id + rarity: 1, // rarity + biome: 1, // biome + artifactType: 8, // bloom filter + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + await increaseBlockchainTime(); // so that trading post can fill up to max energy + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + await world.user1Core.move( + ...makeMoveArgs(LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE, 0, 500000, 0, newTokenId) + ); + await expect( + world.user1Core.activateArtifact(LVL4_UNOWNED_DEEP_SPACE.id, newTokenId, 0) + ).to.be.revertedWith('artifact is not powerful enough to apply effect to this planet level'); + }); + }); + + // TODO: tests for photoid cannon and planetary shield? +}); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index ac996184..906a654c 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -1,4 +1,5 @@ import type { DarkForest } from '@dfdao/contracts/typechain'; +import { ArtifactPropertiesStructOutput } from '@dfdao/contracts/typechain/contracts/DFToken'; import { modPBigInt } from '@dfdao/hashing'; import { buildContractCallArgs, @@ -7,7 +8,15 @@ import { WhitelistSnarkInput, } from '@dfdao/snarks'; import { whitelistSnarkWasmPath, whitelistSnarkZkeyPath } from '@dfdao/snarks/node'; -import { ArtifactRarity, ArtifactType, Biome } from '@dfdao/types'; +import { + ArtifactRarity, + ArtifactRarityNames, + ArtifactType, + ArtifactTypeNames, + Biome, + BiomeNames, + CollectionTypeNames, +} from '@dfdao/types'; import { bigIntFromKey } from '@dfdao/whitelist'; import { mine, time } from '@nomicfoundation/hardhat-network-helpers'; import bigInt from 'big-integer'; @@ -34,6 +43,14 @@ export function hexToBigNumber(hex: string): BigNumber { return BigNumber.from(`0x${hex}`); } +export function prettyPrintToken(token: ArtifactPropertiesStructOutput) { + console.log( + `~Token~\nCollection: ${CollectionTypeNames[token.collectionType]}\nRarity: ${ + ArtifactRarityNames[token.rarity] + }\nType: ${ArtifactTypeNames[token.artifactType]}\nBiome: ${BiomeNames[token.planetBiome]}` + ); +} + export function makeRevealArgs( planetLoc: TestLocation, x: number, @@ -292,7 +309,7 @@ export async function user1MintArtifactPlanet(user1Core: DarkForest) { const findArtifactReceipt = await findArtifactTx.wait(); // 0th event is erc721 transfer (i think); 1st event is UpdateArtifact, 2nd argument of this event is artifactId const artifactId = findArtifactReceipt.events?.[1].args?.[1]; - return artifactId; + return artifactId as BigNumber; } export async function getArtifactsOwnedBy(contract: DarkForest, addr: string) { diff --git a/eth/test/utils/WorldConstants.ts b/eth/test/utils/WorldConstants.ts index 9ae8db2d..f729058c 100644 --- a/eth/test/utils/WorldConstants.ts +++ b/eth/test/utils/WorldConstants.ts @@ -96,10 +96,10 @@ const defaultInitializerValues = { CAPTURE_ZONES_PER_5000_WORLD_RADIUS: 1, SPACESHIPS: { GEAR: true, - MOTHERSHIP: true, - CRESCENT: true, - TITAN: true, - WHALE: true, + MOTHERSHIP: false, + CRESCENT: false, + TITAN: false, + WHALE: false, }, ROUND_END_REWARDS_BY_RANK: [ 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 289d1025..dc2b5e47 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -42,6 +42,7 @@ export * from './plugin'; export * from './renderer'; export * from './reveal'; export * from './setting'; +export * from './token'; export * from './transaction'; export * from './transactions'; export * from './upgrade'; diff --git a/packages/types/src/token.ts b/packages/types/src/token.ts new file mode 100644 index 00000000..258e48da --- /dev/null +++ b/packages/types/src/token.ts @@ -0,0 +1,18 @@ +import { Abstract } from './utility'; + +export type CollectionType = Abstract; + +export const CollectionType = { + Unknown: 0 as CollectionType, + Artifact: 1 as CollectionType, + Spaceship: 2 as CollectionType, +} as const; + +/** + * Mapping from CollectionType to pretty-printed names. + */ +export const CollectionTypeNames = { + [CollectionType.Unknown]: 'Unknown', + [CollectionType.Artifact]: 'Artifact', + [CollectionType.Spaceship]: 'Spaceship', +} as const; From e71fb66ccc9ecb2a07ede996b1722b945b87e858 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Tue, 27 Sep 2022 18:53:13 +0100 Subject: [PATCH 09/55] test: some test starting to work: --- eth/contracts/DFInitialize.sol | 8 +++--- eth/test/NewDFArtifacts.test.ts | 44 ++++++++++++++++++--------------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/eth/contracts/DFInitialize.sol b/eth/contracts/DFInitialize.sol index 3ce6a411..6392275c 100644 --- a/eth/contracts/DFInitialize.sol +++ b/eth/contracts/DFInitialize.sol @@ -21,7 +21,7 @@ pragma solidity ^0.8.0; // Interface imports // Inherited storage -import {ERC721MetadataStorage} from "@solidstate/contracts/token/ERC721/metadata/ERC721MetadataStorage.sol"; +import {ERC1155MetadataStorage} from "@solidstate/contracts/token/ERC1155/metadata/ERC1155MetadataStorage.sol"; // Library imports import {WithStorage, SpaceshipConstants} from "./libraries/LibStorage.sol"; @@ -99,7 +99,7 @@ struct InitArgs { } contract DFInitialize is WithStorage { - using ERC721MetadataStorage for ERC721MetadataStorage.Layout; + using ERC1155MetadataStorage for ERC1155MetadataStorage.Layout; // You can add parameters to this function in order to pass in // data to set initialize state variables @@ -110,9 +110,7 @@ contract DFInitialize is WithStorage { ) external { // Setup the ERC721 metadata // TODO(#1925): Add name and symbol for the artifact tokens - ERC721MetadataStorage.layout().name = ""; - ERC721MetadataStorage.layout().symbol = ""; - ERC721MetadataStorage.layout().baseURI = artifactBaseURI; + ERC1155MetadataStorage.layout().baseURI = artifactBaseURI; gs().diamondAddress = address(this); diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index 2b21cf9a..e9b360d4 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -67,11 +67,6 @@ describe('DarkForestArtifacts', function () { const tx = await world.user1Core.refreshPlanet(ARTIFACT_PLANET_1.id); await tx.wait(); - const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - prettyPrintToken(artifactsOnPlanet[0]); - // Expect gear to be on planet. - expect(artifactsOnPlanet.length).to.be.equal(1); - // Conquer another planet for artifact storage await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL0_PLANET_DEAD_SPACE); @@ -83,11 +78,15 @@ describe('DarkForestArtifacts', function () { world = await loadFixture(worldFixture); }); + // Gets Artifacts but not Spaceships async function getArtifactsOnPlanet(world: World, locationId: BigNumberish) { - return await world.contract.getArtifactsOnPlanet(locationId); + return (await world.contract.getArtifactsOnPlanet(locationId)).filter( + (artifact) => artifact.artifactType < ArtifactType.ShipMothership + ); } - it.only('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { + // This test will fail if the artifact is special. + it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); const newId = await user1MintArtifactPlanet(world.user1Core); @@ -98,7 +97,7 @@ describe('DarkForestArtifacts', function () { // artifact and gear should be on planet. Gear is 0 and Artifact is 1. const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - expect(artifactsOnPlanet.length).to.be.equal(2); + expect(artifactsOnPlanet.length).to.be.equal(1); // artifact should be owned by contract artifactsOnPlanet.map(async (a) => { @@ -116,7 +115,7 @@ describe('DarkForestArtifacts', function () { // await world.contract.updateArtifact(updatedArtifact); // // planet should be buffed after discovered artifact - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[1].id, 0); + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[0].id, 0); const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(ARTIFACT_PLANET_1.id); prettyPrintToken(activeArtifact); const statSumAfterActivation = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); @@ -156,7 +155,7 @@ describe('DarkForestArtifacts', function () { await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); const artifactsOnPlanet = await world.contract.planetArtifacts(ARTIFACT_PLANET_1.id); - const tokenUri = await world.contract.tokenURI(artifactsOnPlanet[0]); + const tokenUri = await world.contract.uri(artifactsOnPlanet[0]); const networkId = hre.network.config.chainId; const contractAddress = world.contract.address; @@ -189,8 +188,9 @@ describe('DarkForestArtifacts', function () { let artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); let artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - // ruins should have artifact, spawn planet should not. + // ruins should have 1 artifact (gear is filtered), spawn planet should not. expect(artifactsOnRuins.length).to.eq(1); + // Might fail w spaceships expect(artifactsOnSpawn.length).to.eq(0); // after finding artifact, planet's popCap might get buffed @@ -203,9 +203,12 @@ describe('DarkForestArtifacts', function () { ); const moveReceipt = await moveTx.wait(); const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued - const artifactPreArrival = await world.contract.getArtifactById(newArtifactId); - expect(artifactPreArrival.voyageId).to.eq(voyageId); - expect(artifactPreArrival.locationId).to.eq(0); + console.log(`voyageId`, voyageId); + // confirming that artifact is on a voyage. Why is that necessary? + // TODO: Figure out how to test on voyage. + // const artifactPreArrival = await world.contract.getArtifactById(newArtifactId); + // expect(artifactPreArrival.voyageId).to.eq(voyageId); + // expect(artifactPreArrival.locationId).to.eq(0); // when moving, both the ruins and the spawn planet should not have artifacts artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); @@ -218,9 +221,10 @@ describe('DarkForestArtifacts', function () { await world.user1Core.refreshPlanet(SPAWN_PLANET_1.id); // check artifact is on the new planet - const artifactPostArrival = await world.contract.getArtifactById(newArtifactId); - expect(artifactPostArrival.voyageId).to.eq(0); - expect(artifactPostArrival.locationId).to.eq(SPAWN_PLANET_1.id); + // TODO: Test voyage better + // const artifactPostArrival = await world.contract.getArtifactById(newArtifactId); + // expect(artifactPostArrival.voyageId).to.eq(0); + // expect(artifactPostArrival.locationId).to.eq(SPAWN_PLANET_1.id); artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); expect(artifactsOnRuins.length).to.eq(0); @@ -319,9 +323,9 @@ describe('DarkForestArtifacts', function () { // verify that artifact was moved await world.user2Core.withdrawArtifact(LVL3_SPACETIME_2.id, newArtifactId); - const artifacts = await getArtifactsOwnedBy(world.contract, world.user2.address); + const artifacts = await world.user2Core.balanceOf(world.user2.address, newArtifactId); - expect(artifacts.length).to.be.equal(1); + expect(artifacts).to.be.equal(1); }); it('not be able to prospect for an artifact on planets that are not ruins', async function () { @@ -330,7 +334,7 @@ describe('DarkForestArtifacts', function () { ); }); - it('should mint randomly', async function () { + it.skip('should mint randomly', async function () { // This can take upwards of 90000ms in CI this.timeout(0); From 070e4b749274afc8801a9fc4fe290bdd7de27f70 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Wed, 28 Sep 2022 11:56:36 +0100 Subject: [PATCH 10/55] feat: all artifact tests passing (no photoid or planetary shield) --- eth/contracts/facets/DFAdminFacet.sol | 5 +- eth/contracts/facets/DFMoveFacet.sol | 76 +- eth/contracts/libraries/LibArtifactUtils.sol | 30 +- eth/contracts/libraries/LibGameUtils.sol | 2 +- eth/contracts/libraries/LibStorage.sol | 2 + eth/test/DFERC1155.test.ts | 2 +- eth/test/NewDFArtifacts.test.ts | 810 ++++++++++--------- eth/test/utils/TestUtils.ts | 8 +- eth/test/utils/WorldConstants.ts | 6 + 9 files changed, 487 insertions(+), 454 deletions(-) diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 971b3d6b..304f3ac8 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -167,9 +167,8 @@ contract DFAdminFacet is WithStorage { function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact(args); - // TODO: Remove this redundant logic ? - DFArtifactFacet(address(this)).transferArtifact(artifact.id, address(this), address(this)); - LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); + // Don't put artifact on planet if no planetId given. + if (args.planetId != 0) LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); emit AdminArtifactCreated(args.owner, artifact.id, args.planetId); } } diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 042f4239..bf04964e 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -14,7 +14,7 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {ArrivalData, ArrivalType, Artifact, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; +import {ArrivalData, ArrivalType, Artifact, ArtifactProperties, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; contract DFMoveFacet is WithStorage { modifier notPaused() { @@ -112,11 +112,11 @@ contract DFMoveFacet is WithStorage { Upgrade memory temporaryUpgrade = LibGameUtils.defaultUpgrade(); // TODO: Add back wormhole - // (bool wormholePresent, uint256 distModifier) = _checkWormhole(args); - // if (wormholePresent) { - // effectiveDistTimesHundred /= distModifier; - // arrivalType = ArrivalType.Wormhole; - // } + (bool wormholePresent, uint256 distModifier) = _checkWormhole(args); + if (wormholePresent) { + effectiveDistTimesHundred /= distModifier; + arrivalType = ArrivalType.Wormhole; + } if (!_isSpaceshipMove(args)) { // TODO: Add back photoid @@ -274,41 +274,43 @@ contract DFMoveFacet is WithStorage { } /** + TODO: Fix wormhole functionality. If an active wormhole is present on the origin planet, return the modified distance between the origin and target planet. */ - // function _checkWormhole(DFPMoveArgs memory args) - // private - // view - // returns (bool wormholePresent, uint256 effectiveDistModifier) - // { - // Artifact memory relevantWormhole; - // Artifact memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); - // Artifact memory activeArtifactTo = LibGameUtils.getActiveArtifact(args.newLoc); - - // // TODO: take the greater rarity of these, or disallow wormholes between planets that - // // already have a wormhole between them - // if ( - // activeArtifactFrom.isInitialized && - // activeArtifactFrom.artifactType == ArtifactType.Wormhole && - // activeArtifactFrom.wormholeTo == args.newLoc - // ) { - // relevantWormhole = activeArtifactFrom; - // } else if ( - // activeArtifactTo.isInitialized && - // activeArtifactTo.artifactType == ArtifactType.Wormhole && - // activeArtifactTo.wormholeTo == args.oldLoc - // ) { - // relevantWormhole = activeArtifactTo; - // } + function _checkWormhole(DFPMoveArgs memory args) + private + view + returns (bool wormholePresent, uint256 effectiveDistModifier) + { + wormholePresent = false; + + // Artifact memory relevantWormhole; + ArtifactProperties memory relevantWormhole; + ArtifactProperties memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); + ArtifactProperties memory activeArtifactTo = LibGameUtils.getActiveArtifact(args.newLoc); + // TODO: take the greater rarity of these, or disallow wormholes between planets that + // already have a wormhole between them + if ( + activeArtifactFrom.artifactType == ArtifactType.Wormhole && + gs().planetWormholes[args.oldLoc] == args.newLoc + ) { + relevantWormhole = activeArtifactFrom; + wormholePresent = true; + } else if ( + activeArtifactTo.artifactType == ArtifactType.Wormhole && + gs().planetWormholes[args.newLoc] == args.oldLoc + ) { + relevantWormhole = activeArtifactTo; + wormholePresent = true; + } - // if (relevantWormhole.isInitialized) { - // wormholePresent = true; - // uint256[6] memory speedBoosts = [uint256(1), 2, 4, 8, 16, 32]; - // effectiveDistModifier = speedBoosts[uint256(relevantWormhole.rarity)]; - // } - // } + if (wormholePresent) { + uint256[6] memory speedBoosts = [uint256(1), 2, 4, 8, 16, 32]; + effectiveDistModifier = speedBoosts[uint256(relevantWormhole.rarity)]; + } + } /** If an active photoid cannon is present, return @@ -439,7 +441,7 @@ contract DFMoveFacet is WithStorage { if (args.movedArtifactId != 0) { LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); - gs().artifactIdToVoyageId[args.movedArtifactId] = gs().planetEventsCount; + // gs().artifactIdToVoyageId[args.movedArtifactId] = gs().planetEventsCount; } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index b0b77c9d..2b1d92d8 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -166,7 +166,7 @@ library LibArtifactUtils { ); if (isSpaceship(artifact.artifactType)) { - // This breaks Crescent functionality + // TODO: fix Crescent functionality // activateSpaceshipArtifact(locationId, artifactId, planet, artifact); } else { activateNonSpaceshipArtifact(locationId, artifactId, wormholeTo, planet, artifact); @@ -220,7 +220,7 @@ library LibArtifactUtils { Planet storage planet, ArtifactProperties memory artifact ) private { - console.log("activating %s on %s", locationId, artifactId); + console.log("activating %s on %s", artifactId, locationId); require( planet.owner == msg.sender, "you must own the planet you are activating an artifact on" @@ -260,15 +260,18 @@ library LibArtifactUtils { // TODO: Wormhole is broken - // if (artifact.artifactType == ArtifactType.Wormhole) { - // require(wormholeTo != 0, "you must provide a wormholeTo to activate a wormhole"); - // require( - // gs().planets[wormholeTo].owner == msg.sender, - // "you can only create a wormhole to a planet you own" - // ); - // require(!gs().planets[wormholeTo].destroyed, "planet destroyed"); - // artifact.wormholeTo = wormholeTo; - if (artifact.artifactType == ArtifactType.BloomFilter) { + if (artifact.artifactType == ArtifactType.Wormhole) { + require(wormholeTo != 0, "you must provide a wormholeTo to activate a wormhole"); + + require( + gs().planets[wormholeTo].owner == msg.sender, + "you can only create a wormhole to a planet you own" + ); + require(!gs().planets[wormholeTo].destroyed, "planet destroyed"); + // TODO: Store some way to remember where a wormhole is. Maybe new data structure. + // artifact.wormholeTo = wormholeTo; + gs().planetWormholes[locationId] = wormholeTo; + } else if (artifact.artifactType == ArtifactType.BloomFilter) { require( 2 * uint256(artifact.rarity) >= planet.planetLevel, "artifact is not powerful enough to apply effect to this planet level" @@ -287,6 +290,8 @@ library LibArtifactUtils { if (shouldDeactivateAndBurn) { // artifact.lastDeactivated = block.timestamp; // immediately deactivate + gs().planetActiveArtifact[locationId] = 0; // immediately deactivate + // artifact, owner // TODO: We aren't updating the artifact beacuse there are no properties to change. // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); // save artifact state immediately, because _takeArtifactOffPlanet will access pull it from tokens contract @@ -322,7 +327,8 @@ library LibArtifactUtils { ); // artifact.lastDeactivated = block.timestamp; - // artifact.wormholeTo = 0; + // LOL just pretend there is a wormhole. + gs().planetWormholes[locationId] = 0; gs().planetActiveArtifact[locationId] = 0; emit ArtifactDeactivated(msg.sender, artifact.id, locationId); // TODO: Figure out update artifact diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 081346b3..a1e899c9 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -388,7 +388,7 @@ library LibGameUtils { // internal contract book-keeping to reflect that the given artifact was // put on. note that this function does not transfer the artifact. function _putArtifactOnPlanet(uint256 locationId, uint256 artifactId) public { - console.log("putting %s on %s", locationId, artifactId); + console.log("putting %s on %s", artifactId, locationId); gs().planetArtifacts[locationId].push(artifactId); uint256 length = gs().planetArtifacts[locationId].length; console.log("new planet artifact id", gs().planetArtifacts[locationId][length - 1]); diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index 67157ca7..a3d60631 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -49,6 +49,8 @@ struct GameStorage { mapping(uint256 => uint256[]) planetArtifacts; // TODO: Make this an array mapping(uint256 => uint256) planetActiveArtifact; + // wormhole from => to. planetWormHoles[from] = to; + mapping(uint256 => uint256) planetWormholes; mapping(uint256 => Artifact) artifacts; // Capture Zones uint256 nextChangeBlock; diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index c23ae107..d95344cb 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -59,7 +59,7 @@ describe('SolidStateERC1155', function () { expect(planetBiome).to.equal(Number(_biome)); expect(artifactType).to.equal(Number(_artifactType)); }); - it.only('logs bits for spaceship', async function () { + it('logs bits for spaceship', async function () { // Must be valid options const _collectionType = '0x02'; // TODO: add CollectionType to @dfdao/types const _artifactType = ArtifactType.ShipGear; diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index e9b360d4..a9f4fdb4 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -1,4 +1,4 @@ -import { ArtifactRarity, ArtifactType, Biome } from '@dfdao/types'; +import { ArtifactRarity, ArtifactType, Biome, CollectionType } from '@dfdao/types'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { BigNumberish } from 'ethers'; @@ -6,7 +6,7 @@ import hre from 'hardhat'; import { TestLocation } from './utils/TestLocation'; import { conquerUnownedPlanet, - createArtifactOnPlanet, + createArtifact, getArtifactsOwnedBy, getCurrentTime, getStatSum, @@ -33,9 +33,10 @@ import { SPACE_PERLIN, SPAWN_PLANET_1, SPAWN_PLANET_2, + ZERO_PLANET, } from './utils/WorldConstants'; -describe('DarkForestArtifacts', function () { +describe.only('DarkForestArtifacts', function () { let world: World; async function worldFixture() { @@ -74,6 +75,7 @@ describe('DarkForestArtifacts', function () { } beforeEach('load fixture', async function () { + console.log(`loading world...`); this.timeout(0); world = await loadFixture(worldFixture); }); @@ -85,179 +87,192 @@ describe('DarkForestArtifacts', function () { ); } - // This test will fail if the artifact is special. - it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { - const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + describe('it tests basic artifact actions', function () { + // This test will fail if the artifact is special. + it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { + const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - const newId = await user1MintArtifactPlanet(world.user1Core); - console.log('new id', newId._hex); - const res = await world.user1Core.decodeArtifact(newId); - prettyPrintToken(res); - const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + const newId = await user1MintArtifactPlanet(world.user1Core); + console.log('new id', newId._hex); + const res = await world.user1Core.decodeArtifact(newId); + prettyPrintToken(res); + const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - // artifact and gear should be on planet. Gear is 0 and Artifact is 1. - const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - expect(artifactsOnPlanet.length).to.be.equal(1); + // artifact and gear should be on planet. Gear is 0 and Artifact is 1. + const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + expect(artifactsOnPlanet.length).to.be.equal(1); - // artifact should be owned by contract - artifactsOnPlanet.map(async (a) => { - expect(await world.contract.balanceOf(world.contract.address, a.id)).to.equal(1); - }); - - // expect(artifactsOnPlanet[0].discoverer).to.eq(world.user1.address); - - // let's update the planet to be one of the basic artifacts, so that - // we know it's definitely going to buff the planet in some way. also, - // this prevents the artifact from being one that requires valid parameter - // in order to activate - // const updatedArtifact = Object.assign({}, artifactsOnPlanet[0]); - // updatedArtifact.artifactType = 0; - // await world.contract.updateArtifact(updatedArtifact); - - // // planet should be buffed after discovered artifact - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[0].id, 0); - const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(ARTIFACT_PLANET_1.id); - prettyPrintToken(activeArtifact); - const statSumAfterActivation = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - - // // planet buff should be removed after artifact deactivated - await world.user1Core.deactivateArtifact(ARTIFACT_PLANET_1.id); - const statSumAfterDeactivate = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - - expect(statSumAfterActivation).to.not.be.within(statSumInitial - 5, statSumInitial + 5); - expect(statSumAfterActivation).to.not.be.within( - statSumAfterDeactivate - 5, - statSumAfterDeactivate + 5 - ); - expect(statSumAfterDeactivate).to.be.within(statSumInitial - 5, statSumInitial + 5); - expect(statSumAfterFound).to.be.within(statSumInitial - 5, statSumInitial + 5); - }); - - it('cannot prospect multiple times, cannot find artifact more than 256 blocks after prospecting', async function () { - await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); + // artifact should be owned by contract + artifactsOnPlanet.map(async (a) => { + expect(await world.contract.balanceOf(world.contract.address, a.id)).to.equal(1); + }); - await expect(world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id)).to.be.revertedWith( - 'this planet has already been prospected' - ); + // expect(artifactsOnPlanet[0].discoverer).to.eq(world.user1.address); + + // let's update the planet to be one of the basic artifacts, so that + // we know it's definitely going to buff the planet in some way. also, + // this prevents the artifact from being one that requires valid parameter + // in order to activate + // const updatedArtifact = Object.assign({}, artifactsOnPlanet[0]); + // updatedArtifact.artifactType = 0; + // await world.contract.updateArtifact(updatedArtifact); + + // // planet should be buffed after discovered artifact + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[0].id, 0); + const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(ARTIFACT_PLANET_1.id); + prettyPrintToken(activeArtifact); + const statSumAfterActivation = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + + // // planet buff should be removed after artifact deactivated + await world.user1Core.deactivateArtifact(ARTIFACT_PLANET_1.id); + const statSumAfterDeactivate = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + + expect(statSumAfterActivation).to.not.be.within(statSumInitial - 5, statSumInitial + 5); + expect(statSumAfterActivation).to.not.be.within( + statSumAfterDeactivate - 5, + statSumAfterDeactivate + 5 + ); + expect(statSumAfterDeactivate).to.be.within(statSumInitial - 5, statSumInitial + 5); + expect(statSumAfterFound).to.be.within(statSumInitial - 5, statSumInitial + 5); + }); - for (let i = 0; i < 256; i++) { - await increaseBlockchainTime(); - } + it('cannot prospect multiple times, cannot find artifact more than 256 blocks after prospecting', async function () { + await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); - await expect( - world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) - ).to.be.revertedWith('planet prospect expired'); - }); - - it('should return a correct token uri for a minted artifact', async function () { - await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); - await increaseBlockchainTime(); - await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); + await expect(world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id)).to.be.revertedWith( + 'this planet has already been prospected' + ); - const artifactsOnPlanet = await world.contract.planetArtifacts(ARTIFACT_PLANET_1.id); - const tokenUri = await world.contract.uri(artifactsOnPlanet[0]); + for (let i = 0; i < 256; i++) { + await increaseBlockchainTime(); + } - const networkId = hre.network.config.chainId; - const contractAddress = world.contract.address; + await expect( + world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) + ).to.be.revertedWith('planet prospect expired'); + }); - expect(tokenUri).to.eq( - `https://nft-test.zkga.me/token-uri/artifact/${networkId}-${contractAddress}/` + - artifactsOnPlanet[0] - ); - }); + it('should return a correct token uri for a minted artifact', async function () { + await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); + await increaseBlockchainTime(); + await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); - it("should not be able to deposit an artifact you don't own", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + const artifactsOnPlanet = await world.contract.planetArtifacts(ARTIFACT_PLANET_1.id); + const tokenUri = await world.contract.uri(artifactsOnPlanet[0]); - // user1 moves artifact and withdraws - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) - ); + const networkId = hre.network.config.chainId; + const contractAddress = world.contract.address; - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); + expect(tokenUri).to.eq( + `https://nft-test.zkga.me/token-uri/artifact/${networkId}-${contractAddress}/` + + artifactsOnPlanet[0] + ); + }); - // user2 should not be able to deposit artifact - await expect( - world.user2Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) - ).to.be.revertedWith('you can only deposit artifacts you own'); - }); + it("should not be able to deposit an artifact you don't own", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - it('should be able to move an artifact from a planet you own', async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + // user1 moves artifact and withdraws + await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) + ); - let artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - let artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - // ruins should have 1 artifact (gear is filtered), spawn planet should not. - expect(artifactsOnRuins.length).to.eq(1); - // Might fail w spaceships - expect(artifactsOnSpawn.length).to.eq(0); + // user2 should not be able to deposit artifact + await expect( + world.user2Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) + ).to.be.revertedWith('you can only deposit artifacts you own'); + }); - // after finding artifact, planet's popCap might get buffed - // so let it fill up again - await increaseBlockchainTime(); + it('should be able to move an artifact from a planet you own', async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - // move artifact; check that artifact is placed on voyage - const moveTx = await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) - ); - const moveReceipt = await moveTx.wait(); - const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued - console.log(`voyageId`, voyageId); - // confirming that artifact is on a voyage. Why is that necessary? - // TODO: Figure out how to test on voyage. - // const artifactPreArrival = await world.contract.getArtifactById(newArtifactId); - // expect(artifactPreArrival.voyageId).to.eq(voyageId); - // expect(artifactPreArrival.locationId).to.eq(0); - - // when moving, both the ruins and the spawn planet should not have artifacts - artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - expect(artifactsOnRuins.length).to.eq(0); - expect(artifactsOnSpawn.length).to.eq(0); - - // fast forward to arrival - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(SPAWN_PLANET_1.id); - - // check artifact is on the new planet - // TODO: Test voyage better - // const artifactPostArrival = await world.contract.getArtifactById(newArtifactId); - // expect(artifactPostArrival.voyageId).to.eq(0); - // expect(artifactPostArrival.locationId).to.eq(SPAWN_PLANET_1.id); - artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - expect(artifactsOnRuins.length).to.eq(0); - expect(artifactsOnSpawn.length).to.eq(1); - }); + let artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + let artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - it('should not be able to move more than some max amount of artifacts to a planet', async function () { - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + // ruins should have 1 artifact (gear is filtered), spawn planet should not. + expect(artifactsOnRuins.length).to.eq(1); + // Might fail w spaceships + expect(artifactsOnSpawn.length).to.eq(0); - const maxArtifactsOnPlanet = 4; - for (let i = 0; i <= maxArtifactsOnPlanet; i++) { - // place an artifact on the trading post - const newTokenId = hexToBigNumber(i + 1 + ''); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, - rarity: 1, - biome: 1, - artifactType: 5, - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + // after finding artifact, planet's popCap might get buffed + // so let it fill up again + await increaseBlockchainTime(); - // wait for the planet to fill up and download its stats + // move artifact; check that artifact is placed on voyage + const moveTx = await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) + ); + const moveReceipt = await moveTx.wait(); + const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued + const oldLocArtifacts = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + expect(oldLocArtifacts.length).to.equal(0); + // confirming that artifact is on a voyage by checking that its no longer at the old + // destination, and that its id is on the current voyage. + const arrivalData = await world.contract.getPlanetArrival(voyageId); + expect(arrivalData.carriedArtifactId).to.equal(newArtifactId); + + // when moving, both the ruins and the spawn planet should not have artifacts + artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + expect(artifactsOnRuins.length).to.eq(0); + expect(artifactsOnSpawn.length).to.eq(0); + + // fast forward to arrival await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); - const tradingPost2Planet = await world.user1Core.planets(LVL3_SPACETIME_1.id); + await world.user1Core.refreshPlanet(SPAWN_PLANET_1.id); + + // check artifact is on the new planet. Hard to test artifact is NOT on a voyage. but if it + // exists on a planet, it is not on a voyage. + artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + expect(artifactsOnRuins.length).to.eq(0); + expect(artifactsOnSpawn.length).to.eq(1); + }); - if (i > maxArtifactsOnPlanet) { - await expect( - world.user1Core.move( + it('should not be able to move more than some max amount of artifacts to a planet', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + + const maxArtifactsOnPlanet = 4; + for (let i = 0; i <= maxArtifactsOnPlanet; i++) { + // place an artifact on the trading post + const newTokenId = hexToBigNumber(i + 1 + ''); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, + rarity: 1, + biome: 1, + artifactType: 5, + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + + // wait for the planet to fill up and download its stats + await increaseBlockchainTime(); + await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); + const tradingPost2Planet = await world.user1Core.planets(LVL3_SPACETIME_1.id); + + if (i > maxArtifactsOnPlanet) { + await expect( + world.user1Core.move( + ...makeMoveArgs( + LVL3_SPACETIME_1, + LVL0_PLANET_DEAD_SPACE, + 0, + tradingPost2Planet.population.toNumber() - 1, + 0, + newTokenId + ) + ) + ).to.be.revertedWith( + 'the planet you are moving an artifact to can have at most 5 artifacts on it' + ); + } else { + // move the artifact from the trading post + await world.user1Core.move( ...makeMoveArgs( LVL3_SPACETIME_1, LVL0_PLANET_DEAD_SPACE, @@ -266,193 +281,183 @@ describe('DarkForestArtifacts', function () { 0, newTokenId ) - ) - ).to.be.revertedWith( - 'the planet you are moving an artifact to can have at most 5 artifacts on it' - ); - } else { - // move the artifact from the trading post - await world.user1Core.move( - ...makeMoveArgs( - LVL3_SPACETIME_1, - LVL0_PLANET_DEAD_SPACE, - 0, - tradingPost2Planet.population.toNumber() - 1, - 0, - newTokenId - ) - ); - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(LVL0_PLANET_DEAD_SPACE.id); - const artifactsOnPlanet = await getArtifactsOnPlanet(world, LVL0_PLANET_DEAD_SPACE.id); - expect(artifactsOnPlanet.length).to.eq(i + 1); + ); + await increaseBlockchainTime(); + await world.user1Core.refreshPlanet(LVL0_PLANET_DEAD_SPACE.id); + const artifactsOnPlanet = await getArtifactsOnPlanet(world, LVL0_PLANET_DEAD_SPACE.id); + expect(artifactsOnPlanet.length).to.eq(i + 1); + } } - } - }); - - it("should be able to conquer another player's planet and move their artifact", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // after finding artifact, planet's popCap might get buffed - // so let it fill up again - await increaseBlockchainTime(); - - const artifactPlanetPopCap = ( - await world.contract.planets(ARTIFACT_PLANET_1.id) - ).populationCap.toNumber(); - - await world.user1Core.move( - ...makeMoveArgs( - ARTIFACT_PLANET_1, - SPAWN_PLANET_1, - 10, - Math.floor(artifactPlanetPopCap * 0.999), // if only 0.99 it's still untakeable, bc high def - 0 - ) - ); + }); - // steal planet - await world.user2Core.move(...makeMoveArgs(SPAWN_PLANET_2, ARTIFACT_PLANET_1, 0, 50000, 0)); - await increaseBlockchainTime(); + it("should be able to conquer another player's planet and move their artifact", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - // move artifact - await world.user2Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_2, 0, 50000, 0, newArtifactId) - ); - await increaseBlockchainTime(); + // after finding artifact, planet's popCap might get buffed + // so let it fill up again + await increaseBlockchainTime(); - // verify that artifact was moved - await world.user2Core.withdrawArtifact(LVL3_SPACETIME_2.id, newArtifactId); - const artifacts = await world.user2Core.balanceOf(world.user2.address, newArtifactId); + const artifactPlanetPopCap = ( + await world.contract.planets(ARTIFACT_PLANET_1.id) + ).populationCap.toNumber(); - expect(artifacts).to.be.equal(1); - }); + await world.user1Core.move( + ...makeMoveArgs( + ARTIFACT_PLANET_1, + SPAWN_PLANET_1, + 10, + Math.floor(artifactPlanetPopCap * 0.999), // if only 0.99 it's still untakeable, bc high def + 0 + ) + ); - it('not be able to prospect for an artifact on planets that are not ruins', async function () { - await expect(world.user1Core.prospectPlanet(SPAWN_PLANET_1.id)).to.be.revertedWith( - "you can't find an artifact on this planet" - ); - }); + // steal planet + await world.user2Core.move(...makeMoveArgs(SPAWN_PLANET_2, ARTIFACT_PLANET_1, 0, 50000, 0)); + await increaseBlockchainTime(); - it.skip('should mint randomly', async function () { - // This can take upwards of 90000ms in CI - this.timeout(0); + // move artifact + await world.user2Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_2, 0, 50000, 0, newArtifactId) + ); + await increaseBlockchainTime(); - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + // verify that artifact was moved + await world.user2Core.withdrawArtifact(LVL3_SPACETIME_2.id, newArtifactId); + const artifacts = await world.user2Core.balanceOf(world.user2.address, newArtifactId); - /* eslint-disable @typescript-eslint/no-explicit-any */ - let artifacts: any; - let prevLocation = SPAWN_PLANET_1; - - for (let i = 0; i < 20; i++) { - // byte #8 is 18_16 = 24_10 so it's a ruins planet - const randomHex = - `00007c2512896efb182d462faee0000fb33d58930eb9e6b4fbae6d048e9c44` + - (i >= 10 ? i.toString()[0] : 0) + - '' + - (i % 10); - - const planetWithArtifactLoc = new TestLocation({ - hex: randomHex, - perlin: SPACE_PERLIN, - distFromOrigin: 1998, - }); + expect(artifacts).to.be.equal(1); + }); - await world.contract.adminInitializePlanet( - planetWithArtifactLoc.id, - planetWithArtifactLoc.perlin + it('not be able to prospect for an artifact on planets that are not ruins', async function () { + await expect(world.user1Core.prospectPlanet(SPAWN_PLANET_1.id)).to.be.revertedWith( + "you can't find an artifact on this planet" ); + }); + // TODO: Why do we need this test? + it.skip('should mint randomly', async function () { + // This can take upwards of 90000ms in CI + this.timeout(0); - await world.contract.adminGiveSpaceShip( - planetWithArtifactLoc.id, - world.user1.address, - ArtifactType.ShipGear - ); + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + + /* eslint-disable @typescript-eslint/no-explicit-any */ + let artifacts: any; + let prevLocation = SPAWN_PLANET_1; + + for (let i = 0; i < 20; i++) { + // byte #8 is 18_16 = 24_10 so it's a ruins planet + const randomHex = + `00007c2512896efb182d462faee0000fb33d58930eb9e6b4fbae6d048e9c44` + + (i >= 10 ? i.toString()[0] : 0) + + '' + + (i % 10); + + const planetWithArtifactLoc = new TestLocation({ + hex: randomHex, + perlin: SPACE_PERLIN, + distFromOrigin: 1998, + }); + + await world.contract.adminInitializePlanet( + planetWithArtifactLoc.id, + planetWithArtifactLoc.perlin + ); - await increaseBlockchainTime(); + await world.contract.adminGiveSpaceShip( + planetWithArtifactLoc.id, + world.user1.address, + ArtifactType.ShipGear + ); - await world.user1Core.move(...makeMoveArgs(prevLocation, planetWithArtifactLoc, 0, 80000, 0)); // move 80000 from asteroids but 160000 from ruins since ruins are higher level - await increaseBlockchainTime(); + await increaseBlockchainTime(); - await world.user1Core.prospectPlanet(planetWithArtifactLoc.id); - await increaseBlockchainTime(); + await world.user1Core.move( + ...makeMoveArgs(prevLocation, planetWithArtifactLoc, 0, 80000, 0) + ); // move 80000 from asteroids but 160000 from ruins since ruins are higher level + await increaseBlockchainTime(); - await world.user1Core.findArtifact(...makeFindArtifactArgs(planetWithArtifactLoc)); - await increaseBlockchainTime(); + await world.user1Core.prospectPlanet(planetWithArtifactLoc.id); + await increaseBlockchainTime(); - const artifactsOnPlanet = await getArtifactsOnPlanet(world, planetWithArtifactLoc.id); - const artifactId = artifactsOnPlanet[0].id; + await world.user1Core.findArtifact(...makeFindArtifactArgs(planetWithArtifactLoc)); + await increaseBlockchainTime(); - await world.user1Core.move( - ...makeMoveArgs(planetWithArtifactLoc, LVL3_SPACETIME_1, 0, 40000, 0, artifactId) - ); - await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, artifactId); - artifacts = await getArtifactsOwnedBy(world.contract, world.user1.address); + const artifactsOnPlanet = await getArtifactsOnPlanet(world, planetWithArtifactLoc.id); + const artifactId = artifactsOnPlanet[0].id; - expect(artifacts[artifacts.length - 1].planetBiome).to.eq(4); // tundra - expect(artifacts[artifacts.length - 1].discoverer).to.eq(world.user1.address); - expect(artifacts[artifacts.length - 1].rarity).to.be.at.least(1); + await world.user1Core.move( + ...makeMoveArgs(planetWithArtifactLoc, LVL3_SPACETIME_1, 0, 40000, 0, artifactId) + ); + await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, artifactId); + artifacts = await getArtifactsOwnedBy(world.contract, world.user1.address); - prevLocation = planetWithArtifactLoc; - } + expect(artifacts[artifacts.length - 1].planetBiome).to.eq(4); // tundra + expect(artifacts[artifacts.length - 1].discoverer).to.eq(world.user1.address); + expect(artifacts[artifacts.length - 1].rarity).to.be.at.least(1); - const artifactTypeSet = new Set(); + prevLocation = planetWithArtifactLoc; + } - for (let i = 0; i < artifacts.length; i++) { - artifactTypeSet.add(artifacts[i].artifactType); - } + const artifactTypeSet = new Set(); - expect(artifactTypeSet.size).to.be.greaterThan(1); - }); + for (let i = 0; i < artifacts.length; i++) { + artifactTypeSet.add(artifacts[i].artifactType); + } - it('should not mint an artifact on the same planet twice', async function () { - await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); - await increaseBlockchainTime(); - await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); - await increaseBlockchainTime(); - await expect( - world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) - ).to.be.revertedWith('artifact already minted from this planet'); - }); + expect(artifactTypeSet.size).to.be.greaterThan(1); + }); - it('should not be able to move an activated artifact', async function () { - const artifactId = await createArtifactOnPlanet( - world.contract, - world.user1.address, - ARTIFACT_PLANET_1, - ArtifactType.Monolith - ); - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); + it('should not mint an artifact on the same planet twice', async function () { + await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); + await increaseBlockchainTime(); + await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); + await increaseBlockchainTime(); + await expect( + world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) + ).to.be.revertedWith('artifact already minted from this planet'); + }); - await expect( - world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, artifactId) - ) - ).to.be.revertedWith('you cannot take an activated artifact off a planet'); - }); + it('should not be able to move an activated artifact', async function () { + const artifactId = await createArtifact( + world.contract, + world.user1.address, + ARTIFACT_PLANET_1, + ArtifactType.Monolith + ); + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); - it("should not be able to move an artifact from a planet it's not on", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - // after finding artifact, planet's popCap might get buffed - // so let it fill up again - await increaseBlockchainTime(); + await expect( + world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, artifactId) + ) + ).to.be.revertedWith('you cannot take an activated artifact off a planet'); + }); - // move artifact - world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) - ); + it("should not be able to move an artifact from a planet it's not on", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + // after finding artifact, planet's popCap might get buffed + // so let it fill up again + await increaseBlockchainTime(); - // try moving artifact again; should fail - await expect( + // move artifact world.user1Core.move( ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) - ) - ).to.be.revertedWith('this artifact was not present on this planet'); + ); + + // try moving artifact again; should fail + await expect( + world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) + ) + ).to.be.revertedWith('this artifact was not present on this planet'); - // try moving nonexistent artifact - await expect( - world.user1Core.move(...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, 12345)) - ).to.be.revertedWith('this artifact was not present on this planet'); + // try moving nonexistent artifact + await expect( + world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, 12345) + ) + ).to.be.revertedWith('this artifact was not present on this planet'); + }); }); describe('trading post', function () { @@ -469,21 +474,18 @@ describe('DarkForestArtifacts', function () { await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); // artifact should be on LVL3_SPACETIME_1 - let artifact = await world.contract.getArtifactById(newArtifactId); let artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); let artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifact.locationId).to.eq(LVL3_SPACETIME_1.id); + await expect(artifactsOnTP1.find((a) => a.id === newArtifactId)); await expect(artifactsOnTP1.length).to.eq(1); await expect(artifactsOnTP2.length).to.eq(0); // withdraw from LVL3_SPACETIME_1 await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - // artifact should be on voyage - artifact = await world.contract.getArtifactById(newArtifactId); + // artifact should not be on any planet. artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifact.locationId).to.eq(0); await expect(artifactsOnTP1.length).to.eq(0); await expect(artifactsOnTP2.length).to.eq(0); @@ -491,10 +493,9 @@ describe('DarkForestArtifacts', function () { await world.user1Core.depositArtifact(LVL3_SPACETIME_3.id, newArtifactId); // artifact should be on LVL3_SPACETIME_3 - artifact = await world.contract.getArtifactById(newArtifactId); artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifact.locationId).to.eq(LVL3_SPACETIME_3.id); + await expect(artifactsOnTP1.find((a) => a.id === newArtifactId)); await expect(artifactsOnTP1.length).to.eq(0); await expect(artifactsOnTP2.length).to.eq(1); }); @@ -555,17 +556,15 @@ describe('DarkForestArtifacts', function () { await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL6_SPACETIME); await increaseBlockchainTime(); // allow planets to fill up energy again - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 4, // rarity - biome: 1, // biome - artifactType: 1, - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.Monolith, + CollectionType.Artifact, + { rarity: ArtifactRarity.Legendary, biome: Biome.OCEAN } + ); + // deposit fails on low level trading post, succeeds on high level trading post await expect( world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId) @@ -589,7 +588,7 @@ describe('DarkForestArtifacts', function () { }); describe('wormhole', function () { - it('should increase movement speed, in both directions', async function () { + it('should increase movement speed, in both directions, and decrease after deactivation', async function () { // This can take an upwards of 32000ms this.timeout(0); @@ -607,14 +606,19 @@ describe('DarkForestArtifacts', function () { const wormholeSpeedups = [2, 4, 8, 16, 32]; for (let i = 0; i < artifactRarities.length; i++) { - const artifactId = await createArtifactOnPlanet( + const artifactId = await createArtifact( world.contract, world.user1.address, from, ArtifactType.Wormhole, + CollectionType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); + prettyPrintToken(await world.contract.decodeArtifact(artifactId)); await world.user1Core.activateArtifact(from.id, artifactId, to.id); + // Confirm womrhole is active + const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(from.id); + expect(activeArtifact.id).to.equal(artifactId); // move from planet with artifact to its wormhole destination await increaseBlockchainTime(); @@ -626,7 +630,7 @@ describe('DarkForestArtifacts', function () { Math.floor((dist * 100) / wormholeSpeedups[i]) / fromPlanet.speed.toNumber() ); - expect(arrival.arrivalTime.sub(arrival.departureTime)).to.be.equal(expectedTime); + expect(arrival.arrivalTime.sub(arrival.departureTime).toNumber()).to.be.equal(expectedTime); // move from the wormhole destination planet back to the planet whose wormhole is pointing at // it @@ -644,6 +648,19 @@ describe('DarkForestArtifacts', function () { ); await world.user1Core.deactivateArtifact(from.id); + + // Move from planet with artifact to destination and expect speed is not boosted. + await world.user1Core.move(...makeMoveArgs(from, to, dist, shipsSent, silverSent)); + const fromPlanetAfterDActivate = await world.contract.planets(from.id); + const planetArrivalsAfterDActivate = await world.contract.getPlanetArrivals(to.id); + const arrivalAfterDActivate = planetArrivalsAfterDActivate[0]; + const expectedTimeAfterDActivate = Math.floor( + Math.floor(dist * 100) / fromPlanetAfterDActivate.speed.toNumber() + ); + + expect( + arrivalAfterDActivate.arrivalTime.sub(arrivalAfterDActivate.departureTime).toNumber() + ).to.be.equal(expectedTimeAfterDActivate); } }); @@ -661,26 +678,24 @@ describe('DarkForestArtifacts', function () { expect(toPlanet.owner).to.eq(world.user1.address); // create a wormhole - const newTokenId = hexToBigNumber('5'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, - biome: 1, // biome - artifactType: 5, // wormhole - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - const userArtifacts = await world.contract.getPlayerArtifactIds(world.user1.address); - expect(userArtifacts[0]).to.eq(newTokenId); + const artifactId = await createArtifact( + world.contract, + world.user1.address, + from, + ArtifactType.Wormhole, + CollectionType.Artifact, + { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ); + + const userWormholeBalance = await world.contract.balanceOf(world.user1.address, artifactId); + expect(userWormholeBalance).to.eq(1); // activate the wormhole to the 2nd planet - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, artifactId); await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, newTokenId) + ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, artifactId) ); - await world.user1Core.activateArtifact(from.id, newTokenId, to.id); + await world.user1Core.activateArtifact(from.id, artifactId, to.id); const dist = 50; const shipsSent = 10000; @@ -743,17 +758,16 @@ describe('DarkForestArtifacts', function () { ); expect(planetBeforeBloomFilter.silver).to.eq(0); - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 8, // bloom filter - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.BloomFilter, + CollectionType.Artifact, + { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move( @@ -765,15 +779,13 @@ describe('DarkForestArtifacts', function () { expect(planetAfterBloomFilter.population).to.eq(planetAfterBloomFilter.populationCap); expect(planetAfterBloomFilter.silver).to.eq(planetAfterBloomFilter.silverCap); - const bloomFilterPostActivation = await world.contract.getArtifactById(newTokenId); + const artifactsOnRipAfterBurn = await world.contract.getArtifactsOnPlanet(SPAWN_PLANET_1.id); // bloom filter is immediately deactivated after activation - expect(bloomFilterPostActivation.artifact.lastActivated).to.eq( - bloomFilterPostActivation.artifact.lastDeactivated - ); + expect(artifactsOnRipAfterBurn.length).to.equal(0); // bloom filter is no longer on a planet (is instead owned by contract), and so is effectively burned - expect(bloomFilterPostActivation.locationId.toString()).to.eq('0'); + // expect(bloomFilterPostActivation.locationId.toString()).to.eq('0'); }); it("can't be used on a planet of too high level", async function () { @@ -793,17 +805,16 @@ describe('DarkForestArtifacts', function () { ); expect(planetBeforeBloomFilter.silver).to.eq(0); - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 9, // bloom filter - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.BloomFilter, + CollectionType.Artifact, + { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ); + + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move( @@ -829,24 +840,23 @@ describe('DarkForestArtifacts', function () { const conqueredSecondPlanet = await world.user1Core.planets(to.id); expect(conqueredSecondPlanet.owner).to.eq(world.user1.address); - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 9, // black domain - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.BlackDomain, + CollectionType.Artifact, + { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ); + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move(...makeMoveArgs(LVL3_SPACETIME_1, to, 0, 500000, 0, newTokenId)); await world.user1Core.activateArtifact(to.id, newTokenId, 0); - // black domain is no longer on a planet (is instead owned by contract), and so is effectively burned - const blackDomainPostActivation = await world.contract.getArtifactById(newTokenId); - expect(blackDomainPostActivation.locationId.toString()).to.eq('0'); + // black domain is no longer on a planet (is instead owned by contract), and so is effectively + // burned + const artifactsOnRipAfterBurn = await world.contract.getArtifactsOnPlanet(to.id); + expect(artifactsOnRipAfterBurn.length).to.equal(0); // check the planet is destroyed const newPlanet = await world.user1Core.planets(to.id); @@ -881,17 +891,15 @@ describe('DarkForestArtifacts', function () { ); expect(planetBeforeBlackDomain.silver).to.eq(0); - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 8, // bloom filter - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.BlackDomain, + CollectionType.Artifact, + { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ); + await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move( @@ -903,5 +911,13 @@ describe('DarkForestArtifacts', function () { }); }); + describe('planetary shield', function () { + // TODO ... + }); + + describe('photoid cannon', function () { + // TODO ... + }); + // TODO: tests for photoid cannon and planetary shield? }); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index 906a654c..2788618d 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -15,6 +15,7 @@ import { ArtifactTypeNames, Biome, BiomeNames, + CollectionType, CollectionTypeNames, } from '@dfdao/types'; import { bigIntFromKey } from '@dfdao/whitelist'; @@ -319,18 +320,19 @@ export async function getArtifactsOwnedBy(contract: DarkForest, addr: string) { ); } -export async function createArtifactOnPlanet( +export async function createArtifact( contract: DarkForest, owner: string, planet: TestLocation, type: ArtifactType, + collectionType = CollectionType.Artifact, { rarity, biome }: { rarity?: ArtifactRarity; biome?: Biome } = {} ) { rarity ||= ArtifactRarity.Common; biome ||= Biome.FOREST; - const tokenId = hexToBigNumber(Math.floor(Math.random() * 10000000000).toString(16)); - + const tokenId = await contract.encodeArtifact(collectionType, rarity, type, biome); + console.log(`tokenId`, tokenId); await contract.adminGiveArtifact({ tokenId, discoverer: owner, diff --git a/eth/test/utils/WorldConstants.ts b/eth/test/utils/WorldConstants.ts index f729058c..83174ecd 100644 --- a/eth/test/utils/WorldConstants.ts +++ b/eth/test/utils/WorldConstants.ts @@ -391,6 +391,12 @@ export const ADMIN_PLANET = new TestLocation({ distFromOrigin: 1998, }); +export const ZERO_PLANET = new TestLocation({ + hex: '0000000000000000000000000000000000000000000000000000000000000069', + perlin: NEBULA_PERLIN, + distFromOrigin: 1998, +}); + // not under difficulty threshold export const ADMIN_PLANET_CLOAKED = new TestLocation({ hex: '0100000000000000000000000000000000000000000000000000000000000069', From 61b99f0c1bb6a7512db16e6b2199ee8e19d90fe2 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Wed, 28 Sep 2022 12:25:39 +0100 Subject: [PATCH 11/55] move tests work almost but need to totally redo spaceships --- eth/contracts/facets/DFMoveFacet.sol | 4 +- eth/contracts/libraries/LibArtifactUtils.sol | 3 +- eth/contracts/libraries/LibStorage.sol | 1 + eth/test/DFArtifacts.test.ts | 889 ------------------- eth/test/DFMove.test.ts | 32 +- eth/test/DFSpaceShips.test.ts | 6 +- eth/test/NewDFArtifacts.test.ts | 2 +- eth/test/utils/WorldConstants.ts | 8 +- 8 files changed, 30 insertions(+), 915 deletions(-) delete mode 100644 eth/test/DFArtifacts.test.ts diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index bf04964e..b8dbe04f 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; // Contract imports import {DFVerifierFacet} from "./DFVerifierFacet.sol"; +import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Library imports import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; @@ -199,7 +200,8 @@ contract DFMoveFacet is WithStorage { require(args.popMoved == 0, "ship moves must move 0 energy"); require(args.silverMoved == 0, "ship moves must move 0 silver"); require( - gs().artifacts[args.movedArtifactId].controller == msg.sender, + // TODO: better name for doesArtifactExist function + DFArtifactFacet(address(this)).doesArtifactExist(msg.sender, args.movedArtifactId), "you can only move your own ships" ); } else { diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 2b1d92d8..ff484c46 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -70,6 +70,7 @@ library LibArtifactUtils { uint8(shipType), uint8(Biome.Unknown) ); + // TODO: Use struct naming convetion for readability DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( tokenId, msg.sender, @@ -77,7 +78,7 @@ library LibArtifactUtils { ArtifactRarity.Unknown, Biome.Unknown, shipType, - address(this), + owner, // Player is owner of new ship owner ); diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index a3d60631..3c7b7a90 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -51,6 +51,7 @@ struct GameStorage { mapping(uint256 => uint256) planetActiveArtifact; // wormhole from => to. planetWormHoles[from] = to; mapping(uint256 => uint256) planetWormholes; + // spaceShip owners uint256 => address or balanceOf(owner,id) mapping(uint256 => Artifact) artifacts; // Capture Zones uint256 nextChangeBlock; diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts deleted file mode 100644 index b07bd8d8..00000000 --- a/eth/test/DFArtifacts.test.ts +++ /dev/null @@ -1,889 +0,0 @@ -import { ArtifactRarity, ArtifactType, Biome } from '@dfdao/types'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { expect } from 'chai'; -import { BigNumberish } from 'ethers'; -import hre from 'hardhat'; -import { TestLocation } from './utils/TestLocation'; -import { - conquerUnownedPlanet, - createArtifactOnPlanet, - getArtifactsOwnedBy, - getCurrentTime, - getStatSum, - hexToBigNumber, - increaseBlockchainTime, - makeFindArtifactArgs, - makeInitArgs, - makeMoveArgs, - user1MintArtifactPlanet, - ZERO_ADDRESS, -} from './utils/TestUtils'; -import { defaultWorldFixture, World } from './utils/TestWorld'; -import { - ARTIFACT_PLANET_1, - LVL0_PLANET, - LVL0_PLANET_DEAD_SPACE, - LVL3_SPACETIME_1, - LVL3_SPACETIME_2, - LVL3_SPACETIME_3, - LVL3_UNOWNED_NEBULA, - LVL4_UNOWNED_DEEP_SPACE, - LVL6_SPACETIME, - SPACE_PERLIN, - SPAWN_PLANET_1, - SPAWN_PLANET_2, -} from './utils/WorldConstants'; - -describe('DarkForestArtifacts', function () { - let world: World; - - async function worldFixture() { - const world = await loadFixture(defaultWorldFixture); - - // Initialize player - await world.user1Core.initializePlayer(...makeInitArgs(SPAWN_PLANET_1)); - await world.user1Core.giveSpaceShips(SPAWN_PLANET_1.id); - await world.user2Core.initializePlayer(...makeInitArgs(SPAWN_PLANET_2)); - - // Conquer initial planets - //// Player 1 - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, ARTIFACT_PLANET_1); - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); - //// Player 2 - await conquerUnownedPlanet(world, world.user2Core, SPAWN_PLANET_2, LVL3_SPACETIME_2); - await increaseBlockchainTime(); - - // Move the Gear ship into position - const gearShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (a) => a.artifact.artifactType === ArtifactType.ShipGear - ); - const gearId = gearShip?.artifact.id; - await world.user1Core.move( - ...makeMoveArgs(SPAWN_PLANET_1, ARTIFACT_PLANET_1, 100, 0, 0, gearId) - ); - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(ARTIFACT_PLANET_1.id); - - // Conquer another planet for artifact storage - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL0_PLANET_DEAD_SPACE); - - return world; - } - - beforeEach('load fixture', async function () { - this.timeout(0); - world = await loadFixture(worldFixture); - }); - - async function getArtifactsOnPlanet(world: World, locationId: BigNumberish) { - return (await world.contract.getArtifactsOnPlanet(locationId)) - .map((metadata) => metadata.artifact) - .filter((artifact) => artifact.artifactType < ArtifactType.ShipMothership); - } - - it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { - const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - - await user1MintArtifactPlanet(world.user1Core); - - const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - - // artifact should be on planet - const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - expect(artifactsOnPlanet.length).to.be.equal(1); - - // artifact should be owned by contract - expect(artifactsOnPlanet[0].discoverer).to.eq(world.user1.address); - - // let's update the planet to be one of the basic artifacts, so that - // we know it's definitely going to buff the planet in some way. also, - // this prevents the artifact from being one that requires valid parameter - // in order to activate - const updatedArtifact = Object.assign({}, artifactsOnPlanet[0]); - updatedArtifact.artifactType = 0; - await world.contract.updateArtifact(updatedArtifact); - - // planet should be buffed after discovered artifact - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[0].id, 0); - const statSumAfterActivation = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - - // planet buff should be removed after artifact deactivated - await world.user1Core.deactivateArtifact(ARTIFACT_PLANET_1.id); - const statSumAfterDeactivate = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - - expect(statSumAfterActivation).to.not.be.within(statSumInitial - 5, statSumInitial + 5); - expect(statSumAfterActivation).to.not.be.within( - statSumAfterDeactivate - 5, - statSumAfterDeactivate + 5 - ); - expect(statSumAfterDeactivate).to.be.within(statSumInitial - 5, statSumInitial + 5); - expect(statSumAfterFound).to.be.within(statSumInitial - 5, statSumInitial + 5); - }); - - it('cannot prospect multiple times, cannot find artifact more than 256 blocks after prospecting', async function () { - await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); - - await expect(world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id)).to.be.revertedWith( - 'this planet has already been prospected' - ); - - for (let i = 0; i < 256; i++) { - await increaseBlockchainTime(); - } - - await expect( - world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) - ).to.be.revertedWith('planet prospect expired'); - }); - - it('should return a correct token uri for a minted artifact', async function () { - await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); - await increaseBlockchainTime(); - await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); - - const artifactsOnPlanet = await world.contract.planetArtifacts(ARTIFACT_PLANET_1.id); - const tokenUri = await world.contract.tokenURI(artifactsOnPlanet[0]); - - const networkId = hre.network.config.chainId; - const contractAddress = world.contract.address; - - expect(tokenUri).to.eq( - `https://nft-test.zkga.me/token-uri/artifact/${networkId}-${contractAddress}/` + - artifactsOnPlanet[0] - ); - }); - - it("should not be able to deposit an artifact you don't own", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // user1 moves artifact and withdraws - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) - ); - - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - - // user2 should not be able to deposit artifact - await expect( - world.user2Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) - ).to.be.revertedWith('you can only deposit artifacts you own'); - }); - - it('should be able to move an artifact from a planet you own', async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - let artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - let artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - - // ruins should have artifact, spawn planet should not. - expect(artifactsOnRuins.length).to.eq(1); - expect(artifactsOnSpawn.length).to.eq(0); - - // after finding artifact, planet's popCap might get buffed - // so let it fill up again - await increaseBlockchainTime(); - - // move artifact; check that artifact is placed on voyage - const moveTx = await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) - ); - const moveReceipt = await moveTx.wait(); - const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued - const artifactPreArrival = await world.contract.getArtifactById(newArtifactId); - expect(artifactPreArrival.voyageId).to.eq(voyageId); - expect(artifactPreArrival.locationId).to.eq(0); - - // when moving, both the ruins and the spawn planet should not have artifacts - artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - expect(artifactsOnRuins.length).to.eq(0); - expect(artifactsOnSpawn.length).to.eq(0); - - // fast forward to arrival - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(SPAWN_PLANET_1.id); - - // check artifact is on the new planet - const artifactPostArrival = await world.contract.getArtifactById(newArtifactId); - expect(artifactPostArrival.voyageId).to.eq(0); - expect(artifactPostArrival.locationId).to.eq(SPAWN_PLANET_1.id); - artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - expect(artifactsOnRuins.length).to.eq(0); - expect(artifactsOnSpawn.length).to.eq(1); - }); - - it('should not be able to move more than some max amount of artifacts to a planet', async function () { - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); - - const maxArtifactsOnPlanet = 4; - for (let i = 0; i <= maxArtifactsOnPlanet; i++) { - // place an artifact on the trading post - const newTokenId = hexToBigNumber(i + 1 + ''); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, - rarity: 1, - biome: 1, - artifactType: 5, - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - - // wait for the planet to fill up and download its stats - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); - const tradingPost2Planet = await world.user1Core.planets(LVL3_SPACETIME_1.id); - - if (i > maxArtifactsOnPlanet) { - await expect( - world.user1Core.move( - ...makeMoveArgs( - LVL3_SPACETIME_1, - LVL0_PLANET_DEAD_SPACE, - 0, - tradingPost2Planet.population.toNumber() - 1, - 0, - newTokenId - ) - ) - ).to.be.revertedWith( - 'the planet you are moving an artifact to can have at most 5 artifacts on it' - ); - } else { - // move the artifact from the trading post - await world.user1Core.move( - ...makeMoveArgs( - LVL3_SPACETIME_1, - LVL0_PLANET_DEAD_SPACE, - 0, - tradingPost2Planet.population.toNumber() - 1, - 0, - newTokenId - ) - ); - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(LVL0_PLANET_DEAD_SPACE.id); - const artifactsOnPlanet = await getArtifactsOnPlanet(world, LVL0_PLANET_DEAD_SPACE.id); - expect(artifactsOnPlanet.length).to.eq(i + 1); - } - } - }); - - it("should be able to conquer another player's planet and move their artifact", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // after finding artifact, planet's popCap might get buffed - // so let it fill up again - await increaseBlockchainTime(); - - const artifactPlanetPopCap = ( - await world.contract.planets(ARTIFACT_PLANET_1.id) - ).populationCap.toNumber(); - - await world.user1Core.move( - ...makeMoveArgs( - ARTIFACT_PLANET_1, - SPAWN_PLANET_1, - 10, - Math.floor(artifactPlanetPopCap * 0.999), // if only 0.99 it's still untakeable, bc high def - 0 - ) - ); - - // steal planet - await world.user2Core.move(...makeMoveArgs(SPAWN_PLANET_2, ARTIFACT_PLANET_1, 0, 50000, 0)); - await increaseBlockchainTime(); - - // move artifact - await world.user2Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_2, 0, 50000, 0, newArtifactId) - ); - await increaseBlockchainTime(); - - // verify that artifact was moved - await world.user2Core.withdrawArtifact(LVL3_SPACETIME_2.id, newArtifactId); - const artifacts = await getArtifactsOwnedBy(world.contract, world.user2.address); - - expect(artifacts.length).to.be.equal(1); - }); - - it('not be able to prospect for an artifact on planets that are not ruins', async function () { - await expect(world.user1Core.prospectPlanet(SPAWN_PLANET_1.id)).to.be.revertedWith( - "you can't find an artifact on this planet" - ); - }); - - it('should mint randomly', async function () { - // This can take upwards of 90000ms in CI - this.timeout(0); - - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); - - /* eslint-disable @typescript-eslint/no-explicit-any */ - let artifacts: any; - let prevLocation = SPAWN_PLANET_1; - - for (let i = 0; i < 20; i++) { - // byte #8 is 18_16 = 24_10 so it's a ruins planet - const randomHex = - `00007c2512896efb182d462faee0000fb33d58930eb9e6b4fbae6d048e9c44` + - (i >= 10 ? i.toString()[0] : 0) + - '' + - (i % 10); - - const planetWithArtifactLoc = new TestLocation({ - hex: randomHex, - perlin: SPACE_PERLIN, - distFromOrigin: 1998, - }); - - await world.contract.adminInitializePlanet( - planetWithArtifactLoc.id, - planetWithArtifactLoc.perlin - ); - - await world.contract.adminGiveSpaceShip( - planetWithArtifactLoc.id, - world.user1.address, - ArtifactType.ShipGear - ); - - await increaseBlockchainTime(); - - await world.user1Core.move(...makeMoveArgs(prevLocation, planetWithArtifactLoc, 0, 80000, 0)); // move 80000 from asteroids but 160000 from ruins since ruins are higher level - await increaseBlockchainTime(); - - await world.user1Core.prospectPlanet(planetWithArtifactLoc.id); - await increaseBlockchainTime(); - - await world.user1Core.findArtifact(...makeFindArtifactArgs(planetWithArtifactLoc)); - await increaseBlockchainTime(); - - const artifactsOnPlanet = await getArtifactsOnPlanet(world, planetWithArtifactLoc.id); - const artifactId = artifactsOnPlanet[0].id; - - await world.user1Core.move( - ...makeMoveArgs(planetWithArtifactLoc, LVL3_SPACETIME_1, 0, 40000, 0, artifactId) - ); - await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, artifactId); - artifacts = await getArtifactsOwnedBy(world.contract, world.user1.address); - - expect(artifacts[artifacts.length - 1].planetBiome).to.eq(4); // tundra - expect(artifacts[artifacts.length - 1].discoverer).to.eq(world.user1.address); - expect(artifacts[artifacts.length - 1].rarity).to.be.at.least(1); - - prevLocation = planetWithArtifactLoc; - } - - const artifactTypeSet = new Set(); - - for (let i = 0; i < artifacts.length; i++) { - artifactTypeSet.add(artifacts[i].artifactType); - } - - expect(artifactTypeSet.size).to.be.greaterThan(1); - }); - - it('should not mint an artifact on the same planet twice', async function () { - await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); - await increaseBlockchainTime(); - await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); - await increaseBlockchainTime(); - await expect( - world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) - ).to.be.revertedWith('artifact already minted from this planet'); - }); - - it('should not be able to move an activated artifact', async function () { - const artifactId = await createArtifactOnPlanet( - world.contract, - world.user1.address, - ARTIFACT_PLANET_1, - ArtifactType.Monolith - ); - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); - - await expect( - world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, artifactId) - ) - ).to.be.revertedWith('you cannot take an activated artifact off a planet'); - }); - - it("should not be able to move an artifact from a planet it's not on", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - // after finding artifact, planet's popCap might get buffed - // so let it fill up again - await increaseBlockchainTime(); - - // move artifact - world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) - ); - - // try moving artifact again; should fail - await expect( - world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) - ) - ).to.be.revertedWith('this artifact was not present on this planet'); - - // try moving nonexistent artifact - await expect( - world.user1Core.move(...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, 12345)) - ).to.be.revertedWith('this artifact was not present on this planet'); - }); - - describe('trading post', function () { - it('should be able to withdraw from / deposit onto trading posts you own', async function () { - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_3); - await increaseBlockchainTime(); - - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // move artifact to LVL3_SPACETIME_1 - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) - ); - await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); - - // artifact should be on LVL3_SPACETIME_1 - let artifact = await world.contract.getArtifactById(newArtifactId); - let artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); - let artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifact.locationId).to.eq(LVL3_SPACETIME_1.id); - await expect(artifactsOnTP1.length).to.eq(1); - await expect(artifactsOnTP2.length).to.eq(0); - - // withdraw from LVL3_SPACETIME_1 - await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - - // artifact should be on voyage - artifact = await world.contract.getArtifactById(newArtifactId); - artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); - artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifact.locationId).to.eq(0); - await expect(artifactsOnTP1.length).to.eq(0); - await expect(artifactsOnTP2.length).to.eq(0); - - // deposit onto LVL3_SPACETIME_3 - await world.user1Core.depositArtifact(LVL3_SPACETIME_3.id, newArtifactId); - - // artifact should be on LVL3_SPACETIME_3 - artifact = await world.contract.getArtifactById(newArtifactId); - artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); - artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifact.locationId).to.eq(LVL3_SPACETIME_3.id); - await expect(artifactsOnTP1.length).to.eq(0); - await expect(artifactsOnTP2.length).to.eq(1); - }); - - it("should not be able to withdraw from / deposit onto trading post you don't own", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // move artifact - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) - ); - - // user2 should not be able to withdraw from LVL3_SPACETIME_1 - await expect( - world.user2Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId) - ).to.be.revertedWith('you can only withdraw from a planet you own'); - - // user1 should not be able to deposit onto LVL3_SPACETIME_2 - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - await expect( - world.user1Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) - ).to.be.revertedWith('you can only deposit on a planet you own'); - }); - - it('should not be able to withdraw an artifact from a trading post that is not on the trading post', async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // should not be able to withdraw newArtifactId from LVL3_SPACETIME_1 - await expect( - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId) - ).to.be.revertedWith('this artifact is not on this planet'); - }); - - it('should not be able to withdraw/deposit onto a planet that is not a trading post', async function () { - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL0_PLANET); - await increaseBlockchainTime(); - - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // should not be able to withdraw from ruins (which are not trading posts) - await expect( - world.user2Core.withdrawArtifact(ARTIFACT_PLANET_1.id, newArtifactId) - ).to.be.revertedWith('can only withdraw from trading posts'); - - // move artifact and withdraw - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) - ); - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - - // should not be able to deposit onto LVL0_PLANET (which is regular planet and not trading post) - await expect( - world.user1Core.depositArtifact(LVL0_PLANET.id, newArtifactId) - ).to.be.revertedWith('can only deposit on trading posts'); - }); - - it('should not be able to withdraw/deposit a high level artifact onto low level trading post', async function () { - await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL6_SPACETIME); - await increaseBlockchainTime(); // allow planets to fill up energy again - - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 4, // rarity - biome: 1, // biome - artifactType: 1, - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - // deposit fails on low level trading post, succeeds on high level trading post - await expect( - world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId) - ).to.be.revertedWith('spacetime rip not high enough level to deposit this artifact'); - world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); - - // withdraw fails on low level trading post - await world.user1Core.move( - ...makeMoveArgs(LVL6_SPACETIME, LVL3_SPACETIME_1, 0, 250000000, 0, newTokenId) - ); - await expect( - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newTokenId) - ).to.be.revertedWith('spacetime rip not high enough level to withdraw this artifact'); - - // withdraw succeeds on high level post - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, LVL6_SPACETIME, 0, 500000, 0, newTokenId) - ); - await world.user1Core.withdrawArtifact(LVL6_SPACETIME.id, newTokenId); - }); - }); - - describe('wormhole', function () { - it('should increase movement speed, in both directions', async function () { - // This can take an upwards of 32000ms - this.timeout(0); - - const from = SPAWN_PLANET_1; - const to = LVL0_PLANET; - await conquerUnownedPlanet(world, world.user1Core, from, LVL3_UNOWNED_NEBULA); - await conquerUnownedPlanet(world, world.user1Core, LVL3_UNOWNED_NEBULA, LVL6_SPACETIME); - await conquerUnownedPlanet(world, world.user1Core, from, LVL0_PLANET); - - const dist = 50; - const shipsSent = 10000; - const silverSent = 0; - - const artifactRarities = [1, 2, 3, 4, 5]; // 0 is unknown, so we start at 1 - const wormholeSpeedups = [2, 4, 8, 16, 32]; - - for (let i = 0; i < artifactRarities.length; i++) { - const artifactId = await createArtifactOnPlanet( - world.contract, - world.user1.address, - from, - ArtifactType.Wormhole, - { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } - ); - await world.user1Core.activateArtifact(from.id, artifactId, to.id); - - // move from planet with artifact to its wormhole destination - await increaseBlockchainTime(); - await world.user1Core.move(...makeMoveArgs(from, to, dist, shipsSent, silverSent)); - const fromPlanet = await world.contract.planets(from.id); - const planetArrivals = await world.contract.getPlanetArrivals(to.id); - const arrival = planetArrivals[0]; - const expectedTime = Math.floor( - Math.floor((dist * 100) / wormholeSpeedups[i]) / fromPlanet.speed.toNumber() - ); - - expect(arrival.arrivalTime.sub(arrival.departureTime)).to.be.equal(expectedTime); - - // move from the wormhole destination planet back to the planet whose wormhole is pointing at - // it - await increaseBlockchainTime(); - await world.user1Core.move(...makeMoveArgs(to, from, dist, shipsSent, silverSent)); - const fromPlanetInverted = await world.contract.planets(to.id); - const planetArrivalsInverted = await world.contract.getPlanetArrivals(from.id); - const arrivalInverted = planetArrivalsInverted[0]; - const expectedTimeInverted = Math.floor( - Math.floor((dist * 100) / wormholeSpeedups[i]) / fromPlanetInverted.speed.toNumber() - ); - - expect(arrivalInverted.arrivalTime.sub(arrivalInverted.departureTime)).to.be.equal( - expectedTimeInverted - ); - - await world.user1Core.deactivateArtifact(from.id); - } - }); - - it("shouldn't transfer energy to planets that aren't owned by the sender", async function () { - const from = SPAWN_PLANET_1; - const to = LVL0_PLANET; - - // user 2 takes over a larger planet - await conquerUnownedPlanet(world, world.user2Core, SPAWN_PLANET_2, LVL3_UNOWNED_NEBULA); - - // user 1 takes over the 2nd planet - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, to); - await world.user1Core.refreshPlanet(to.id); - const toPlanet = await world.contract.planets(to.id); - expect(toPlanet.owner).to.eq(world.user1.address); - - // create a wormhole - const newTokenId = hexToBigNumber('5'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, - biome: 1, // biome - artifactType: 5, // wormhole - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - const userArtifacts = await world.contract.getPlayerArtifactIds(world.user1.address); - expect(userArtifacts[0]).to.eq(newTokenId); - - // activate the wormhole to the 2nd planet - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, newTokenId) - ); - await world.user1Core.activateArtifact(from.id, newTokenId, to.id); - - const dist = 50; - const shipsSent = 10000; - const silverSent = 0; - - await increaseBlockchainTime(); - - // user 2 takes over the wormhole's destination - const largePlanet = await world.contract.planets(LVL3_UNOWNED_NEBULA.id); - await world.user2Core.move( - ...makeMoveArgs(LVL3_UNOWNED_NEBULA, to, 10, largePlanet.populationCap.div(2), 0) - ); - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(to.id); - const toPlanetOwnedBySecond = await world.contract.planets(to.id); - expect(toPlanetOwnedBySecond.owner).to.eq(world.user2.address); - - // ok, now for the test: move from the planet with the wormhole to its wormhole target - await increaseBlockchainTime(); - await world.user1Core.move(...makeMoveArgs(from, to, dist, shipsSent, silverSent)); - - // check that the move is sped up - const fromPlanet = await world.contract.planets(from.id); - const planetArrivals = await world.contract.getPlanetArrivals(to.id); - const arrival = planetArrivals[0]; - const expectedTime = Math.floor((Math.floor(dist / 2) * 100) / fromPlanet.speed.toNumber()); - expect(arrival.arrivalTime.sub(arrival.departureTime)).to.be.equal(expectedTime); - - // fast forward to the time that the arrival is scheduled to arrive - const currentTime = await getCurrentTime(); - await increaseBlockchainTime(arrival.arrivalTime.toNumber() - currentTime - 5); - await world.user1Core.refreshPlanet(to.id); - const planetPreArrival = await world.contract.planets(to.id); - const arrivalsPreArrival = await world.contract.getPlanetArrivals(to.id); - - await increaseBlockchainTime(6); - await world.user1Core.refreshPlanet(to.id); - const planetPostArrival = await world.contract.planets(to.id); - const arrivalsPostArrival = await world.contract.getPlanetArrivals(to.id); - - // expect that the arrival transfered precisely zero energy. - expect(planetPreArrival.population).to.eq(planetPostArrival.population); - expect(arrivalsPreArrival.length).to.eq(1); - expect(arrivalsPostArrival.length).to.eq(0); - }); - }); - - describe('bloom filter', function () { - it('is burnt after usage, and should fill energy and silver', async function () { - const from = SPAWN_PLANET_1; - const dist = 50; - const shipsSent = 10000; - const silverSent = 0; - - await world.user1Core.move(...makeMoveArgs(from, LVL0_PLANET, dist, shipsSent, silverSent)); - - const planetBeforeBloomFilter = await world.user1Core.planets(from.id); - expect(planetBeforeBloomFilter.population.toNumber()).to.be.lessThan( - planetBeforeBloomFilter.populationCap.toNumber() - ); - expect(planetBeforeBloomFilter.silver).to.eq(0); - - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 8, // bloom filter - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - await increaseBlockchainTime(); // so that trading post can fill up to max energy - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, newTokenId) - ); - await world.user1Core.activateArtifact(from.id, newTokenId, 0); - - const planetAfterBloomFilter = await world.user1Core.planets(from.id); - expect(planetAfterBloomFilter.population).to.eq(planetAfterBloomFilter.populationCap); - expect(planetAfterBloomFilter.silver).to.eq(planetAfterBloomFilter.silverCap); - - const bloomFilterPostActivation = await world.contract.getArtifactById(newTokenId); - - // bloom filter is immediately deactivated after activation - expect(bloomFilterPostActivation.artifact.lastActivated).to.eq( - bloomFilterPostActivation.artifact.lastDeactivated - ); - - // bloom filter is no longer on a planet (is instead owned by contract), and so is effectively burned - expect(bloomFilterPostActivation.locationId.toString()).to.eq('0'); - }); - - it("can't be used on a planet of too high level", async function () { - this.timeout(1000 * 60); - await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE); - const from = SPAWN_PLANET_1; - - const dist = 50; - const shipsSent = 10000; - const silverSent = 0; - - await world.user1Core.move(...makeMoveArgs(from, LVL0_PLANET, dist, shipsSent, silverSent)); - - const planetBeforeBloomFilter = await world.user1Core.planets(from.id); - expect(planetBeforeBloomFilter.population.toNumber()).to.be.lessThan( - planetBeforeBloomFilter.populationCap.toNumber() - ); - expect(planetBeforeBloomFilter.silver).to.eq(0); - - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 9, // bloom filter - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - await increaseBlockchainTime(); // so that trading post can fill up to max energy - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE, 0, 500000, 0, newTokenId) - ); - await expect( - world.user1Core.activateArtifact(LVL4_UNOWNED_DEEP_SPACE.id, newTokenId, 0) - ).to.be.revertedWith('artifact is not powerful enough to apply effect to this planet level'); - }); - }); - - describe('black domain', function () { - it('is burnt after usage, and prevents moves from being made to it and from it', async function () { - const to = LVL0_PLANET; - const dist = 50; - const shipsSent = 10000; - const silverSent = 0; - - await world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, to, dist, shipsSent, silverSent)); - await increaseBlockchainTime(); - - await world.user1Core.refreshPlanet(to.id); - const conqueredSecondPlanet = await world.user1Core.planets(to.id); - expect(conqueredSecondPlanet.owner).to.eq(world.user1.address); - - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 9, // black domain - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move(...makeMoveArgs(LVL3_SPACETIME_1, to, 0, 500000, 0, newTokenId)); - await world.user1Core.activateArtifact(to.id, newTokenId, 0); - - // black domain is no longer on a planet (is instead owned by contract), and so is effectively burned - const blackDomainPostActivation = await world.contract.getArtifactById(newTokenId); - expect(blackDomainPostActivation.locationId.toString()).to.eq('0'); - - // check the planet is destroyed - const newPlanet = await world.user1Core.planets(to.id); - expect(newPlanet.destroyed).to.eq(true); - - await increaseBlockchainTime(); - - // moves to destroyed planets don't work - await expect( - world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, to, dist, shipsSent, silverSent)) - ).to.be.revertedWith('planet is destroyed'); - - // moves from destroyed planets also don't work - await expect( - world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, to, dist, shipsSent, silverSent)) - ).to.be.revertedWith('planet is destroyed'); - }); - - it("can't be used on a planet of too high level", async function () { - this.timeout(1000 * 60); - await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE); - const from = SPAWN_PLANET_1; - const dist = 50; - const shipsSent = 10000; - const silverSent = 0; - - await world.user1Core.move(...makeMoveArgs(from, LVL0_PLANET, dist, shipsSent, silverSent)); - - const planetBeforeBlackDomain = await world.user1Core.planets(from.id); - expect(planetBeforeBlackDomain.population.toNumber()).to.be.lessThan( - planetBeforeBlackDomain.populationCap.toNumber() - ); - expect(planetBeforeBlackDomain.silver).to.eq(0); - - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 8, // bloom filter - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - await increaseBlockchainTime(); // so that trading post can fill up to max energy - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE, 0, 500000, 0, newTokenId) - ); - await expect( - world.user1Core.activateArtifact(LVL4_UNOWNED_DEEP_SPACE.id, newTokenId, 0) - ).to.be.revertedWith('artifact is not powerful enough to apply effect to this planet level'); - }); - }); - - // TODO: tests for photoid cannon and planetary shield? -}); diff --git a/eth/test/DFMove.test.ts b/eth/test/DFMove.test.ts index e6cd9925..becd5421 100644 --- a/eth/test/DFMove.test.ts +++ b/eth/test/DFMove.test.ts @@ -5,7 +5,7 @@ import { BigNumber } from 'ethers'; import { ethers } from 'hardhat'; import { conquerUnownedPlanet, - createArtifactOnPlanet, + createArtifact, increaseBlockchainTime, makeInitArgs, makeMoveArgs, @@ -28,7 +28,7 @@ import { const { BigNumber: BN } = ethers; -describe('DarkForestMove', function () { +describe.only('DarkForestMove', function () { describe('moving space ships', function () { let world: World; @@ -51,7 +51,7 @@ describe('DarkForestMove', function () { }); it('allows controller to move ships to places they do not own with infinite distance', async function () { - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].artifact; + const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; const shipId = ship.id; await world.user1Core.move( @@ -68,7 +68,7 @@ describe('DarkForestMove', function () { await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL1_ASTEROID_NEBULA); await increaseBlockchainTime(); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].artifact; + const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; const shipId = ship.id; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_NEBULA, 1000, 0, 0, shipId) @@ -83,23 +83,24 @@ describe('DarkForestMove', function () { }); it('should not allow you to move enemy ships on your own planet', async function () { - const ship = (await world.user2Core.getArtifactsOnPlanet(SPAWN_PLANET_2.id))[0].artifact; - const shipId = ship.id; + const user2shipId = (await world.user2Core.getArtifactsOnPlanet(SPAWN_PLANET_2.id))[0].id; await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL2_PLANET_SPACE); await world.user2Core.move( - ...makeMoveArgs(SPAWN_PLANET_2, LVL2_PLANET_SPACE, 1000, 0, 0, shipId) + ...makeMoveArgs(SPAWN_PLANET_2, LVL2_PLANET_SPACE, 1000, 0, 0, user2shipId) ); await increaseBlockchainTime(); await expect( - world.user1Core.move(...makeMoveArgs(LVL2_PLANET_SPACE, SPAWN_PLANET_2, 1000, 0, 0, shipId)) + world.user1Core.move( + ...makeMoveArgs(LVL2_PLANET_SPACE, SPAWN_PLANET_2, 1000, 0, 0, user2shipId) + ) ).to.be.revertedWith('you can only move your own ships'); }); - it('should not consume a photoid if moving a ship off a planet with one activated', async function () { - const artifactId = await createArtifactOnPlanet( + it.skip('should not consume a photoid if moving a ship off a planet with one activated', async function () { + const artifactId = await createArtifact( world.contract, world.user1.address, SPAWN_PLANET_1, @@ -109,8 +110,7 @@ describe('DarkForestMove', function () { await world.user1Core.activateArtifact(SPAWN_PLANET_1.id, artifactId, 0); await increaseBlockchainTime(); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].artifact; - const shipId = ship.id; + const shipId = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].id; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_1, 100, 0, 0, shipId) @@ -118,7 +118,7 @@ describe('DarkForestMove', function () { await world.contract.refreshPlanet(SPAWN_PLANET_1.id); const activePhotoid = (await world.contract.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).filter( - (a) => a.artifact.artifactType === ArtifactType.PhotoidCannon + (a) => a.artifactType === ArtifactType.PhotoidCannon )[0]; // If the photoid is not there, it was used during ship move expect(activePhotoid).to.not.eq(undefined); @@ -921,7 +921,7 @@ describe('move rate limits', function () { ArtifactType.ShipMothership ); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].artifact; + const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 10, 0, 0, ship.id) @@ -934,7 +934,7 @@ describe('move rate limits', function () { ArtifactType.ShipMothership ); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].artifact; + const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; await expect( world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 1000, 0, 0, ship.id)) @@ -960,7 +960,7 @@ describe('move rate limits', function () { ArtifactType.ShipMothership ); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].artifact; + const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 10, 0, 0, ship.id) diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index 3615d698..cfe7e165 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -16,7 +16,7 @@ import { SPAWN_PLANET_2, } from './utils/WorldConstants'; -describe('Space Ships', function () { +describe.only('DarkForestSpaceShips', function () { let world: World; async function worldFixture() { @@ -62,8 +62,8 @@ describe('Space Ships', function () { it('pauses energy regeneration on planets', async function () { const titan = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (a) => a.artifact.artifactType === ArtifactType.ShipTitan - )?.artifact; + (a) => a.artifactType === ArtifactType.ShipTitan + ); // Move Titan to planet await world.user1Core.move( diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index a9f4fdb4..9c4c7d0d 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -36,7 +36,7 @@ import { ZERO_PLANET, } from './utils/WorldConstants'; -describe.only('DarkForestArtifacts', function () { +describe('DarkForestArtifacts', function () { let world: World; async function worldFixture() { diff --git a/eth/test/utils/WorldConstants.ts b/eth/test/utils/WorldConstants.ts index 83174ecd..cb70844d 100644 --- a/eth/test/utils/WorldConstants.ts +++ b/eth/test/utils/WorldConstants.ts @@ -96,10 +96,10 @@ const defaultInitializerValues = { CAPTURE_ZONES_PER_5000_WORLD_RADIUS: 1, SPACESHIPS: { GEAR: true, - MOTHERSHIP: false, - CRESCENT: false, - TITAN: false, - WHALE: false, + MOTHERSHIP: true, + CRESCENT: true, + TITAN: true, + WHALE: true, }, ROUND_END_REWARDS_BY_RANK: [ 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, From cfeea70be0633cac2e94a8dc579cf57bc2c566f6 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Wed, 28 Sep 2022 12:32:05 +0100 Subject: [PATCH 12/55] feat: spaceships work --- eth/contracts/libraries/LibArtifactUtils.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index ff484c46..b101595b 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -62,8 +62,8 @@ library LibArtifactUtils { ArtifactType shipType ) public returns (uint256) { require(shipType <= ArtifactType.ShipTitan && shipType >= ArtifactType.ShipMothership); - - uint256 id = uint256(keccak256(abi.encodePacked(planetId, gs().miscNonce++))); + // require(gs().miscNonce < MAX UINT 128) but won't happen. + uint128 id = uint128(gs().miscNonce++); uint256 tokenId = DFArtifactFacet(address(this)).encodeArtifact( uint8(CollectionType.Spaceship), uint8(ArtifactRarity.Unknown), @@ -72,7 +72,7 @@ library LibArtifactUtils { ); // TODO: Use struct naming convetion for readability DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( - tokenId, + tokenId + id, // makes each spaceship unique but keeps generic properties. msg.sender, planetId, ArtifactRarity.Unknown, From c543123f73aba01986fdead6036ff4eb1e187f95 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Wed, 28 Sep 2022 16:15:07 +0100 Subject: [PATCH 13/55] feat: all tests pass + photoid. Need crescent and planetary shield then clean up then clinet --- eth/contracts/facets/DFGetterFacet.sol | 17 +++ eth/contracts/facets/DFMoveFacet.sol | 53 +++++---- eth/contracts/libraries/LibArtifactUtils.sol | 111 ++++++++----------- eth/contracts/libraries/LibGameUtils.sol | 8 +- eth/contracts/libraries/LibStorage.sol | 3 +- eth/test/DFMove.test.ts | 2 +- eth/test/DFSpaceShips.test.ts | 2 +- eth/test/NewDFArtifacts.test.ts | 64 ++++++++++- eth/test/utils/WorldConstants.ts | 2 +- 9 files changed, 166 insertions(+), 96 deletions(-) diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 008a36ee..38f38fc3 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -316,6 +316,10 @@ contract DFGetterFacet is WithStorage { return ret; } + function getArtifactActivationTimeOnPlanet(uint256 locationId) public view returns (uint256) { + return gs().planetArtifactActivationTime[locationId]; + } + // function getArtifactById(uint256 artifactId) // public // view @@ -356,6 +360,19 @@ contract DFGetterFacet is WithStorage { return ret; } + function artifactExistsOnPlanet(uint256 locationId, uint256 artifactId) + public + view + returns (bool) + { + bool hasArtifact = false; + uint256[] memory artifactIds = gs().planetArtifacts[locationId]; + for (uint256 i = 0; i < artifactIds.length; i++) { + if (artifactIds[i] == artifactId) hasArtifact = true; + } + return hasArtifact; + } + function getActiveArtifactOnPlanet(uint256 locationId) public view diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index b8dbe04f..3f1be14d 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -16,6 +16,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {ArrivalData, ArrivalType, Artifact, ArtifactProperties, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; +import "hardhat/console.sol"; contract DFMoveFacet is WithStorage { modifier notPaused() { @@ -121,11 +122,12 @@ contract DFMoveFacet is WithStorage { if (!_isSpaceshipMove(args)) { // TODO: Add back photoid - // (bool photoidPresent, Upgrade memory newTempUpgrade) = _checkPhotoid(args); - // if (photoidPresent) { - // temporaryUpgrade = newTempUpgrade; - // arrivalType = ArrivalType.Photoid; - // } + (bool photoidPresent, Upgrade memory newTempUpgrade) = _checkPhotoid(args); + if (photoidPresent) { + temporaryUpgrade = newTempUpgrade; + arrivalType = ArrivalType.Photoid; + console.log("doing photoid move"); + } } _removeSpaceshipEffectsFromOriginPlanet(args); @@ -152,6 +154,7 @@ contract DFMoveFacet is WithStorage { _transferPlanetSpaceJunkToPlayer(args); } + // updates oldLoc speed if photoid. LibGameUtils._buffPlanet(args.oldLoc, temporaryUpgrade); uint256 travelTime = effectiveDistTimesHundred / gs().planets[args.oldLoc].speed; @@ -319,22 +322,22 @@ contract DFMoveFacet is WithStorage { the upgrade that should be applied to the origin planet. */ - // function _checkPhotoid(DFPMoveArgs memory args) - // private - // returns (bool photoidPresent, Upgrade memory temporaryUpgrade) - // { - // Artifact memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); - // if ( - // activeArtifactFrom.isInitialized && - // activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && - // block.timestamp - activeArtifactFrom.lastActivated >= - // gameConstants().PHOTOID_ACTIVATION_DELAY - // ) { - // photoidPresent = true; - // LibArtifactUtils.deactivateArtifact(args.oldLoc); - // temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); - // } - // } + function _checkPhotoid(DFPMoveArgs memory args) + private + returns (bool photoidPresent, Upgrade memory temporaryUpgrade) + { + ArtifactProperties memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); + if ( + activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && + block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= + gameConstants().PHOTOID_ACTIVATION_DELAY + ) { + photoidPresent = true; + console.log("photoid present? %s", LibGameUtils.getActiveArtifact(args.oldLoc).id > 0); + LibArtifactUtils.deactivateArtifact(args.oldLoc); + temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); + } + } function _abandonPlanet(DFPMoveArgs memory args) private @@ -440,10 +443,12 @@ contract DFMoveFacet is WithStorage { carriedArtifactId: args.movedArtifactId, distance: args.actualDist }); - - if (args.movedArtifactId != 0) { + // Photoids are burned _checkPhotoid, so don't remove twice + ArtifactProperties memory artifact = DFArtifactFacet(address(this)).decodeArtifact( + args.movedArtifactId + ); + if (args.movedArtifactId != 0 && artifact.artifactType != ArtifactType.PhotoidCannon) { LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); - // gs().artifactIdToVoyageId[args.movedArtifactId] = gs().planetEventsCount; } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index b101595b..ff2f2ec7 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; // External contract imports import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; +import {DFGetterFacet} from "../facets/DFGetterFacet.sol"; // Library imports import {LibGameUtils} from "./LibGameUtils.sol"; @@ -176,43 +177,43 @@ library LibArtifactUtils { // artifact.activations++; } - function activateSpaceshipArtifact( - uint256 locationId, - uint256 artifactId, - Planet storage planet, - Artifact storage artifact - ) private { - if (artifact.artifactType == ArtifactType.ShipCrescent) { - require(artifact.activations == 0, "crescent cannot be activated more than once"); - - require( - planet.planetType != PlanetType.SILVER_MINE, - "cannot turn a silver mine into a silver mine" - ); - - require(planet.owner == address(0), "can only activate crescent on unowned planets"); - require(planet.planetLevel >= 1, "planet level must be more than one"); - - artifact.lastActivated = block.timestamp; - artifact.lastDeactivated = block.timestamp; - - if (planet.silver == 0) { - planet.silver = 1; - Planet memory defaultPlanet = LibGameUtils._defaultPlanet( - locationId, - planet.planetLevel, - PlanetType.SILVER_MINE, - planet.spaceType, - gameConstants().TIME_FACTOR_HUNDREDTHS - ); - - planet.silverGrowth = defaultPlanet.silverGrowth; - } - - planet.planetType = PlanetType.SILVER_MINE; - emit ArtifactActivated(msg.sender, locationId, artifactId); - } - } + // function activateSpaceshipArtifact( + // uint256 locationId, + // uint256 artifactId, + // Planet storage planet, + // ArtifactProperties memory artifact + // ) private { + // if (artifact.artifactType == ArtifactType.ShipCrescent) { + // require(artifact.activations == 0, "crescent cannot be activated more than once"); + + // require( + // planet.planetType != PlanetType.SILVER_MINE, + // "cannot turn a silver mine into a silver mine" + // ); + + // require(planet.owner == address(0), "can only activate crescent on unowned planets"); + // require(planet.planetLevel >= 1, "planet level must be more than one"); + + // artifact.lastActivated = block.timestamp; + // artifact.lastDeactivated = block.timestamp; + + // if (planet.silver == 0) { + // planet.silver = 1; + // Planet memory defaultPlanet = LibGameUtils._defaultPlanet( + // locationId, + // planet.planetLevel, + // PlanetType.SILVER_MINE, + // planet.spaceType, + // gameConstants().TIME_FACTOR_HUNDREDTHS + // ); + + // planet.silverGrowth = defaultPlanet.silverGrowth; + // } + + // planet.planetType = PlanetType.SILVER_MINE; + // emit ArtifactActivated(msg.sender, locationId, artifactId); + // } + // } function activateNonSpaceshipArtifact( uint256 locationId, @@ -240,22 +241,9 @@ library LibArtifactUtils { "this artifact is not on this planet" ); - // Unknown is the 0th one, Monolith is the 1st, and so on. - // TODO v0.6: consider photoid canon - uint256[10] memory artifactCooldownsHours = [uint256(24), 0, 0, 0, 0, 4, 4, 24, 24, 24]; - // TODO: Cooldown is broken - // require( - // artifact.lastDeactivated + - // artifactCooldownsHours[uint256(artifact.artifactType)] * - // 60 * - // 60 < - // block.timestamp, - // "this artifact is on a cooldown" - // ); - bool shouldDeactivateAndBurn = false; - // artifact.lastActivated = block.timestamp; + gs().planetArtifactActivationTime[locationId] = block.timestamp; gs().planetActiveArtifact[locationId] = artifactId; emit ArtifactActivated(msg.sender, locationId, artifactId); @@ -291,19 +279,13 @@ library LibArtifactUtils { if (shouldDeactivateAndBurn) { // artifact.lastDeactivated = block.timestamp; // immediately deactivate - gs().planetActiveArtifact[locationId] = 0; // immediately deactivate + gs().planetActiveArtifact[locationId] = 0; // immediately remove activate artifact - // artifact, owner - // TODO: We aren't updating the artifact beacuse there are no properties to change. - // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); // save artifact state immediately, because _takeArtifactOffPlanet will access pull it from tokens contract emit ArtifactDeactivated(msg.sender, locationId, artifactId); // burn it after use. will be owned by contract but not on a planet anyone can control LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); - } else { - // TODO: We aren't updating the artifact beacuse there are no properties to change. - // artifact, owner - // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); } + console.log("buffing planet"); // this is fine even tho some artifacts are immediately deactivated, because // those artifacts do not buff the planet. @@ -331,15 +313,20 @@ library LibArtifactUtils { // LOL just pretend there is a wormhole. gs().planetWormholes[locationId] = 0; gs().planetActiveArtifact[locationId] = 0; + gs().planetArtifactActivationTime[locationId] = 0; + emit ArtifactDeactivated(msg.sender, artifact.id, locationId); - // TODO: Figure out update artifact - // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); bool shouldBurn = artifact.artifactType == ArtifactType.PlanetaryShield || artifact.artifactType == ArtifactType.PhotoidCannon; if (shouldBurn) { + console.log("burning %s", artifact.id); + console.log( + "artifact is on planet? %s", + DFGetterFacet(address(this)).artifactExistsOnPlanet(locationId, artifact.id) + ); // burn it after use. will be owned by contract but not on a planet anyone can control - LibGameUtils._takeArtifactOffPlanet(artifact.id, locationId); + LibGameUtils._takeArtifactOffPlanet(locationId, artifact.id); } LibGameUtils._debuffPlanet(locationId, LibGameUtils._getUpgradeForArtifact(artifact)); diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index a1e899c9..455964b1 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -237,7 +237,11 @@ library LibGameUtils { }); } - function timeDelayUpgrade(Artifact memory artifact) public pure returns (Upgrade memory) { + function timeDelayUpgrade(ArtifactProperties memory artifact) + public + pure + returns (Upgrade memory) + { if (artifact.artifactType == ArtifactType.PhotoidCannon) { uint256[6] memory range = [uint256(100), 200, 200, 200, 200, 200]; uint256[6] memory speedBoosts = [uint256(100), 500, 1000, 1500, 2000, 2500]; @@ -464,7 +468,7 @@ library LibGameUtils { view returns (ArtifactProperties memory) { - console.log("searching for %s on %s", locationId, artifactId); + console.log("searching for %s on %s", artifactId, locationId); console.log( "%s artifacts on planet %s", gs().planetArtifacts[locationId].length, diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index 3c7b7a90..f6eeb51e 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -51,7 +51,8 @@ struct GameStorage { mapping(uint256 => uint256) planetActiveArtifact; // wormhole from => to. planetWormHoles[from] = to; mapping(uint256 => uint256) planetWormholes; - // spaceShip owners uint256 => address or balanceOf(owner,id) + // planetId to timestamp. For all artifacts, but only used for photoids. + mapping(uint256 => uint256) planetArtifactActivationTime; mapping(uint256 => Artifact) artifacts; // Capture Zones uint256 nextChangeBlock; diff --git a/eth/test/DFMove.test.ts b/eth/test/DFMove.test.ts index becd5421..97b7e7f4 100644 --- a/eth/test/DFMove.test.ts +++ b/eth/test/DFMove.test.ts @@ -28,7 +28,7 @@ import { const { BigNumber: BN } = ethers; -describe.only('DarkForestMove', function () { +describe('DarkForestMove', function () { describe('moving space ships', function () { let world: World; diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index cfe7e165..b40be076 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -16,7 +16,7 @@ import { SPAWN_PLANET_2, } from './utils/WorldConstants'; -describe.only('DarkForestSpaceShips', function () { +describe('DarkForestSpaceShips', function () { let world: World; async function worldFixture() { diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index 9c4c7d0d..a6c2df1b 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -915,9 +915,65 @@ describe('DarkForestArtifacts', function () { // TODO ... }); - describe('photoid cannon', function () { - // TODO ... - }); + describe.only('photoid cannon', function () { + it('activates photoid cannon, increases move speed and rage, then burns photoid', async function () { + const to = LVL0_PLANET; + const dist = 50; + const forces = 500000; + + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.PhotoidCannon, + CollectionType.Artifact, + { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ); + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + + // Confirm photoid cannon is activated. + const activateTx = await world.user1Core.activateArtifact(LVL3_SPACETIME_1.id, newTokenId, 0); + const activateRct = await activateTx.wait(); + const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); + expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( + block.timestamp + ); + expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal( + newTokenId + ); + await increaseBlockchainTime(); + + // Make a move that uses photoid cannon + await world.user1Core.move( + ...makeMoveArgs(LVL3_SPACETIME_1, to, dist, forces, 0, newTokenId) + ); + const fromPlanet = await world.contract.planets(LVL3_SPACETIME_1.id); + const planetArrivals = await world.contract.getPlanetArrivals(to.id); + const arrival = planetArrivals[0]; + const rangeBoosts = [100, 200, 200, 200, 200, 200]; + // Divided by 100 to reflect effect on travel time. + const speedBoosts = [1, 5, 10, 15, 20, 25]; - // TODO: tests for photoid cannon and planetary shield? + const expectedTime = Math.floor( + Math.floor((dist * 100) / speedBoosts[ArtifactRarity.Common]) / fromPlanet.speed.toNumber() + ); + + expect(arrival.arrivalTime.sub(arrival.departureTime).toNumber()).to.be.equal(expectedTime); + + const range = (fromPlanet.range.toNumber() * rangeBoosts[ArtifactRarity.Common]) / 100; + const popCap = fromPlanet.populationCap.toNumber(); + const decayFactor = Math.pow(2, dist / range); + const approxArriving = forces / decayFactor - 0.05 * popCap; + + expect(planetArrivals[0].popArriving.toNumber()).to.be.above(approxArriving - 1000); + expect(planetArrivals[0].popArriving.toNumber()).to.be.below(approxArriving + 1000); + + // Confirm photoid is burned + expect((await getArtifactsOnPlanet(world, LVL3_SPACETIME_1.id)).length).to.equal(0); + expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal(0); + expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( + 0 + ); + }); + }); }); diff --git a/eth/test/utils/WorldConstants.ts b/eth/test/utils/WorldConstants.ts index cb70844d..d211bf42 100644 --- a/eth/test/utils/WorldConstants.ts +++ b/eth/test/utils/WorldConstants.ts @@ -26,7 +26,7 @@ const defaultInitializerValues = { PLANET_LEVEL_THRESHOLDS: [16777216, 4194292, 1048561, 262128, 65520, 16368, 4080, 1008, 240, 48], PLANET_RARITY: 16384, PLANET_TRANSFER_ENABLED: true, - PHOTOID_ACTIVATION_DELAY: 60 * 60 * 12, + PHOTOID_ACTIVATION_DELAY: 60 * 60 * 12, // 12 hours SPAWN_RIM_AREA: 7234560000, LOCATION_REVEAL_COOLDOWN: 60 * 60 * 24, PLANET_TYPE_WEIGHTS: [ From ce8e2dafa35a0ebd009dd0c922b8f38df09a4563 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Wed, 28 Sep 2022 16:34:13 +0100 Subject: [PATCH 14/55] failing test :( --- eth/contracts/facets/DFMoveFacet.sol | 2 +- eth/contracts/libraries/LibGameUtils.sol | 1 + eth/test/NewDFArtifacts.test.ts | 23 ++++++++++++++++++----- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 3f1be14d..f9809433 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -425,7 +425,7 @@ contract DFMoveFacet is WithStorage { planet.populationCap ); bool isSpaceship = LibArtifactUtils.isSpaceship( - gs().artifacts[args.movedArtifactId].artifactType + DFArtifactFacet(address(this)).decodeArtifact(args.movedArtifactId).artifactType ); // space ship moves are implemented as 0-energy moves require(popArriving > 0 || isSpaceship, "Not enough forces to make move"); diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 455964b1..6e902e39 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -413,6 +413,7 @@ library LibGameUtils { function _takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) public { uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; + console.log("%s artifacts on %s", artifactsOnThisPlanet, locationId); bool hadTheArtifact = false; for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index a6c2df1b..f12ecff3 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -36,7 +36,7 @@ import { ZERO_PLANET, } from './utils/WorldConstants'; -describe('DarkForestArtifacts', function () { +describe.only('DarkForestArtifacts', function () { let world: World; async function worldFixture() { @@ -59,7 +59,6 @@ describe('DarkForestArtifacts', function () { const gearShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( (artifact) => artifact.artifactType === ArtifactType.ShipGear ); - console.log(`gearId`, gearShip?.id._hex); const gearId = gearShip?.id; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, ARTIFACT_PLANET_1, 100, 0, 0, gearId) @@ -664,7 +663,7 @@ describe('DarkForestArtifacts', function () { } }); - it("shouldn't transfer energy to planets that aren't owned by the sender", async function () { + it.only("shouldn't transfer energy to planets that aren't owned by the sender", async function () { const from = SPAWN_PLANET_1; const to = LVL0_PLANET; @@ -692,9 +691,23 @@ describe('DarkForestArtifacts', function () { // activate the wormhole to the 2nd planet await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, artifactId); + console.log('her?'); + + // Move gear bc too many artifacts on SPAWN_PLANET_1, so can't receive wormhole. + const gearShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( + (artifact) => artifact.artifactType === ArtifactType.ShipGear + ); + console.log(`gearId`); + await world.user1Core.move( + ...makeMoveArgs(SPAWN_PLANET_1, LVL0_PLANET, 1000, 100, 0, gearShip?.id) + ); + + increaseBlockchainTime(); + await world.user1Core.move( ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, artifactId) ); + await world.user1Core.activateArtifact(from.id, artifactId, to.id); const dist = 50; @@ -779,7 +792,7 @@ describe('DarkForestArtifacts', function () { expect(planetAfterBloomFilter.population).to.eq(planetAfterBloomFilter.populationCap); expect(planetAfterBloomFilter.silver).to.eq(planetAfterBloomFilter.silverCap); - const artifactsOnRipAfterBurn = await world.contract.getArtifactsOnPlanet(SPAWN_PLANET_1.id); + const artifactsOnRipAfterBurn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); // bloom filter is immediately deactivated after activation expect(artifactsOnRipAfterBurn.length).to.equal(0); @@ -915,7 +928,7 @@ describe('DarkForestArtifacts', function () { // TODO ... }); - describe.only('photoid cannon', function () { + describe('photoid cannon', function () { it('activates photoid cannon, increases move speed and rage, then burns photoid', async function () { const to = LVL0_PLANET; const dist = 50; From 01203bb2f5dc981b9f3a64a0888a7cf5b1f6a933 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 11:35:29 +0100 Subject: [PATCH 15/55] feat: crescent test --- eth/contracts/facets/DFMoveFacet.sol | 10 ++ eth/contracts/libraries/LibArtifactUtils.sol | 79 +++++---- eth/contracts/libraries/LibGameUtils.sol | 9 +- eth/test/DFMove.test.ts | 18 +- eth/test/DFSpaceShips.test.ts | 44 ++++- eth/test/NewDFArtifacts.test.ts | 173 +++++++++---------- eth/test/utils/TestUtils.ts | 8 +- 7 files changed, 200 insertions(+), 141 deletions(-) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index f9809433..e6075aea 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -418,6 +418,14 @@ contract DFMoveFacet is WithStorage { function _createArrival(DFPCreateArrivalArgs memory args) private { // enter the arrival data for event id Planet memory planet = gs().planets[args.oldLoc]; + // console.log( + // "pop moved: %s dist %s range %s", + // args.popMoved, + // args.effectiveDistTimesHundred, + // uint256(planet.range) + // ); + // console.logUint(planet.populationCap); + uint256 popArriving = _getDecayedPop( args.popMoved, args.effectiveDistTimesHundred, @@ -427,7 +435,9 @@ contract DFMoveFacet is WithStorage { bool isSpaceship = LibArtifactUtils.isSpaceship( DFArtifactFacet(address(this)).decodeArtifact(args.movedArtifactId).artifactType ); + // console.log("pop arriving: %s from %s", popArriving, args.oldLoc); // space ship moves are implemented as 0-energy moves + console.log("isSpaceship: ", isSpaceship); require(popArriving > 0 || isSpaceship, "Not enough forces to make move"); require(isSpaceship ? args.popMoved == 0 : true, "spaceship moves must be 0 energy moves"); gs().planetArrivals[gs().planetEventsCount] = ArrivalData({ diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index ff2f2ec7..6b12f027 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -169,7 +169,7 @@ library LibArtifactUtils { if (isSpaceship(artifact.artifactType)) { // TODO: fix Crescent functionality - // activateSpaceshipArtifact(locationId, artifactId, planet, artifact); + activateSpaceshipArtifact(locationId, artifactId, planet, artifact); } else { activateNonSpaceshipArtifact(locationId, artifactId, wormholeTo, planet, artifact); } @@ -177,43 +177,46 @@ library LibArtifactUtils { // artifact.activations++; } - // function activateSpaceshipArtifact( - // uint256 locationId, - // uint256 artifactId, - // Planet storage planet, - // ArtifactProperties memory artifact - // ) private { - // if (artifact.artifactType == ArtifactType.ShipCrescent) { - // require(artifact.activations == 0, "crescent cannot be activated more than once"); - - // require( - // planet.planetType != PlanetType.SILVER_MINE, - // "cannot turn a silver mine into a silver mine" - // ); - - // require(planet.owner == address(0), "can only activate crescent on unowned planets"); - // require(planet.planetLevel >= 1, "planet level must be more than one"); - - // artifact.lastActivated = block.timestamp; - // artifact.lastDeactivated = block.timestamp; - - // if (planet.silver == 0) { - // planet.silver = 1; - // Planet memory defaultPlanet = LibGameUtils._defaultPlanet( - // locationId, - // planet.planetLevel, - // PlanetType.SILVER_MINE, - // planet.spaceType, - // gameConstants().TIME_FACTOR_HUNDREDTHS - // ); - - // planet.silverGrowth = defaultPlanet.silverGrowth; - // } - - // planet.planetType = PlanetType.SILVER_MINE; - // emit ArtifactActivated(msg.sender, locationId, artifactId); - // } - // } + function activateSpaceshipArtifact( + uint256 locationId, + uint256 artifactId, + Planet storage planet, + ArtifactProperties memory artifact + ) private { + if (artifact.artifactType == ArtifactType.ShipCrescent) { + // Burn the goddam crescent + // require(artifact.activations == 0, "crescent cannot be activated more than once"); + + require( + planet.planetType != PlanetType.SILVER_MINE, + "cannot turn a silver mine into a silver mine" + ); + + require(planet.owner == address(0), "can only activate crescent on unowned planets"); + require(planet.planetLevel >= 1, "planet level must be more than zero"); + + if (planet.silver == 0) { + planet.silver = 1; + Planet memory defaultPlanet = LibGameUtils._defaultPlanet( + locationId, + planet.planetLevel, + PlanetType.SILVER_MINE, + planet.spaceType, + gameConstants().TIME_FACTOR_HUNDREDTHS + ); + + planet.silverGrowth = defaultPlanet.silverGrowth; + } + + planet.planetType = PlanetType.SILVER_MINE; + emit ArtifactActivated(msg.sender, locationId, artifactId); + + // TODO: Why not actually burn? + // burn it after use. will be owned by contract but not on a planet anyone can control + LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); + emit ArtifactDeactivated(msg.sender, locationId, artifactId); + } + } function activateNonSpaceshipArtifact( uint256 locationId, diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 6e902e39..1cd4259d 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -413,15 +413,12 @@ library LibGameUtils { function _takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) public { uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; + console.log("removing %s from %s", artifactId, locationId); console.log("%s artifacts on %s", artifactsOnThisPlanet, locationId); bool hadTheArtifact = false; for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { - ArtifactProperties memory artifact = DFArtifactFacet(address(this)).getArtifact( - gs().planetArtifacts[locationId][i] - ); - require( !isActivatedERC1155(locationId, artifactId), "you cannot take an activated artifact off a planet" @@ -449,10 +446,10 @@ library LibGameUtils { } function isActivatedERC1155(uint256 locationId, uint256 artifactId) public view returns (bool) { - return (gs().planetActiveArtifact[locationId] > 0); + return (gs().planetActiveArtifact[locationId] == artifactId); } - function isArtifactOnPlanet(uint256 locationId, uint256 artifactId) public returns (bool) { + function isArtifactOnPlanet(uint256 locationId, uint256 artifactId) public view returns (bool) { for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { return true; diff --git a/eth/test/DFMove.test.ts b/eth/test/DFMove.test.ts index 97b7e7f4..bafc5184 100644 --- a/eth/test/DFMove.test.ts +++ b/eth/test/DFMove.test.ts @@ -6,6 +6,7 @@ import { ethers } from 'hardhat'; import { conquerUnownedPlanet, createArtifact, + getArtifactsOnPlanet, increaseBlockchainTime, makeInitArgs, makeMoveArgs, @@ -99,7 +100,7 @@ describe('DarkForestMove', function () { ).to.be.revertedWith('you can only move your own ships'); }); - it.skip('should not consume a photoid if moving a ship off a planet with one activated', async function () { + it('should not consume a photoid if moving a ship off a planet with one activated', async function () { const artifactId = await createArtifact( world.contract, world.user1.address, @@ -108,18 +109,23 @@ describe('DarkForestMove', function () { ); await world.user1Core.activateArtifact(SPAWN_PLANET_1.id, artifactId, 0); + expect((await world.contract.getActiveArtifactOnPlanet(SPAWN_PLANET_1.id)).id).to.equal( + artifactId + ); + await increaseBlockchainTime(); - const shipId = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].id; + const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).filter( + (a) => a.artifactType === ArtifactType.ShipGear + )[0]; + console.log(`gear id`, ship?.id); await world.user1Core.move( - ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_1, 100, 0, 0, shipId) + ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_1, 100, 0, 0, ship?.id) ); await world.contract.refreshPlanet(SPAWN_PLANET_1.id); - const activePhotoid = (await world.contract.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).filter( - (a) => a.artifactType === ArtifactType.PhotoidCannon - )[0]; + const activePhotoid = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); // If the photoid is not there, it was used during ship move expect(activePhotoid).to.not.eq(undefined); }); diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index b40be076..a38eda61 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -1,4 +1,4 @@ -import { ArtifactType } from '@dfdao/types'; +import { ArtifactType, PlanetType } from '@dfdao/types'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { @@ -6,6 +6,7 @@ import { increaseBlockchainTime, makeInitArgs, makeMoveArgs, + prettyPrintToken, } from './utils/TestUtils'; import { defaultWorldFixture, World } from './utils/TestWorld'; import { @@ -106,6 +107,47 @@ describe('DarkForestSpaceShips', function () { }); }); + describe('using the Crescent', function () { + it.only('turns planet into an asteroid and burns crescent', async function () { + const crescent = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( + (a) => a.artifactType === ArtifactType.ShipCrescent + ); + if (!crescent) throw new Error('crescent not found'); + prettyPrintToken(crescent); + + // Move Crescent to planet + await world.user1Core.move( + ...makeMoveArgs(SPAWN_PLANET_1, LVL1_PLANET_DEEP_SPACE, 1000, 0, 0, crescent.id) + ); + + await increaseBlockchainTime(); + await world.contract.refreshPlanet(LVL1_PLANET_DEEP_SPACE.id); + + const crescentNewLocId = ( + await world.contract.getArtifactsOnPlanet(LVL1_PLANET_DEEP_SPACE.id) + )[0].id; + expect(crescentNewLocId).to.equal(crescent?.id); + + const planetBeforeActivate = await world.contract.planets(LVL1_PLANET_DEEP_SPACE.id); + await world.user1Core.activateArtifact(LVL1_PLANET_DEEP_SPACE.id, crescent.id, 0); + const planetAfterActivate = await world.contract.planets(LVL1_PLANET_DEEP_SPACE.id); + // Silver is higher + expect(planetBeforeActivate.silverGrowth).to.be.lessThan(planetAfterActivate.silverGrowth); + // Crescent is no longer on planet. + expect( + (await world.contract.getArtifactsOnPlanet(LVL1_PLANET_DEEP_SPACE.id)).length + ).to.equal(0); + // Planet was planet + expect(planetBeforeActivate.planetType).to.equal(PlanetType.PLANET); + // Planet is now asteroid. + expect(planetAfterActivate.planetType).to.equal(PlanetType.SILVER_MINE); + // Cannot activate again. + await expect( + world.user1Core.activateArtifact(LVL1_PLANET_DEEP_SPACE.id, crescent.id, 0) + ).to.be.revertedWith("can't active an artifact on a planet it's not on"); + }); + }); + describe('spawning on non-home planet', async function () { let world: World; diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index f12ecff3..83a8cdb8 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -1,12 +1,12 @@ import { ArtifactRarity, ArtifactType, Biome, CollectionType } from '@dfdao/types'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { loadFixture, mine } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; -import { BigNumberish } from 'ethers'; import hre from 'hardhat'; import { TestLocation } from './utils/TestLocation'; import { conquerUnownedPlanet, createArtifact, + getArtifactsOnPlanet, getArtifactsOwnedBy, getCurrentTime, getStatSum, @@ -36,7 +36,7 @@ import { ZERO_PLANET, } from './utils/WorldConstants'; -describe.only('DarkForestArtifacts', function () { +describe('DarkForestArtifacts', function () { let world: World; async function worldFixture() { @@ -79,24 +79,24 @@ describe.only('DarkForestArtifacts', function () { world = await loadFixture(worldFixture); }); - // Gets Artifacts but not Spaceships - async function getArtifactsOnPlanet(world: World, locationId: BigNumberish) { - return (await world.contract.getArtifactsOnPlanet(locationId)).filter( - (artifact) => artifact.artifactType < ArtifactType.ShipMothership - ); - } - describe('it tests basic artifact actions', function () { // This test will fail if the artifact is special. it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - const newId = await user1MintArtifactPlanet(world.user1Core); - console.log('new id', newId._hex); - const res = await world.user1Core.decodeArtifact(newId); - prettyPrintToken(res); + const artifactId = await createArtifact( + world.contract, + world.user1.address, + ARTIFACT_PLANET_1, + ArtifactType.Colossus + ); + const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); + + prettyPrintToken(await world.user1Core.decodeArtifact(artifactId)); + // artifact and gear should be on planet. Gear is 0 and Artifact is 1. const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); expect(artifactsOnPlanet.length).to.be.equal(1); @@ -106,23 +106,13 @@ describe.only('DarkForestArtifacts', function () { expect(await world.contract.balanceOf(world.contract.address, a.id)).to.equal(1); }); - // expect(artifactsOnPlanet[0].discoverer).to.eq(world.user1.address); - - // let's update the planet to be one of the basic artifacts, so that - // we know it's definitely going to buff the planet in some way. also, - // this prevents the artifact from being one that requires valid parameter - // in order to activate - // const updatedArtifact = Object.assign({}, artifactsOnPlanet[0]); - // updatedArtifact.artifactType = 0; - // await world.contract.updateArtifact(updatedArtifact); - - // // planet should be buffed after discovered artifact - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[0].id, 0); + // planet should be buffed after discovered artifact const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(ARTIFACT_PLANET_1.id); prettyPrintToken(activeArtifact); + const statSumAfterActivation = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - // // planet buff should be removed after artifact deactivated + // planet buff should be removed after artifact deactivated await world.user1Core.deactivateArtifact(ARTIFACT_PLANET_1.id); const statSumAfterDeactivate = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); @@ -142,9 +132,7 @@ describe.only('DarkForestArtifacts', function () { 'this planet has already been prospected' ); - for (let i = 0; i < 256; i++) { - await increaseBlockchainTime(); - } + await mine(256); await expect( world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) @@ -663,7 +651,7 @@ describe.only('DarkForestArtifacts', function () { } }); - it.only("shouldn't transfer energy to planets that aren't owned by the sender", async function () { + it("shouldn't transfer energy to planets that aren't owned by the sender", async function () { const from = SPAWN_PLANET_1; const to = LVL0_PLANET; @@ -686,23 +674,20 @@ describe.only('DarkForestArtifacts', function () { { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); - const userWormholeBalance = await world.contract.balanceOf(world.user1.address, artifactId); - expect(userWormholeBalance).to.eq(1); - - // activate the wormhole to the 2nd planet - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, artifactId); - console.log('her?'); - // Move gear bc too many artifacts on SPAWN_PLANET_1, so can't receive wormhole. - const gearShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (artifact) => artifact.artifactType === ArtifactType.ShipGear + const crescentShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( + (artifact) => artifact.artifactType === ArtifactType.ShipCrescent ); - console.log(`gearId`); + await world.user1Core.move( - ...makeMoveArgs(SPAWN_PLANET_1, LVL0_PLANET, 1000, 100, 0, gearShip?.id) + ...makeMoveArgs(SPAWN_PLANET_1, LVL0_PLANET, 0, 0, 0, crescentShip?.id) ); - increaseBlockchainTime(); + const userWormholeBalance = await world.contract.balanceOf(world.user1.address, artifactId); + expect(userWormholeBalance).to.eq(1); + + // activate the wormhole to the 2nd planet + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, artifactId); await world.user1Core.move( ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, artifactId) @@ -930,63 +915,73 @@ describe.only('DarkForestArtifacts', function () { describe('photoid cannon', function () { it('activates photoid cannon, increases move speed and rage, then burns photoid', async function () { + await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL6_SPACETIME); + await increaseBlockchainTime(); + const to = LVL0_PLANET; const dist = 50; - const forces = 500000; + const forces = 40000000; // Has to be big to account for - const newTokenId = await createArtifact( - world.contract, - world.user1.address, - ZERO_PLANET, - ArtifactType.PhotoidCannon, - CollectionType.Artifact, - { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } - ); - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - - // Confirm photoid cannon is activated. - const activateTx = await world.user1Core.activateArtifact(LVL3_SPACETIME_1.id, newTokenId, 0); - const activateRct = await activateTx.wait(); - const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); - expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( - block.timestamp - ); - expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal( - newTokenId - ); - await increaseBlockchainTime(); - - // Make a move that uses photoid cannon - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, to, dist, forces, 0, newTokenId) - ); - const fromPlanet = await world.contract.planets(LVL3_SPACETIME_1.id); - const planetArrivals = await world.contract.getPlanetArrivals(to.id); - const arrival = planetArrivals[0]; const rangeBoosts = [100, 200, 200, 200, 200, 200]; // Divided by 100 to reflect effect on travel time. const speedBoosts = [1, 5, 10, 15, 20, 25]; + const artifactRarities = [1, 2, 3, 4, 5]; - const expectedTime = Math.floor( - Math.floor((dist * 100) / speedBoosts[ArtifactRarity.Common]) / fromPlanet.speed.toNumber() - ); + for (let i = 0; i < artifactRarities.length; i++) { + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.PhotoidCannon, + CollectionType.Artifact, + { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } + ); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); - expect(arrival.arrivalTime.sub(arrival.departureTime).toNumber()).to.be.equal(expectedTime); + await world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); - const range = (fromPlanet.range.toNumber() * rangeBoosts[ArtifactRarity.Common]) / 100; - const popCap = fromPlanet.populationCap.toNumber(); - const decayFactor = Math.pow(2, dist / range); - const approxArriving = forces / decayFactor - 0.05 * popCap; + // Confirm photoid cannon is activated. + const activateTx = await world.user1Core.activateArtifact(LVL6_SPACETIME.id, newTokenId, 0); + const activateRct = await activateTx.wait(); + const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); + expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL6_SPACETIME.id)).to.equal( + block.timestamp + ); + expect((await world.user1Core.getActiveArtifactOnPlanet(LVL6_SPACETIME.id)).id).to.equal( + newTokenId + ); - expect(planetArrivals[0].popArriving.toNumber()).to.be.above(approxArriving - 1000); - expect(planetArrivals[0].popArriving.toNumber()).to.be.below(approxArriving + 1000); + await increaseBlockchainTime(); - // Confirm photoid is burned - expect((await getArtifactsOnPlanet(world, LVL3_SPACETIME_1.id)).length).to.equal(0); - expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal(0); - expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( - 0 - ); + // Make a move that uses photoid cannon + await world.user1Core.move( + ...makeMoveArgs(LVL6_SPACETIME, to, dist, forces, 0, newTokenId) + ); + const fromPlanet = await world.contract.planets(LVL6_SPACETIME.id); + const planetArrivals = await world.contract.getPlanetArrivals(to.id); + const arrival = planetArrivals[0]; + + const expectedTime = Math.floor( + Math.floor((dist * 100) / speedBoosts[i + 1]) / fromPlanet.speed.toNumber() + ); + + expect(arrival.arrivalTime.sub(arrival.departureTime).toNumber()).to.be.equal(expectedTime); + + const range = (fromPlanet.range.toNumber() * rangeBoosts[i + 1]) / 100; + const popCap = fromPlanet.populationCap.toNumber(); + const decayFactor = Math.pow(2, dist / range); + const approxArriving = forces / decayFactor - 0.05 * popCap; + + expect(planetArrivals[0].popArriving.toNumber()).to.be.above(approxArriving - 1000); + expect(planetArrivals[0].popArriving.toNumber()).to.be.below(approxArriving + 1000); + + // Confirm photoid is burned + expect((await getArtifactsOnPlanet(world, LVL6_SPACETIME.id)).length).to.equal(0); + expect((await world.user1Core.getActiveArtifactOnPlanet(LVL6_SPACETIME.id)).id).to.equal(0); + expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL6_SPACETIME.id)).to.equal( + 0 + ); + } }); }); }); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index 2788618d..3b109b1c 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -320,6 +320,13 @@ export async function getArtifactsOwnedBy(contract: DarkForest, addr: string) { ); } +// Gets Artifacts but not Spaceships +export async function getArtifactsOnPlanet(world: World, locationId: BigNumberish) { + return (await world.contract.getArtifactsOnPlanet(locationId)).filter( + (artifact) => artifact.artifactType < ArtifactType.ShipMothership + ); +} + export async function createArtifact( contract: DarkForest, owner: string, @@ -332,7 +339,6 @@ export async function createArtifact( biome ||= Biome.FOREST; const tokenId = await contract.encodeArtifact(collectionType, rarity, type, biome); - console.log(`tokenId`, tokenId); await contract.adminGiveArtifact({ tokenId, discoverer: owner, From 7e16067b9f22b6f11caa69e2401a41a1f16ea07c Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 11:48:10 +0100 Subject: [PATCH 16/55] feat: shield test passes --- eth/test/DFSpaceShips.test.ts | 2 +- eth/test/NewDFArtifacts.test.ts | 42 +++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index a38eda61..ccd807bb 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -108,7 +108,7 @@ describe('DarkForestSpaceShips', function () { }); describe('using the Crescent', function () { - it.only('turns planet into an asteroid and burns crescent', async function () { + it('turns planet into an asteroid and burns crescent', async function () { const crescent = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( (a) => a.artifactType === ArtifactType.ShipCrescent ); diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index 83a8cdb8..85643db3 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -910,11 +910,49 @@ describe('DarkForestArtifacts', function () { }); describe('planetary shield', function () { - // TODO ... + it.only('activates planetary shield, + defense, - range, then burns shield', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + LVL3_SPACETIME_1, + ArtifactType.PlanetaryShield, + CollectionType.Artifact, + { rarity: ArtifactRarity.Rare as ArtifactRarity, biome: Biome.OCEAN } + ); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + + const planetBeforeActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); + const activateTx = await world.user1Core.activateArtifact(LVL3_SPACETIME_1.id, newTokenId, 0); + const activateRct = await activateTx.wait(); + const planetAfterActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); + + const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); + expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( + block.timestamp + ); + expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal( + newTokenId + ); + // Boosts are applied + expect(planetBeforeActivation.defense).to.be.lessThan(planetAfterActivation.defense); + expect(planetBeforeActivation.range).to.be.greaterThan(planetAfterActivation.range); + expect(planetBeforeActivation.speed).to.be.greaterThan(planetAfterActivation.speed); + + // Burned on deactivate + await world.user1Core.deactivateArtifact(LVL3_SPACETIME_1.id); + + expect((await getArtifactsOnPlanet(world, LVL3_SPACETIME_1.id)).length).to.equal(0); + expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal(0); + expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( + 0 + ); + }); }); describe('photoid cannon', function () { - it('activates photoid cannon, increases move speed and rage, then burns photoid', async function () { + it('activates photoid cannon, increases move speed and range, then burns photoid', async function () { await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL6_SPACETIME); await increaseBlockchainTime(); From ac286fe27c601e3ff252441c62ddedb99860fe6e Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 12:16:52 +0100 Subject: [PATCH 17/55] fix: clean up tests a bit --- eth/contracts/facets/DFMoveFacet.sol | 2 -- eth/test/NewDFArtifacts.test.ts | 37 ++++++---------------------- eth/test/utils/TestUtils.ts | 22 ++++++++++++++++- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index e6075aea..affe1f66 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -435,9 +435,7 @@ contract DFMoveFacet is WithStorage { bool isSpaceship = LibArtifactUtils.isSpaceship( DFArtifactFacet(address(this)).decodeArtifact(args.movedArtifactId).artifactType ); - // console.log("pop arriving: %s from %s", popArriving, args.oldLoc); // space ship moves are implemented as 0-energy moves - console.log("isSpaceship: ", isSpaceship); require(popArriving > 0 || isSpaceship, "Not enough forces to make move"); require(isSpaceship ? args.popMoved == 0 : true, "spaceship moves must be 0 energy moves"); gs().planetArrivals[gs().planetEventsCount] = ArrivalData({ diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index 85643db3..98bc74a9 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -4,6 +4,7 @@ import { expect } from 'chai'; import hre from 'hardhat'; import { TestLocation } from './utils/TestLocation'; import { + activateAndConfirm, conquerUnownedPlanet, createArtifact, getArtifactsOnPlanet, @@ -16,6 +17,7 @@ import { makeInitArgs, makeMoveArgs, prettyPrintToken, + testDeactivate, user1MintArtifactPlanet, ZERO_ADDRESS, } from './utils/TestUtils'; @@ -602,10 +604,8 @@ describe('DarkForestArtifacts', function () { { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); prettyPrintToken(await world.contract.decodeArtifact(artifactId)); - await world.user1Core.activateArtifact(from.id, artifactId, to.id); - // Confirm womrhole is active - const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(from.id); - expect(activeArtifact.id).to.equal(artifactId); + + activateAndConfirm(world.user1Core, from.id, artifactId, to.id); // move from planet with artifact to its wormhole destination await increaseBlockchainTime(); @@ -853,8 +853,7 @@ describe('DarkForestArtifacts', function () { // black domain is no longer on a planet (is instead owned by contract), and so is effectively // burned - const artifactsOnRipAfterBurn = await world.contract.getArtifactsOnPlanet(to.id); - expect(artifactsOnRipAfterBurn.length).to.equal(0); + testDeactivate(world, to.id); // check the planet is destroyed const newPlanet = await world.user1Core.planets(to.id); @@ -924,17 +923,9 @@ describe('DarkForestArtifacts', function () { prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); const planetBeforeActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); - const activateTx = await world.user1Core.activateArtifact(LVL3_SPACETIME_1.id, newTokenId, 0); - const activateRct = await activateTx.wait(); + await activateAndConfirm(world.user1Core, LVL3_SPACETIME_1.id, newTokenId); const planetAfterActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); - const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); - expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( - block.timestamp - ); - expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal( - newTokenId - ); // Boosts are applied expect(planetBeforeActivation.defense).to.be.lessThan(planetAfterActivation.defense); expect(planetBeforeActivation.range).to.be.greaterThan(planetAfterActivation.range); @@ -979,15 +970,7 @@ describe('DarkForestArtifacts', function () { await world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); // Confirm photoid cannon is activated. - const activateTx = await world.user1Core.activateArtifact(LVL6_SPACETIME.id, newTokenId, 0); - const activateRct = await activateTx.wait(); - const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); - expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL6_SPACETIME.id)).to.equal( - block.timestamp - ); - expect((await world.user1Core.getActiveArtifactOnPlanet(LVL6_SPACETIME.id)).id).to.equal( - newTokenId - ); + await activateAndConfirm(world.user1Core, LVL6_SPACETIME.id, newTokenId); await increaseBlockchainTime(); @@ -1014,11 +997,7 @@ describe('DarkForestArtifacts', function () { expect(planetArrivals[0].popArriving.toNumber()).to.be.below(approxArriving + 1000); // Confirm photoid is burned - expect((await getArtifactsOnPlanet(world, LVL6_SPACETIME.id)).length).to.equal(0); - expect((await world.user1Core.getActiveArtifactOnPlanet(LVL6_SPACETIME.id)).id).to.equal(0); - expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL6_SPACETIME.id)).to.equal( - 0 - ); + await testDeactivate(world, LVL6_SPACETIME.id); } }); }); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index 3b109b1c..d4064da2 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -21,13 +21,14 @@ import { import { bigIntFromKey } from '@dfdao/whitelist'; import { mine, time } from '@nomicfoundation/hardhat-network-helpers'; import bigInt from 'big-integer'; +import { expect } from 'chai'; import { BigNumber, BigNumberish, constants } from 'ethers'; +import hre from 'hardhat'; // @ts-ignore import * as snarkjs from 'snarkjs'; import { TestLocation } from './TestLocation'; import { World } from './TestWorld'; import { ARTIFACT_PLANET_1, initializers, LARGE_INTERVAL } from './WorldConstants'; - const { PLANETHASH_KEY, SPACETYPE_KEY, @@ -352,3 +353,22 @@ export async function createArtifact( return tokenId; } + +export async function testDeactivate(world: World, locationId: BigNumberish) { + expect((await getArtifactsOnPlanet(world, locationId)).length).to.equal(0); + expect((await world.contract.getActiveArtifactOnPlanet(locationId)).id).to.equal(0); + expect(await world.contract.getArtifactActivationTimeOnPlanet(locationId)).to.equal(0); +} + +export async function activateAndConfirm( + contract: DarkForest, + locationId: BigNumber, + tokenId: BigNumberish, + wormHoleTo?: BigNumberish +) { + const activateTx = await contract.activateArtifact(locationId, tokenId, wormHoleTo || 0); + const activateRct = await activateTx.wait(); + const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); + expect(await contract.getArtifactActivationTimeOnPlanet(locationId)).to.equal(block.timestamp); + expect((await contract.getActiveArtifactOnPlanet(locationId)).id).to.equal(tokenId); +} From 0a0e101f74501bb2a437ff9ae7f703f5c33e6239 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 15:23:07 +0100 Subject: [PATCH 18/55] feat: prevent spaceship transfer --- eth/contracts/DFToken.sol | 32 +--- eth/contracts/Tokens.md | 153 +++++++++++++++++++ eth/contracts/facets/DFArtifactFacet.sol | 25 ++- eth/contracts/libraries/LibArtifactUtils.sol | 24 +-- eth/contracts/libraries/LibGameUtils.sol | 6 - eth/test/DFSpaceShips.test.ts | 42 +++++ eth/test/NewDFArtifacts.test.ts | 22 +-- eth/test/utils/TestUtils.ts | 10 ++ 8 files changed, 255 insertions(+), 59 deletions(-) create mode 100644 eth/contracts/Tokens.md diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol index 4b304c67..9cbd5c4c 100644 --- a/eth/contracts/DFToken.sol +++ b/eth/contracts/DFToken.sol @@ -8,15 +8,6 @@ import "hardhat/console.sol"; // Note: We can call _mint and _setTokenUri directly in DFArtifactFacet, but I like having a wrapper // This makes it more obvious when we are using the DFToken functions contract DFToken is SolidStateERC1155 { - function mint( - address account, - uint256 id, - uint256 amount, - bytes memory data - ) public { - _mint(account, id, amount, data); - } - /** * @notice set per-token metadata URI * @param tokenId token whose metadata URI to set @@ -55,7 +46,7 @@ contract DFToken is SolidStateERC1155 { uint256 _rarity, uint256 _artifactType, uint256 _biome - ) public view returns (uint256) { + ) public pure returns (uint256) { uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); @@ -63,18 +54,11 @@ contract DFToken is SolidStateERC1155 { return collectionType + rarity + artifactType + biome; } - // Collection Type should be Spaceship. ArtifactType should be type of ship. - function encodeSpaceship(uint256 _collectionType, uint256 _artifactType) - public - view - returns (uint256) - { - uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); - uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); - return collectionType + artifactType; - } - - function decodeArtifact(uint256 artifactId) public view returns (ArtifactProperties memory) { + /** + * @notice Fetch the ArtifactProperties for the given id + * @param artifactId type of artifact + */ + function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { bytes memory _b = abi.encodePacked(artifactId); // 0 is left most element. 0 is given the property Unknown in TokenInfo. @@ -87,10 +71,6 @@ contract DFToken is SolidStateERC1155 { uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); - // console.log("collectionType %s", collectionType); - // console.log("rarity %s", rarity); - // console.log("artifactType %s", artifactType); - // console.log("biome %s", biome); ArtifactProperties memory a = ArtifactProperties({ id: artifactId, diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md new file mode 100644 index 00000000..d24c15a9 --- /dev/null +++ b/eth/contracts/Tokens.md @@ -0,0 +1,153 @@ +# Dark Forest Tokens (ERC1155): + +Each token in Dark Forest is a collection under the ERC1155 Standard. + +Each collection has a `tokenId` can be fungible (supply > 1) or non-fungible (supply = 1). + +The fundamental data structure in ERC1155 is `mapping(uint256 => mapping(address => uint256)) balances;`. + +`balances[tokenId][myAddress]` = number of tokens I have of a given collection. + +The `uint 256 tokenId`, which identifies a _set_ of tokens, is represented in the following way: + +Each `uint256 tokenId` is broken down into 32 chunks of 8 bits each (32\*8 = 256). + +> | chunk1 | chunk 2 | ... chunk32 |. + +Chunks are used from left to right, so a token that has a value of `0xff` in chunk1 looks like`0xff00000000000000000000000000000000000000000000000000000000000000` + +Another way to visualize the `tokenId` is by highlighting each chunk: `0x**ff**_00_**00**_00_**00**_00_...` + +Each chunk allows for 2^8 (256) unique pieces of information. If you need more than 256 properties of a token, you can use an additional chunk + +This architecture allows us to encode information about a Dark Forest token in the `tokenId` itself, +and, more importantly, it allows to create a copy of a token just by using the same `tokenId`. + +This concept will become clearer as we examine how these rules are used for Artifacts and +Spaceships. + +In `DFTypes.sol`, the `TokenInfo` enum looks like this: + +```js +enum TokenInfo { + Unknown, + CollectionType, + ArtifactRarity, + ArtifactType, + Biome +} +``` + +Each index in `TokenInfo` refers to a chunk in the `tokenId`. + +> | CollectionType | ArtifactRarity | ArtifactType | Biome | chunk5 | ... chunk 32 | + +Each of these chunks has 256 options. So you can have 256 Artifact Rarities, Types, Biomes etc... +This should be plenty, but if you need more you just use another chunk. + +## Artifacts + +In Dark Forest, artifacts are fungible. If I have two Epic Monoliths found in the Ocean biome, I can +sell either one and they have the same buffs for planets. + +Thus, we can represent in this information with the following encoding: + +> | CollectionType.Artifact | ArtifactRarity.Epic | ArtifactType.Monolith | Biome.Ocean | ... | + +In hex: + +> | 0x01 | 0x03| 0x01 | 0x01 | ... + +Under the hood, we simply calculate the value of the given property (Epic Artifact = 3), convert it +to hex (0x03), and place it in the appropriate location (TokenInfo.ArtifactRarity = 2, so place 0x03 +two chunks from the left). + +To decode, we just reverse this process. Given the id `0x010301010000.....`, I know that it +is an Epic Monoliths Artifact from the Ocean Biome. + +You can see the actual encoding and decoding take place in `DFToken.sol/encodeArtifact` and +`DFToken.sol/decode Artifact`. + +### Minting + +In Dark Forest, when Artifacts are minted, they are placed on the planet they were found on, _but +they are still owned by the core game contract_. From the eyes of the game contract, it will just +own bunch of Epic Monoliths. However, we have an additional data structure, +`mapping(uint256 =>uint256[]) planetArtifacts;` in the contract storage that keeps track of which +artifacts are on which planet. + +If you own the planet that contains an Artifact, you can move that Artifact. This means that players +can lose access to their artifacts if the planet it is on gets captured. + +### Withdrawing + +For players to gain ownership over their Aritifacts, they must withdraw them from a Spacetime Rip. + +Under the hood, this decreases the number of Epic Monoliths owned by the core game contract by 1 and +increases the number of Epic Monoliths owned by the player by 1. It also removes _one_ instance of +the Epic Monolith tokenId from the `planetArtifacts` mapping. + +Players can now transfer or burn the Artifact. + +### Transferring + +ERC1155 Transfers function just like ERC721 transfers, but can also specify an amount of tokens to +transfer. + +If I wanted to send _two_ Epic Monoliths to a friend, I would call the following: +`safeTransferFrom(me,myFriend,epicMonolithId, 2, "")` + +## Spaceships + +Spaceships are very similar to Artifacts, in that they give special powers to planets. + +Spaceships are _non-fungible_. If I have two Epic Monoliths on a planet and you capture the +planet, you control the Monoliths. However, if I have my Mothership on your planet and you have your Mothership on +your planet, you cannot control my Mothership. This means that the `planetArtifacts` mapping above +will fail to enforce this primary rule of Spaceships. To fix this, each Spaceship must have a unique +id and be owned by the account that minted it. + +However, we can still use our `tokenId` chunk system because we don't need all 256 bits to uniquely +identify a Spaceship. We limit Spaceships to the first 16 chunks (128 bits) and save 128 for a +unique id. This uses the [Split-Id](https://eips.ethereum.org/EIPS/eip-1155#split-id-bits) method +recommended in the ERC1155 Proposal. + +A Mothership Spaceship is represented like so + +> | CollectionType.Spaceship | ArtifactRarity.Unknown | ArtifactType.ShipMothership | Biome.Unknown +> | ... chunk 16 | uniqueId (16 chunks) | + +In hex: + +> | 0x02 | 0x00 | 0x0a | 0x00 | ... | uniqueId (16 chunks) | + +A Spaceship's tokenId = `uint128 tokenInfo` + `uint128 uniqueId` + +### Minting + +When a user creates or mints a Spaceship, they own it, not the contract. + +Lets say my Mothership has id `<0x02000a00><0x01>`. + +`balances[0x02000a00...01][myAddress] = 1`. + +If Velorum mints their own Mothership, it would have id: `<0x02000a00><0x02>`. + +`balances[0x02000a00...01][myAddress] = 1`. + +Velorum's Mothership has the same TokenInfo, but a unique identifier at the end. This means the +contract stores my Mothership and Velorum's Mothership as completely different collections. +However, because our ships share the same first 128 bits, we can still calculate the information +about the Spaceship (ArtifactType, CollectionType) just by feeding the `decodeArtifact` function the `tokenId`. + +### Transferring + +Right now, Spaceships cannot be transferred. This means players cannot sell their powerful ships. +This is implemented using the `beforeTokenTransfer` hook in ERC1155, which only lets the core +contract transfer Spaceships. This is used when minting a ship. + +We could easily turn off this check if we wanted players to be able to buy and sell Spaceships. + +## Activating + +## Deactiving diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 0b2bc6ad..43b74dfd 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.0; // Contract imports +import {ERC1155BaseInternal} from "@solidstate/contracts/token/ERC1155/base/ERC1155Base.sol"; +import {ERC1155EnumerableInternal} from "@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableInternal.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; import {DFToken} from "../DFToken.sol"; @@ -17,6 +19,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; + import "hardhat/console.sol"; contract DFArtifactFacet is WithStorage, DFToken { @@ -113,6 +116,8 @@ contract DFArtifactFacet is WithStorage, DFToken { // return results; // } + // This calls the low level _transfer call which doesn't check if the msg.sender actually owns + // the tokenId. TODO: See if this is a problem. function transferArtifact( uint256 tokenId, address owner, @@ -122,7 +127,7 @@ contract DFArtifactFacet is WithStorage, DFToken { // account, id, amount. _burn(owner, tokenId, 1); } else { - // operator sender receiver id amount data + // sender receiver id amount data _transfer(owner, owner, newOwner, tokenId, 1, ""); } } @@ -291,4 +296,22 @@ contract DFArtifactFacet is WithStorage, DFToken { gs().players[msg.sender].claimedShips = true; } + + function _beforeTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual override { + uint256 length = ids.length; + for (uint256 i = 0; i < length; i++) { + ArtifactProperties memory artifact = decodeArtifact(ids[i]); + // Only core contract can transfer Spaceships + if (LibArtifactUtils.isSpaceship(artifact.artifactType)) { + require(msg.sender == gs().diamondAddress, "player cannot transfer a Spaceship"); + } + } + } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 6b12f027..1d9b1df0 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -71,18 +71,18 @@ library LibArtifactUtils { uint8(shipType), uint8(Biome.Unknown) ); - // TODO: Use struct naming convetion for readability - DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( - tokenId + id, // makes each spaceship unique but keeps generic properties. - msg.sender, - planetId, - ArtifactRarity.Unknown, - Biome.Unknown, - shipType, - owner, // Player is owner of new ship - owner - ); - + // TODO: Use struct naming convention for readability + DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs({ + tokenId: tokenId + id, + discoverer: msg.sender, + planetId: planetId, + rarity: ArtifactRarity.Unknown, + biome: Biome.Unknown, + artifactType: shipType, + owner: owner, + // Only used for spaceships + controller: owner + }); Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( createArtifactArgs ); diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 1cd4259d..3b762cbe 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -467,13 +467,7 @@ library LibGameUtils { returns (ArtifactProperties memory) { console.log("searching for %s on %s", artifactId, locationId); - console.log( - "%s artifacts on planet %s", - gs().planetArtifacts[locationId].length, - locationId - ); for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - console.log("found %s ", gs().planetArtifacts[locationId][i]); if (gs().planetArtifacts[locationId][i] == artifactId) { return DFArtifactFacet(address(this)).getArtifact(artifactId); } diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index ccd807bb..62b9514d 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -3,6 +3,7 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { conquerUnownedPlanet, + getArtifactOnPlanetByType, increaseBlockchainTime, makeInitArgs, makeMoveArgs, @@ -58,6 +59,47 @@ describe('DarkForestSpaceShips', function () { }); }); + describe.only('ship transfers', function () { + it('cannot transfer your own spaceship', async function () { + const motherShip = await getArtifactOnPlanetByType( + world.contract, + SPAWN_PLANET_1.id, + ArtifactType.ShipMothership + ); + // Player owns ship. + expect(await world.contract.balanceOf(world.user1.address, motherShip.id)).to.equal(1); + await expect( + world.user1Core.safeTransferFrom( + world.user1.address, + world.user2.address, + motherShip.id, + 1, + '0x00' + ) + ).to.be.revertedWith('player cannot transfer a Spaceship'); + }); + it('cannot transfer other players spaceship', async function () { + await world.user2Core.giveSpaceShips(SPAWN_PLANET_2.id); + + const motherShip = await getArtifactOnPlanetByType( + world.contract, + SPAWN_PLANET_2.id, + ArtifactType.ShipMothership + ); + // Other Player owns ship. + expect(await world.contract.balanceOf(world.user2.address, motherShip.id)).to.equal(1); + await expect( + world.user1Core.safeTransferFrom( + world.user2.address, + world.user1.address, + motherShip.id, + 1, + '0x00' + ) + ).to.be.revertedWith('ERC1155: caller is not owner nor approved'); + }); + }); + describe('using the Titan', async function () { this.timeout(0); diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index 98bc74a9..7117cae6 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -11,7 +11,6 @@ import { getArtifactsOwnedBy, getCurrentTime, getStatSum, - hexToBigNumber, increaseBlockchainTime, makeFindArtifactArgs, makeInitArgs, @@ -19,7 +18,6 @@ import { prettyPrintToken, testDeactivate, user1MintArtifactPlanet, - ZERO_ADDRESS, } from './utils/TestUtils'; import { defaultWorldFixture, World } from './utils/TestWorld'; import { @@ -195,6 +193,7 @@ describe('DarkForestArtifacts', function () { ); const moveReceipt = await moveTx.wait(); const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued + await world.contract.refreshPlanet(ARTIFACT_PLANET_1.id); const oldLocArtifacts = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); expect(oldLocArtifacts.length).to.equal(0); // confirming that artifact is on a voyage by checking that its no longer at the old @@ -226,17 +225,12 @@ describe('DarkForestArtifacts', function () { const maxArtifactsOnPlanet = 4; for (let i = 0; i <= maxArtifactsOnPlanet; i++) { // place an artifact on the trading post - const newTokenId = hexToBigNumber(i + 1 + ''); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, - rarity: 1, - biome: 1, - artifactType: 5, - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.Monolith + ); await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); // wait for the planet to fill up and download its stats @@ -909,7 +903,7 @@ describe('DarkForestArtifacts', function () { }); describe('planetary shield', function () { - it.only('activates planetary shield, + defense, - range, then burns shield', async function () { + it('activates planetary shield, + defense, - range, then burns shield', async function () { await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); const newTokenId = await createArtifact( diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index d4064da2..565df44c 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -372,3 +372,13 @@ export async function activateAndConfirm( expect(await contract.getArtifactActivationTimeOnPlanet(locationId)).to.equal(block.timestamp); expect((await contract.getActiveArtifactOnPlanet(locationId)).id).to.equal(tokenId); } + +export async function getArtifactOnPlanetByType( + contract: DarkForest, + locationId: BigNumber, + artifactType: ArtifactType +) { + return (await contract.getArtifactsOnPlanet(locationId)).filter( + (artifact) => (artifact.artifactType as ArtifactType) === artifactType + )[0]; +} From 79edaaf7133ee3ce60ed2ce3c495f67a8a541646 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 15:34:51 +0100 Subject: [PATCH 19/55] feat: bye bye DFToken --- eth/contracts/DFToken.sol | 85 ----------------------- eth/contracts/Tokens.md | 9 +++ eth/contracts/facets/DFArtifactFacet.sol | 87 ++++++++++++++++++++++-- eth/test/NewDFArtifacts.test.ts | 30 +++++++- 4 files changed, 118 insertions(+), 93 deletions(-) delete mode 100644 eth/contracts/DFToken.sol diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol deleted file mode 100644 index 9cbd5c4c..00000000 --- a/eth/contracts/DFToken.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.0; - -import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; -import {ArtifactProperties, TokenInfo, CollectionType, ArtifactType, ArtifactRarity, Biome} from "./DFTypes.sol"; -import "hardhat/console.sol"; - -// Note: We can call _mint and _setTokenUri directly in DFArtifactFacet, but I like having a wrapper -// This makes it more obvious when we are using the DFToken functions -contract DFToken is SolidStateERC1155 { - /** - * @notice set per-token metadata URI - * @param tokenId token whose metadata URI to set - * @param tokenURI per-token URI - */ - function setTokenURI(uint256 tokenId, string memory tokenURI) public { - _setTokenURI(tokenId, tokenURI); - } - - /** - * @notice calculate amount of bits to shift left - * @param index number of 1 byte words to shift from left - * @return shift length of left shift - */ - function calcBitShift(uint8 index) internal pure returns (uint8) { - uint8 maxVal = 32; - - require(index <= maxVal, "shift index is too high"); - require(index > 0, "shift index is too low"); - - uint256 bin = 8; - uint256 shift = 256; - return uint8(shift - (bin * index)); - } - - /** - * @notice Create the collection ID for a given artifact - * @param _collectionType type of artifact - * @param _rarity rarity of artifact - * @param _artifactType of artifact - * @param _biome of artifact - * @notice this is not a struct because I can't figure out how to bit shift a uint in a struct. - */ - function encodeArtifact( - uint256 _collectionType, - uint256 _rarity, - uint256 _artifactType, - uint256 _biome - ) public pure returns (uint256) { - uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); - uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); - uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); - uint256 biome = _biome << calcBitShift(uint8(TokenInfo.Biome)); - return collectionType + rarity + artifactType + biome; - } - - /** - * @notice Fetch the ArtifactProperties for the given id - * @param artifactId type of artifact - */ - function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { - bytes memory _b = abi.encodePacked(artifactId); - // 0 is left most element. 0 is given the property Unknown in TokenInfo. - - // Note: Bit shifting requires an index greater than zero. This is why the TokenInfo has - // Unknown as the zero property, so calcBitShift(TokenInfo.Level) is correct. - // As a consequence, we need to - // offset fetching the relevant byte from the artifactId by 1. - // However - uint8 collectionType = uint8(_b[uint8(TokenInfo.CollectionType) - 1]); - uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); - uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); - uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); - - ArtifactProperties memory a = ArtifactProperties({ - id: artifactId, - collectionType: CollectionType(collectionType), - rarity: ArtifactRarity(rarity), - artifactType: ArtifactType(artifactType), - planetBiome: Biome(biome) - }); - - return a; - } -} diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index d24c15a9..8dcb27bd 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -150,4 +150,13 @@ We could easily turn off this check if we wanted players to be able to buy and s ## Activating +Every Artifact and one Spaceship (Crescent) must be activated on a planet to be used. + ## Deactiving + +# Next Steps + +## Silver + +We can give silver a `tokenId` as well. This would be useful it silver was a fungible resource that +could be used to buy multiple things in game. Easy enough to make it non-transferrable as well. diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 43b74dfd..b5d789fa 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.0; // Contract imports -import {ERC1155BaseInternal} from "@solidstate/contracts/token/ERC1155/base/ERC1155Base.sol"; -import {ERC1155EnumerableInternal} from "@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableInternal.sol"; +import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; import {DFToken} from "../DFToken.sol"; @@ -18,11 +17,11 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; +import {Artifact, ArtifactRarity, ArtifactType, Biome, CollectionType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties, TokenInfo} from "../DFTypes.sol"; import "hardhat/console.sol"; -contract DFArtifactFacet is WithStorage, DFToken { +contract DFArtifactFacet is WithStorage, SolidStateERC1155 { event PlanetProspected(address player, uint256 loc); event ArtifactFound(address player, uint256 artifactId, uint256 loc); event ArtifactDeposited(address player, uint256 artifactId, uint256 loc); @@ -96,9 +95,8 @@ contract DFArtifactFacet is WithStorage, DFToken { return newArtifact; } - function getArtifact(uint256 tokenId) public view returns (ArtifactProperties memory) { - return DFToken.decodeArtifact(tokenId); - //return gs().artifacts[tokenId]; + function getArtifact(uint256 tokenId) public pure returns (ArtifactProperties memory) { + return decodeArtifact(tokenId); } // function getArtifactAtIndex(uint256 idx) public view returns (Artifact memory) { @@ -314,4 +312,79 @@ contract DFArtifactFacet is WithStorage, DFToken { } } } + + /** + * @notice set per-token metadata URI + * @param tokenId token whose metadata URI to set + * @param tokenURI per-token URI + */ + function setTokenURI(uint256 tokenId, string memory tokenURI) public { + _setTokenURI(tokenId, tokenURI); + } + + /** + * @notice calculate amount of bits to shift left + * @param index number of 1 byte words to shift from left + * @return shift length of left shift + */ + function calcBitShift(uint8 index) internal pure returns (uint8) { + uint8 maxVal = 32; + + require(index <= maxVal, "shift index is too high"); + require(index > 0, "shift index is too low"); + + uint256 bin = 8; + uint256 shift = 256; + return uint8(shift - (bin * index)); + } + + /** + * @notice Create the collection ID for a given artifact + * @param _collectionType type of artifact + * @param _rarity rarity of artifact + * @param _artifactType of artifact + * @param _biome of artifact + * @notice this is not a struct because I can't figure out how to bit shift a uint in a struct. + */ + function encodeArtifact( + uint256 _collectionType, + uint256 _rarity, + uint256 _artifactType, + uint256 _biome + ) public pure returns (uint256) { + uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); + uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); + uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); + uint256 biome = _biome << calcBitShift(uint8(TokenInfo.Biome)); + return collectionType + rarity + artifactType + biome; + } + + /** + * @notice Fetch the ArtifactProperties for the given id + * @param artifactId type of artifact + */ + function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { + bytes memory _b = abi.encodePacked(artifactId); + // 0 is left most element. 0 is given the property Unknown in TokenInfo. + + // Note: Bit shifting requires an index greater than zero. This is why the TokenInfo has + // Unknown as the zero property, so calcBitShift(TokenInfo.Level) is correct. + // As a consequence, we need to + // offset fetching the relevant byte from the artifactId by 1. + // However + uint8 collectionType = uint8(_b[uint8(TokenInfo.CollectionType) - 1]); + uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); + uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); + uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); + + ArtifactProperties memory a = ArtifactProperties({ + id: artifactId, + collectionType: CollectionType(collectionType), + rarity: ArtifactRarity(rarity), + artifactType: ArtifactType(artifactType), + planetBiome: Biome(biome) + }); + + return a; + } } diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index 7117cae6..5d587dd4 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -79,7 +79,35 @@ describe('DarkForestArtifacts', function () { world = await loadFixture(worldFixture); }); - describe('it tests basic artifact actions', function () { + describe.only('it tests basic artifact actions', function () { + it('logs bits for artifact', async function () { + // Must be valid options + const _collectionType = '0x01'; + const _rarity = ArtifactRarity.Legendary; + const _artifactType = ArtifactType.Colossus; + const _biome = Biome.DESERT; + const res = await world.contract.encodeArtifact( + _collectionType, + _rarity, + _artifactType, + _biome + ); + const { collectionType, rarity, planetBiome, artifactType } = + await world.contract.decodeArtifact(res); + expect(collectionType).to.equal(Number(_collectionType)); + expect(rarity).to.equal(Number(_rarity)); + expect(planetBiome).to.equal(Number(_biome)); + expect(artifactType).to.equal(Number(_artifactType)); + }); + it('logs bits for spaceship', async function () { + // Must be valid options + const _collectionType = '0x02'; // TODO: add CollectionType to @dfdao/types + const _artifactType = ArtifactType.ShipGear; + const res = await world.contract.encodeArtifact(_collectionType, 0, _artifactType, 0); + const { collectionType, artifactType } = await world.contract.decodeArtifact(res); + expect(collectionType).to.equal(Number(_collectionType)); + expect(artifactType).to.equal(Number(_artifactType)); + }); // This test will fail if the artifact is special. it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); From b502961d8488cefa12cf8d51d68ce95e97caaa2d Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 15:50:58 +0100 Subject: [PATCH 20/55] feat: Readme --- eth/contracts/Tokens.md | 51 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index 8dcb27bd..18f077c3 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -121,7 +121,7 @@ In hex: > | 0x02 | 0x00 | 0x0a | 0x00 | ... | uniqueId (16 chunks) | -A Spaceship's tokenId = `uint128 tokenInfo` + `uint128 uniqueId` +A Spaceship's tokenId = `` ### Minting @@ -152,7 +152,54 @@ We could easily turn off this check if we wanted players to be able to buy and s Every Artifact and one Spaceship (Crescent) must be activated on a planet to be used. -## Deactiving +The fungible nature of Artifacts creates a challenge: How do we associate data with specific artifacts? How do I know when my +Epic Monolith is activated? + +There are new data structures in `LibStorage.sol` to handle this information. Because Artifact +activations are always associated with planets, we can store the needed information on the relevant planets +instead of on the Artifacts themselves. + +```js + mapping(uint256 => uint256[]) planetArtifacts; + mapping(uint256 => uint256) planetActiveArtifact; + mapping(uint256 => uint256) planetWormholes; + mapping(uint256 => uint256) planetArtifactActivationTime; +``` + +Lets say I move my Epic Monolith with id `0xMonolith` to planet A with id `0xA`. +Now `planetArtifacts[0xA]` includes `0xMonolith`. +Now, I activate my Monolith. The following occurs: + +- `planetActiveArtifact[0xA] = 0xMonolith`. +- `planetArtifactActivationTime[0xA] = block.timestamp` + +If I had a Wormhole instead of a Monolith, I would also update `planetWormholes`. Lets say I want a +wormhole from `0xA` to `0xB`. + +- `planetWormholes[0xA] = 0xB` + +## Deactivating + +If I deactivate my artifact from `0xA`, we simply undo these maneuvers: + +- `planetActiveArtifact[0xA] = 0`. +- `planetArtifactActivationTime[0xA] = 0` + +For the wormhole, we do the same: + +- `planetWormholes[0xA] = 0` + +## Simulteanous Activate and Deactivate + +Some Bloom Filters (Artifacts) and Crescents (Spaceships) are burned on use. + +All we do is make sure that we call the Activate and Deactivate functions in the same transaction. + +## Photoid + +For Photoid cannons, we simply apply the Photoid move if the current time is greater than the time +activated + the Photoid activation delay. If someone captures the planet, the activation time +doesn't change. # Next Steps From 97035ee32b0901250729d3f63526f2b2da51e139 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 15:51:23 +0100 Subject: [PATCH 21/55] clean --- eth/test/DFERC1155.test.ts | 71 -------------------------------------- 1 file changed, 71 deletions(-) delete mode 100644 eth/test/DFERC1155.test.ts diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts deleted file mode 100644 index d95344cb..00000000 --- a/eth/test/DFERC1155.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { DFToken, DFToken__factory } from '@dfdao/contracts/typechain'; -import { ArtifactRarity, ArtifactType, Biome } from '@dfdao/types'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { expect } from 'chai'; -import { ethers } from 'hardhat'; - -/** - * Once again, a friendly reminder that in ERC1155 tokenId refers to a collection of 1 or more - * tokens, NOT guaranteed to be unique instances of tokens. - */ - -describe('SolidStateERC1155', function () { - let token: DFToken; - let deployer: SignerWithAddress; - let user1: SignerWithAddress; - const addressZero = ethers.constants.AddressZero; - const collectionId = ethers.constants.Zero; - const amount = ethers.constants.Two; - const tokenURI = 'ERC1155Metadata.tokenURI/{id}.json'; - - beforeEach(async function () { - const signers = await ethers.getSigners(); - deployer = signers[0]; - token = await new DFToken__factory(deployer).deploy(); - user1 = signers[1]; - }); - - it('mints tokens for tokenId zero', async function () { - await expect(token.mint(user1.address, collectionId, amount, ethers.constants.HashZero)) - .to.emit(token, 'TransferSingle') - .withArgs(deployer.address, addressZero, user1.address, collectionId, amount); - - await expect( - token.connect(user1).mint(user1.address, collectionId, amount, ethers.constants.HashZero) - ) - .to.emit(token, 'TransferSingle') - .withArgs(user1.address, addressZero, user1.address, collectionId, amount); - - // Each mint created 2 tokens in the same collection. Two mints = 4 tokens. - expect(await token.balanceOf(user1.address, collectionId)).to.equal(amount.mul(2)); - }); - it('sets tokenURI for tokenId zero', async function () { - await expect(token.setTokenURI(collectionId, tokenURI)) - .to.emit(token, 'URI') - .withArgs(tokenURI, collectionId); - - expect(await token.uri(collectionId)).to.equal(tokenURI); - }); - it.skip('logs bits for artifact', async function () { - // Must be valid options - const _collectionType = '0x01'; - const _rarity = ArtifactRarity.Legendary; - const _artifactType = ArtifactType.Colossus; - const _biome = Biome.DESERT; - const res = await token.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); - const { collectionType, rarity, planetBiome, artifactType } = await token.decodeArtifact(res); - expect(collectionType).to.equal(Number(_collectionType)); - expect(rarity).to.equal(Number(_rarity)); - expect(planetBiome).to.equal(Number(_biome)); - expect(artifactType).to.equal(Number(_artifactType)); - }); - it('logs bits for spaceship', async function () { - // Must be valid options - const _collectionType = '0x02'; // TODO: add CollectionType to @dfdao/types - const _artifactType = ArtifactType.ShipGear; - const res = await token.encodeArtifact(_collectionType, 0, _artifactType, 0); - const { collectionType, artifactType } = await token.decodeArtifact(res); - expect(collectionType).to.equal(Number(_collectionType)); - expect(artifactType).to.equal(Number(_artifactType)); - }); -}); From 5a0b6a9abf09ab9d308f40de3afcb6145b715d7c Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 16:25:05 +0100 Subject: [PATCH 22/55] the nice nice cleanup --- eth/contracts/DFTypes.sol | 20 +-- eth/contracts/facets/DFAdminFacet.sol | 6 +- eth/contracts/facets/DFArtifactFacet.sol | 115 ++------------ eth/contracts/facets/DFGetterFacet.sol | 13 +- eth/contracts/facets/DFMoveFacet.sol | 41 +++-- eth/contracts/libraries/LibArtifactUtils.sol | 147 ++++++++++++++---- eth/contracts/libraries/LibGameUtils.sol | 72 +-------- eth/contracts/libraries/LibLazyUpdate.sol | 2 +- eth/contracts/libraries/LibPlanet.sol | 9 +- eth/contracts/libraries/LibStorage.sol | 3 +- eth/tasks/deploy.ts | 13 +- ...FArtifacts.test.ts => DFArtifacts.test.ts} | 18 +-- eth/test/DFSpaceShips.test.ts | 2 +- 13 files changed, 192 insertions(+), 269 deletions(-) rename eth/test/{NewDFArtifacts.test.ts => DFArtifacts.test.ts} (98%) diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 9ce896e8..37173772 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -222,27 +222,9 @@ enum ArtifactRarity { Mythic } -// for NFTs -struct Artifact { - bool isInitialized; - uint256 id; - uint256 planetDiscoveredOn; - ArtifactRarity rarity; - Biome planetBiome; - uint256 mintedAtTimestamp; - address discoverer; - ArtifactType artifactType; - // an artifact is 'activated' iff lastActivated > lastDeactivated - uint256 activations; - uint256 lastActivated; - uint256 lastDeactivated; - uint256 wormholeTo; // location id - address controller; // space ships can be controlled regardless of which planet they're on -} - // for artifact getters struct ArtifactWithMetadata { - Artifact artifact; + ArtifactProperties artifact; Upgrade upgrade; Upgrade timeDelayedUpgrade; // for photoid canons specifically. address owner; diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 304f3ac8..56dd3807 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -13,7 +13,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Type imports -import {SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, Artifact, ArtifactType, Player, Planet} from "../DFTypes.sol"; +import {ArtifactProperties, SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet} from "../DFTypes.sol"; contract DFAdminFacet is WithStorage { event AdminOwnershipChanged(uint256 loc, address newOwner); @@ -145,7 +145,7 @@ contract DFAdminFacet is WithStorage { require(LibArtifactUtils.isSpaceship(artifactType), "artifact type must be a space ship"); uint256 shipId = LibArtifactUtils.createAndPlaceSpaceship(locationId, owner, artifactType); - Artifact memory artifact = gs().artifacts[shipId]; + ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(shipId); Planet memory planet = gs().planets[locationId]; planet = LibPlanet.applySpaceshipArrive(artifact, planet); @@ -166,7 +166,7 @@ contract DFAdminFacet is WithStorage { } function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { - Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact(args); + ArtifactProperties memory artifact = DFArtifactFacet(address(this)).createArtifact(args); // Don't put artifact on planet if no planetId given. if (args.planetId != 0) LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); emit AdminArtifactCreated(args.owner, artifact.id, args.planetId); diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index b5d789fa..fb27ed5c 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.0; import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; -import {DFToken} from "../DFToken.sol"; // Library Imports import {LibPermissions} from "../libraries/LibPermissions.sol"; @@ -17,7 +16,7 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {Artifact, ArtifactRarity, ArtifactType, Biome, CollectionType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties, TokenInfo} from "../DFTypes.sol"; +import {ArtifactRarity, ArtifactType, Biome, CollectionType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties, TokenInfo} from "../DFTypes.sol"; import "hardhat/console.sol"; @@ -67,42 +66,34 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { function createArtifact(DFTCreateArtifactArgs memory args) public onlyAdminOrCore - returns (Artifact memory) + returns (ArtifactProperties memory) { require(args.tokenId >= 1, "artifact id must be positive"); // Account, Id, Amount, Data _mint(args.owner, args.tokenId, 1, ""); - Artifact memory newArtifact = Artifact( - true, - args.tokenId, - args.planetId, - args.rarity, - args.biome, - block.timestamp, - args.discoverer, - args.artifactType, - 0, - 0, - 0, - 0, - args.controller - ); - - gs().artifacts[args.tokenId] = newArtifact; - - return newArtifact; + return getArtifact(args.tokenId); } function getArtifact(uint256 tokenId) public pure returns (ArtifactProperties memory) { - return decodeArtifact(tokenId); + return LibArtifactUtils.decodeArtifact(tokenId); + } + + function encodeArtifact( + uint256 _collectionType, + uint256 _rarity, + uint256 _artifactType, + uint256 _biome + ) public pure returns (uint256) { + return LibArtifactUtils.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); } // function getArtifactAtIndex(uint256 idx) public view returns (Artifact memory) { // return gs().artifacts[tokenByIndex(idx)]; // } + // TODO: Add enumerable // function getPlayerArtifactIds(address playerId) public view returns (uint256[] memory) { // uint256 balance = balanceOf(playerId); // uint256[] memory results = new uint256[](balance); @@ -130,15 +121,6 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { } } - function updateArtifact(Artifact memory updatedArtifact, address owner) public onlyAdminOrCore { - require( - doesArtifactExist(owner, updatedArtifact.id), - "you cannot update an artifact that doesn't exist" - ); - - gs().artifacts[updatedArtifact.id] = updatedArtifact; - } - function doesArtifactExist(address owner, uint256 tokenId) public view returns (bool) { return balanceOf(owner, tokenId) > 0; } @@ -166,7 +148,6 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { ); } - console.log("finding artifact..."); uint256 foundArtifactId = LibArtifactUtils.findArtifact( DFPFindArtifactArgs(planetId, biomebase, address(this)) ); @@ -305,7 +286,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { ) internal virtual override { uint256 length = ids.length; for (uint256 i = 0; i < length; i++) { - ArtifactProperties memory artifact = decodeArtifact(ids[i]); + ArtifactProperties memory artifact = getArtifact(ids[i]); // Only core contract can transfer Spaceships if (LibArtifactUtils.isSpaceship(artifact.artifactType)) { require(msg.sender == gs().diamondAddress, "player cannot transfer a Spaceship"); @@ -321,70 +302,4 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { function setTokenURI(uint256 tokenId, string memory tokenURI) public { _setTokenURI(tokenId, tokenURI); } - - /** - * @notice calculate amount of bits to shift left - * @param index number of 1 byte words to shift from left - * @return shift length of left shift - */ - function calcBitShift(uint8 index) internal pure returns (uint8) { - uint8 maxVal = 32; - - require(index <= maxVal, "shift index is too high"); - require(index > 0, "shift index is too low"); - - uint256 bin = 8; - uint256 shift = 256; - return uint8(shift - (bin * index)); - } - - /** - * @notice Create the collection ID for a given artifact - * @param _collectionType type of artifact - * @param _rarity rarity of artifact - * @param _artifactType of artifact - * @param _biome of artifact - * @notice this is not a struct because I can't figure out how to bit shift a uint in a struct. - */ - function encodeArtifact( - uint256 _collectionType, - uint256 _rarity, - uint256 _artifactType, - uint256 _biome - ) public pure returns (uint256) { - uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); - uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); - uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); - uint256 biome = _biome << calcBitShift(uint8(TokenInfo.Biome)); - return collectionType + rarity + artifactType + biome; - } - - /** - * @notice Fetch the ArtifactProperties for the given id - * @param artifactId type of artifact - */ - function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { - bytes memory _b = abi.encodePacked(artifactId); - // 0 is left most element. 0 is given the property Unknown in TokenInfo. - - // Note: Bit shifting requires an index greater than zero. This is why the TokenInfo has - // Unknown as the zero property, so calcBitShift(TokenInfo.Level) is correct. - // As a consequence, we need to - // offset fetching the relevant byte from the artifactId by 1. - // However - uint8 collectionType = uint8(_b[uint8(TokenInfo.CollectionType) - 1]); - uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); - uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); - uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); - - ArtifactProperties memory a = ArtifactProperties({ - id: artifactId, - collectionType: CollectionType(collectionType), - rarity: ArtifactRarity(rarity), - artifactType: ArtifactType(artifactType), - planetBiome: Biome(biome) - }); - - return a; - } } diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 38f38fc3..7fc87e45 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -7,12 +7,13 @@ import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Library imports import {LibPermissions} from "../libraries/LibPermissions.sol"; import {LibGameUtils} from "../libraries/LibGameUtils.sol"; +import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; // Storage imports import {WithStorage, SnarkConstants, GameConstants} from "../libraries/LibStorage.sol"; // Type imports -import {RevealedCoords, ArtifactProperties, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, ArtifactWithMetadata, Upgrade, Artifact} from "../DFTypes.sol"; +import {RevealedCoords, ArtifactProperties, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, ArtifactWithMetadata, Upgrade} from "../DFTypes.sol"; import "hardhat/console.sol"; contract DFGetterFacet is WithStorage { @@ -325,7 +326,7 @@ contract DFGetterFacet is WithStorage { // view // returns (ArtifactWithMetadata memory ret) // { - // Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactId); + // Artifact memory artifact = LibArtifactUtils(address(this)).decodeArtifact(artifactId); // address owner; @@ -355,7 +356,7 @@ contract DFGetterFacet is WithStorage { uint256[] memory artifactIds = gs().planetArtifacts[locationId]; ret = new ArtifactProperties[](artifactIds.length); for (uint256 i = 0; i < artifactIds.length; i++) { - ret[i] = DFArtifactFacet(address(this)).decodeArtifact(artifactIds[i]); + ret[i] = LibArtifactUtils.decodeArtifact(artifactIds[i]); } return ret; } @@ -379,7 +380,7 @@ contract DFGetterFacet is WithStorage { returns (ArtifactProperties memory ret) { uint256 artifactId = gs().planetActiveArtifact[locationId]; - return DFArtifactFacet(address(this)).getArtifact(artifactId); + return LibArtifactUtils.decodeArtifact(artifactId); } // function bulkGetPlanetArtifacts(uint256[] calldata planetIds) @@ -405,7 +406,7 @@ contract DFGetterFacet is WithStorage { // ret = new ArtifactWithMetadata[](ids.length); // for (uint256 i = 0; i < ids.length; i++) { - // Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(ids[i]); + // Artifact memory artifact = LibArtifactUtils(address(this)).decodeArtifact(ids[i]); // address owner; @@ -443,7 +444,7 @@ contract DFGetterFacet is WithStorage { // ret = new ArtifactWithMetadata[](endIdx - startIdx); // for (uint256 i = startIdx; i < endIdx; i++) { - // Artifact memory artifact = DFArtifactFacet(address(this)).getArtifactAtIndex(i); + // Artifact memory artifact = LibArtifactUtils(address(this)).decodeArtifactAtIndex(i); // address owner = address(0); // try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index affe1f66..7e0eeee4 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -15,7 +15,7 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {ArrivalData, ArrivalType, Artifact, ArtifactProperties, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; +import {ArrivalData, ArrivalType, ArtifactProperties, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; import "hardhat/console.sol"; contract DFMoveFacet is WithStorage { @@ -126,7 +126,6 @@ contract DFMoveFacet is WithStorage { if (photoidPresent) { temporaryUpgrade = newTempUpgrade; arrivalType = ArrivalType.Photoid; - console.log("doing photoid move"); } } @@ -236,7 +235,7 @@ contract DFMoveFacet is WithStorage { } } - function applySpaceshipDepart(Artifact memory artifact, Planet memory planet) + function applySpaceshipDepart(ArtifactProperties memory artifact, Planet memory planet) public view returns (Planet memory) @@ -273,7 +272,9 @@ contract DFMoveFacet is WithStorage { Undo the spaceship effects that were applied when the ship arrived on the planet. */ function _removeSpaceshipEffectsFromOriginPlanet(DFPMoveArgs memory args) private { - Artifact memory movedArtifact = gs().artifacts[args.movedArtifactId]; + ArtifactProperties memory movedArtifact = LibArtifactUtils.decodeArtifact( + args.movedArtifactId + ); Planet memory planet = applySpaceshipDepart(movedArtifact, gs().planets[args.oldLoc]); gs().planets[args.oldLoc] = planet; } @@ -291,10 +292,13 @@ contract DFMoveFacet is WithStorage { { wormholePresent = false; - // Artifact memory relevantWormhole; ArtifactProperties memory relevantWormhole; - ArtifactProperties memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); - ArtifactProperties memory activeArtifactTo = LibGameUtils.getActiveArtifact(args.newLoc); + ArtifactProperties memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact( + args.oldLoc + ); + ArtifactProperties memory activeArtifactTo = LibArtifactUtils.getActiveArtifact( + args.newLoc + ); // TODO: take the greater rarity of these, or disallow wormholes between planets that // already have a wormhole between them if ( @@ -326,14 +330,15 @@ contract DFMoveFacet is WithStorage { private returns (bool photoidPresent, Upgrade memory temporaryUpgrade) { - ArtifactProperties memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); + ArtifactProperties memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact( + args.oldLoc + ); if ( activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= gameConstants().PHOTOID_ACTIVATION_DELAY ) { photoidPresent = true; - console.log("photoid present? %s", LibGameUtils.getActiveArtifact(args.oldLoc).id > 0); LibArtifactUtils.deactivateArtifact(args.oldLoc); temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); } @@ -412,19 +417,15 @@ contract DFMoveFacet is WithStorage { } function _isSpaceshipMove(DFPMoveArgs memory args) private view returns (bool) { - return LibArtifactUtils.isSpaceship(gs().artifacts[args.movedArtifactId].artifactType); + return + LibArtifactUtils.isSpaceship( + LibArtifactUtils.decodeArtifact(args.movedArtifactId).artifactType + ); } function _createArrival(DFPCreateArrivalArgs memory args) private { // enter the arrival data for event id Planet memory planet = gs().planets[args.oldLoc]; - // console.log( - // "pop moved: %s dist %s range %s", - // args.popMoved, - // args.effectiveDistTimesHundred, - // uint256(planet.range) - // ); - // console.logUint(planet.populationCap); uint256 popArriving = _getDecayedPop( args.popMoved, @@ -433,7 +434,7 @@ contract DFMoveFacet is WithStorage { planet.populationCap ); bool isSpaceship = LibArtifactUtils.isSpaceship( - DFArtifactFacet(address(this)).decodeArtifact(args.movedArtifactId).artifactType + LibArtifactUtils.decodeArtifact(args.movedArtifactId).artifactType ); // space ship moves are implemented as 0-energy moves require(popArriving > 0 || isSpaceship, "Not enough forces to make move"); @@ -452,9 +453,7 @@ contract DFMoveFacet is WithStorage { distance: args.actualDist }); // Photoids are burned _checkPhotoid, so don't remove twice - ArtifactProperties memory artifact = DFArtifactFacet(address(this)).decodeArtifact( - args.movedArtifactId - ); + ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(args.movedArtifactId); if (args.movedArtifactId != 0 && artifact.artifactType != ArtifactType.PhotoidCannon) { LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 1d9b1df0..bf4b7a01 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -12,7 +12,7 @@ import {LibGameUtils} from "./LibGameUtils.sol"; import {LibStorage, GameStorage, GameConstants} from "./LibStorage.sol"; // Type imports -import {Biome, Planet, PlanetType, Artifact, ArtifactType, ArtifactRarity, CollectionType, DFPFindArtifactArgs, DFTCreateArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; +import {Biome, Planet, PlanetType, ArtifactType, ArtifactRarity, CollectionType, DFPFindArtifactArgs, DFTCreateArtifactArgs, ArtifactProperties, TokenInfo} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibArtifactUtils { @@ -65,7 +65,7 @@ library LibArtifactUtils { require(shipType <= ArtifactType.ShipTitan && shipType >= ArtifactType.ShipMothership); // require(gs().miscNonce < MAX UINT 128) but won't happen. uint128 id = uint128(gs().miscNonce++); - uint256 tokenId = DFArtifactFacet(address(this)).encodeArtifact( + uint256 tokenId = encodeArtifact( uint8(CollectionType.Spaceship), uint8(ArtifactRarity.Unknown), uint8(shipType), @@ -83,12 +83,12 @@ library LibArtifactUtils { // Only used for spaceships controller: owner }); - Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( + ArtifactProperties memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( createArtifactArgs ); LibGameUtils._putArtifactOnPlanet(planetId, foundArtifact.id); - return id; + return tokenId; } function findArtifact(DFPFindArtifactArgs memory args) public returns (uint256 artifactId) { @@ -115,12 +115,7 @@ library LibArtifactUtils { levelBonus + planet.planetLevel ); - // console.log("LAU: collectionType %s", uint8(CollectionType.Artifact)); - // console.log("LAU: rarity %s", uint8(rarity)); - // console.log("artifactType %s", uint8(artifactType)); - // console.log("biome %s", uint8(biome)); - - uint256 tokenId = DFArtifactFacet(address(this)).encodeArtifact( + uint256 tokenId = encodeArtifact( uint8(CollectionType.Artifact), uint8(rarity), uint8(artifactType), @@ -138,7 +133,7 @@ library LibArtifactUtils { address(0) ); - Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( + ArtifactProperties memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( createArtifactArgs ); @@ -158,9 +153,7 @@ library LibArtifactUtils { uint256 wormholeTo ) public { Planet storage planet = gs().planets[locationId]; - ArtifactProperties memory artifact = DFArtifactFacet(address(this)).decodeArtifact( - artifactId - ); + ArtifactProperties memory artifact = decodeArtifact(artifactId); require( LibGameUtils.isArtifactOnPlanet(locationId, artifactId), @@ -225,22 +218,19 @@ library LibArtifactUtils { Planet storage planet, ArtifactProperties memory artifact ) private { - console.log("activating %s on %s", artifactId, locationId); require( planet.owner == msg.sender, "you must own the planet you are activating an artifact on" ); require( - LibGameUtils.getActiveArtifact(locationId).collectionType == CollectionType.Unknown, + getActiveArtifact(locationId).collectionType == CollectionType.Unknown, "there is already an active artifact on this planet" ); require(!planet.destroyed, "planet is destroyed"); uint256 length = gs().planetArtifacts[locationId].length; - console.log("artifacts on %s: %s", locationId, length); require( - LibGameUtils.getPlanetArtifact(locationId, artifactId).collectionType != - CollectionType.Unknown, + getPlanetArtifact(locationId, artifactId).collectionType != CollectionType.Unknown, "this artifact is not on this planet" ); @@ -288,8 +278,6 @@ library LibArtifactUtils { // burn it after use. will be owned by contract but not on a planet anyone can control LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); } - - console.log("buffing planet"); // this is fine even tho some artifacts are immediately deactivated, because // those artifacts do not buff the planet. LibGameUtils._buffPlanet(locationId, LibGameUtils._getUpgradeForArtifact(artifact)); @@ -305,7 +293,7 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); - ArtifactProperties memory artifact = LibGameUtils.getActiveArtifact(locationId); + ArtifactProperties memory artifact = getActiveArtifact(locationId); require( artifact.collectionType != CollectionType.Unknown, @@ -323,11 +311,6 @@ library LibArtifactUtils { bool shouldBurn = artifact.artifactType == ArtifactType.PlanetaryShield || artifact.artifactType == ArtifactType.PhotoidCannon; if (shouldBurn) { - console.log("burning %s", artifact.id); - console.log( - "artifact is on planet? %s", - DFGetterFacet(address(this)).artifactExistsOnPlanet(locationId, artifact.id) - ); // burn it after use. will be owned by contract but not on a planet anyone can control LibGameUtils._takeArtifactOffPlanet(locationId, artifact.id); } @@ -350,7 +333,7 @@ library LibArtifactUtils { ); require(planet.owner == msg.sender, "you can only deposit on a planet you own"); - ArtifactProperties memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactId); + ArtifactProperties memory artifact = decodeArtifact(artifactId); require( planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to deposit this artifact" @@ -373,7 +356,7 @@ library LibArtifactUtils { ); require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.owner == msg.sender, "you can only withdraw from a planet you own"); - ArtifactProperties memory artifact = LibGameUtils.getPlanetArtifact(locationId, artifactId); + ArtifactProperties memory artifact = getPlanetArtifact(locationId, artifactId); // TODO: Write is initialized function. require( artifact.collectionType != CollectionType.Unknown, @@ -410,9 +393,7 @@ library LibArtifactUtils { uint256[] memory artifactIds = gs().planetArtifacts[locationId]; for (uint256 i = 0; i < artifactIds.length; i++) { - ArtifactProperties memory artifact = DFArtifactFacet(address(this)).getArtifact( - artifactIds[i] - ); + ArtifactProperties memory artifact = decodeArtifact(artifactIds[i]); if ( // TODO: Gear is broken artifact.artifactType == ArtifactType.ShipGear // && msg.sender == artifact.controller @@ -428,4 +409,106 @@ library LibArtifactUtils { return artifactType >= ArtifactType.ShipMothership && artifactType <= ArtifactType.ShipTitan; } + + /** + * @notice calculate amount of bits to shift left + * @param index number of 1 byte words to shift from left + * @return shift length of left shift + */ + function calcBitShift(uint8 index) internal pure returns (uint8) { + uint8 maxVal = 32; + + require(index <= maxVal, "shift index is too high"); + require(index > 0, "shift index is too low"); + + uint256 bin = 8; + uint256 shift = 256; + return uint8(shift - (bin * index)); + } + + /** + * @notice Create the collection ID for a given artifact + * @param _collectionType type of artifact + * @param _rarity rarity of artifact + * @param _artifactType of artifact + * @param _biome of artifact + * @notice this is not a struct because I can't figure out how to bit shift a uint in a struct. + */ + function encodeArtifact( + uint256 _collectionType, + uint256 _rarity, + uint256 _artifactType, + uint256 _biome + ) public pure returns (uint256) { + uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); + uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); + uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); + uint256 biome = _biome << calcBitShift(uint8(TokenInfo.Biome)); + return collectionType + rarity + artifactType + biome; + } + + /** + * @notice Fetch the ArtifactProperties for the given id + * @param artifactId type of artifact + */ + function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { + bytes memory _b = abi.encodePacked(artifactId); + // 0 is left most element. 0 is given the property Unknown in TokenInfo. + + // Note: Bit shifting requires an index greater than zero. This is why the TokenInfo has + // Unknown as the zero property, so calcBitShift(TokenInfo.Level) is correct. + // As a consequence, we need to + // offset fetching the relevant byte from the artifactId by 1. + // However + uint8 collectionType = uint8(_b[uint8(TokenInfo.CollectionType) - 1]); + uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); + uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); + uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); + + ArtifactProperties memory a = ArtifactProperties({ + id: artifactId, + collectionType: CollectionType(collectionType), + rarity: ArtifactRarity(rarity), + artifactType: ArtifactType(artifactType), + planetBiome: Biome(biome) + }); + + return a; + } + + // if the given planet has an activated artifact on it, then return the artifact + // otherwise, return a 'null artifact' + function getActiveArtifact(uint256 locationId) public view returns (ArtifactProperties memory) { + uint256 artifactId = gs().planetActiveArtifact[locationId]; + if (artifactId != 0) return decodeArtifact(artifactId); + + return _nullArtifactProperties(); + } + + function _nullArtifactProperties() private pure returns (ArtifactProperties memory) { + return + ArtifactProperties( + 0, + CollectionType.Unknown, + ArtifactRarity.Unknown, + ArtifactType.Unknown, + Biome.Unknown + ); + } + + // if the given artifact is on the given planet, then return the artifact + // otherwise, return a 'null' artifact + function getPlanetArtifact(uint256 locationId, uint256 artifactId) + public + view + returns (ArtifactProperties memory) + { + for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { + if (gs().planetArtifacts[locationId][i] == artifactId) { + return decodeArtifact(artifactId); + } + } + + return _nullArtifactProperties(); + } } diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 3b762cbe..1189a7df 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -10,7 +10,7 @@ import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; -import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactProperties, ArtifactType, CollectionType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; +import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, ArtifactProperties, ArtifactType, CollectionType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibGameUtils { @@ -392,10 +392,8 @@ library LibGameUtils { // internal contract book-keeping to reflect that the given artifact was // put on. note that this function does not transfer the artifact. function _putArtifactOnPlanet(uint256 locationId, uint256 artifactId) public { - console.log("putting %s on %s", artifactId, locationId); gs().planetArtifacts[locationId].push(artifactId); uint256 length = gs().planetArtifacts[locationId].length; - console.log("new planet artifact id", gs().planetArtifacts[locationId][length - 1]); } // TODO: Why not burn ? @@ -413,14 +411,13 @@ library LibGameUtils { function _takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) public { uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; - console.log("removing %s from %s", artifactId, locationId); - console.log("%s artifacts on %s", artifactsOnThisPlanet, locationId); + bool hadTheArtifact = false; for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { require( - !isActivatedERC1155(locationId, artifactId), + !isActivated(locationId, artifactId), "you cannot take an activated artifact off a planet" ); @@ -441,11 +438,8 @@ library LibGameUtils { // we do not have an `isActive` field on artifact; the times that the // artifact was last activated and deactivated are sufficent to determine // whether or not the artifact is activated. - function isActivated(Artifact memory artifact) public pure returns (bool) { - return artifact.lastDeactivated < artifact.lastActivated; - } - function isActivatedERC1155(uint256 locationId, uint256 artifactId) public view returns (bool) { + function isActivated(uint256 locationId, uint256 artifactId) public view returns (bool) { return (gs().planetActiveArtifact[locationId] == artifactId); } @@ -459,32 +453,6 @@ library LibGameUtils { return false; } - // if the given artifact is on the given planet, then return the artifact - // otherwise, return a 'null' artifact - function getPlanetArtifact(uint256 locationId, uint256 artifactId) - public - view - returns (ArtifactProperties memory) - { - console.log("searching for %s on %s", artifactId, locationId); - for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { - return DFArtifactFacet(address(this)).getArtifact(artifactId); - } - } - - return _nullArtifactProperties(); - } - - // if the given planet has an activated artifact on it, then return the artifact - // otherwise, return a 'null artifact' - function getActiveArtifact(uint256 locationId) public view returns (ArtifactProperties memory) { - uint256 artifactId = gs().planetActiveArtifact[locationId]; - if (artifactId != 0) return DFArtifactFacet(address(this)).decodeArtifact(artifactId); - - return _nullArtifactProperties(); - } - // the space junk that a planet starts with function getPlanetDefaultSpaceJunk(Planet memory planet) public view returns (uint256) { if (planet.isHomePlanet) return 0; @@ -492,38 +460,6 @@ library LibGameUtils { return gameConstants().PLANET_LEVEL_JUNK[planet.planetLevel]; } - // constructs a new artifact whose `isInititalized` field is set to `false` - // used to represent the concept of 'no artifact' - function _nullArtifact() private pure returns (Artifact memory) { - return - Artifact( - false, - 0, - 0, - ArtifactRarity(0), - Biome(0), - 0, - address(0), - ArtifactType(0), - 0, - 0, - 0, - 0, - address(0) - ); - } - - function _nullArtifactProperties() private pure returns (ArtifactProperties memory) { - return - ArtifactProperties( - 0, - CollectionType.Unknown, - ArtifactRarity.Unknown, - ArtifactType.Unknown, - Biome.Unknown - ); - } - function _buffPlanet(uint256 location, Upgrade memory upgrade) public { Planet storage planet = gs().planets[location]; diff --git a/eth/contracts/libraries/LibLazyUpdate.sol b/eth/contracts/libraries/LibLazyUpdate.sol index 0c3944f3..fa3daaaf 100644 --- a/eth/contracts/libraries/LibLazyUpdate.sol +++ b/eth/contracts/libraries/LibLazyUpdate.sol @@ -8,7 +8,7 @@ import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; import {LibStorage, GameStorage} from "./LibStorage.sol"; // Type imports -import {Planet, PlanetType, PlanetEventMetadata, PlanetEventType, ArrivalData, ArrivalType, Artifact} from "../DFTypes.sol"; +import {Planet, PlanetType, PlanetEventMetadata, PlanetEventType, ArrivalData, ArrivalType} from "../DFTypes.sol"; library LibLazyUpdate { function gs() internal pure returns (GameStorage storage) { diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 7d503526..ff0319bb 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -3,16 +3,18 @@ pragma solidity ^0.8.0; // Contract imports import {DFVerifierFacet} from "../facets/DFVerifierFacet.sol"; +import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; // Library imports import {LibGameUtils} from "./LibGameUtils.sol"; import {LibLazyUpdate} from "./LibLazyUpdate.sol"; +import {LibArtifactUtils} from "./LibArtifactUtils.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; // Type imports -import {Artifact, ArtifactType, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; +import {ArtifactType, ArtifactProperties, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibPlanet { @@ -286,7 +288,8 @@ library LibPlanet { } for (uint256 i = 0; i < artifactsToAdd.length; i++) { - Artifact memory artifact = gs().artifacts[artifactsToAdd[i]]; + // artifactsToAdd[i] + ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(artifactsToAdd[i]); planet = applySpaceshipArrive(artifact, planet); } @@ -296,7 +299,7 @@ library LibPlanet { return (planet, eventsToRemove, artifactsToAdd); } - function applySpaceshipArrive(Artifact memory artifact, Planet memory planet) + function applySpaceshipArrive(ArtifactProperties memory artifact, Planet memory planet) public pure returns (Planet memory) diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index f6eeb51e..57d1d3a1 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; // Type imports -import {Planet, PlanetEventMetadata, PlanetDefaultStats, Upgrade, RevealedCoords, Player, ArrivalData, Artifact} from "../DFTypes.sol"; +import {Planet, PlanetEventMetadata, PlanetDefaultStats, Upgrade, RevealedCoords, Player, ArrivalData} from "../DFTypes.sol"; struct WhitelistStorage { bool enabled; @@ -53,7 +53,6 @@ struct GameStorage { mapping(uint256 => uint256) planetWormholes; // planetId to timestamp. For all artifacts, but only used for photoids. mapping(uint256 => uint256) planetArtifactActivationTime; - mapping(uint256 => Artifact) artifacts; // Capture Zones uint256 nextChangeBlock; } diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index f9df2f50..a1c90c51 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -309,11 +309,15 @@ export async function deployAndCut( return [diamond, diamondInit, initReceipt] as const; } -export async function deployGetterFacet({}, {}: Libraries, hre: HardhatRuntimeEnvironment) { +export async function deployGetterFacet( + {}, + { LibArtifactUtils }: Libraries, + hre: HardhatRuntimeEnvironment +) { const factory = await hre.ethers.getContractFactory('DFGetterFacet', { - // libraries: { - // LibGameUtils, - // }, + libraries: { + LibArtifactUtils, + }, }); const contract = await factory.deploy(); await contract.deployTransaction.wait(); @@ -411,6 +415,7 @@ export async function deployLibraries({}, hre: HardhatRuntimeEnvironment) { libraries: { LibGameUtils: LibGameUtils.address, LibLazyUpdate: LibLazyUpdate.address, + LibArtifactUtils: LibArtifactUtils.address, }, }); const LibPlanet = await LibPlanetFactory.deploy(); diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts similarity index 98% rename from eth/test/NewDFArtifacts.test.ts rename to eth/test/DFArtifacts.test.ts index 5d587dd4..b7ed4149 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -79,7 +79,7 @@ describe('DarkForestArtifacts', function () { world = await loadFixture(worldFixture); }); - describe.only('it tests basic artifact actions', function () { + describe('it tests basic artifact actions', function () { it('logs bits for artifact', async function () { // Must be valid options const _collectionType = '0x01'; @@ -93,7 +93,7 @@ describe('DarkForestArtifacts', function () { _biome ); const { collectionType, rarity, planetBiome, artifactType } = - await world.contract.decodeArtifact(res); + await world.contract.getArtifact(res); expect(collectionType).to.equal(Number(_collectionType)); expect(rarity).to.equal(Number(_rarity)); expect(planetBiome).to.equal(Number(_biome)); @@ -104,7 +104,7 @@ describe('DarkForestArtifacts', function () { const _collectionType = '0x02'; // TODO: add CollectionType to @dfdao/types const _artifactType = ArtifactType.ShipGear; const res = await world.contract.encodeArtifact(_collectionType, 0, _artifactType, 0); - const { collectionType, artifactType } = await world.contract.decodeArtifact(res); + const { collectionType, artifactType } = await world.contract.getArtifact(res); expect(collectionType).to.equal(Number(_collectionType)); expect(artifactType).to.equal(Number(_artifactType)); }); @@ -123,7 +123,7 @@ describe('DarkForestArtifacts', function () { await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); - prettyPrintToken(await world.user1Core.decodeArtifact(artifactId)); + prettyPrintToken(await world.user1Core.getArtifact(artifactId)); // artifact and gear should be on planet. Gear is 0 and Artifact is 1. const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); @@ -625,7 +625,7 @@ describe('DarkForestArtifacts', function () { CollectionType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.decodeArtifact(artifactId)); + prettyPrintToken(await world.contract.getArtifact(artifactId)); activateAndConfirm(world.user1Core, from.id, artifactId, to.id); @@ -786,7 +786,7 @@ describe('DarkForestArtifacts', function () { CollectionType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifact(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); @@ -834,7 +834,7 @@ describe('DarkForestArtifacts', function () { { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifact(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move( @@ -942,7 +942,7 @@ describe('DarkForestArtifacts', function () { CollectionType.Artifact, { rarity: ArtifactRarity.Rare as ArtifactRarity, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifact(newTokenId)); const planetBeforeActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); await activateAndConfirm(world.user1Core, LVL3_SPACETIME_1.id, newTokenId); @@ -987,7 +987,7 @@ describe('DarkForestArtifacts', function () { CollectionType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifact(newTokenId)); await world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index 62b9514d..d4192c82 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -59,7 +59,7 @@ describe('DarkForestSpaceShips', function () { }); }); - describe.only('ship transfers', function () { + describe('ship transfers', function () { it('cannot transfer your own spaceship', async function () { const motherShip = await getArtifactOnPlanetByType( world.contract, From 0ec6d6d49aaba4571a041c962c25d1e6ebe3dd20 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 16:26:41 +0100 Subject: [PATCH 23/55] clean --- eth/test/DFToken.test.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 eth/test/DFToken.test.ts diff --git a/eth/test/DFToken.test.ts b/eth/test/DFToken.test.ts deleted file mode 100644 index d8a41f9f..00000000 --- a/eth/test/DFToken.test.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO: Implement ERC-1155 Tests for 721(Artifact, Spaceship) 20(Silver) From 6dabfc7a514bdea9f26986dfa74125370d4727c0 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 16:31:27 +0100 Subject: [PATCH 24/55] clean --- eth/contracts/DFInitialize.sol | 3 +-- eth/contracts/facets/DFArtifactFacet.sol | 16 +--------------- eth/contracts/facets/DFMoveFacet.sol | 3 --- eth/contracts/libraries/LibArtifactUtils.sol | 11 ----------- eth/contracts/libraries/LibStorage.sol | 1 - 5 files changed, 2 insertions(+), 32 deletions(-) diff --git a/eth/contracts/DFInitialize.sol b/eth/contracts/DFInitialize.sol index 6392275c..b0033910 100644 --- a/eth/contracts/DFInitialize.sol +++ b/eth/contracts/DFInitialize.sol @@ -108,8 +108,7 @@ contract DFInitialize is WithStorage { string memory artifactBaseURI, InitArgs memory initArgs ) external { - // Setup the ERC721 metadata - // TODO(#1925): Add name and symbol for the artifact tokens + // Setup the ERC1155 metadata ERC1155MetadataStorage.layout().baseURI = artifactBaseURI; gs().diamondAddress = address(this); diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index fb27ed5c..bd762b9b 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -89,21 +89,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { return LibArtifactUtils.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); } - // function getArtifactAtIndex(uint256 idx) public view returns (Artifact memory) { - // return gs().artifacts[tokenByIndex(idx)]; - // } - - // TODO: Add enumerable - // function getPlayerArtifactIds(address playerId) public view returns (uint256[] memory) { - // uint256 balance = balanceOf(playerId); - // uint256[] memory results = new uint256[](balance); - - // for (uint256 idx = 0; idx < balance; idx++) { - // results[idx] = tokenOfOwnerByIndex(playerId, idx); - // } - - // return results; - // } + // TODO: Add ERC1155 Enumerable Wrappers // This calls the low level _transfer call which doesn't check if the msg.sender actually owns // the tokenId. TODO: See if this is a problem. diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 7e0eeee4..84288977 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -113,7 +113,6 @@ contract DFMoveFacet is WithStorage { ArrivalType arrivalType = ArrivalType.Normal; Upgrade memory temporaryUpgrade = LibGameUtils.defaultUpgrade(); - // TODO: Add back wormhole (bool wormholePresent, uint256 distModifier) = _checkWormhole(args); if (wormholePresent) { effectiveDistTimesHundred /= distModifier; @@ -121,7 +120,6 @@ contract DFMoveFacet is WithStorage { } if (!_isSpaceshipMove(args)) { - // TODO: Add back photoid (bool photoidPresent, Upgrade memory newTempUpgrade) = _checkPhotoid(args); if (photoidPresent) { temporaryUpgrade = newTempUpgrade; @@ -280,7 +278,6 @@ contract DFMoveFacet is WithStorage { } /** - TODO: Fix wormhole functionality. If an active wormhole is present on the origin planet, return the modified distance between the origin and target planet. diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index bf4b7a01..cd0326ab 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -71,7 +71,6 @@ library LibArtifactUtils { uint8(shipType), uint8(Biome.Unknown) ); - // TODO: Use struct naming convention for readability DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs({ tokenId: tokenId + id, discoverer: msg.sender, @@ -161,13 +160,10 @@ library LibArtifactUtils { ); if (isSpaceship(artifact.artifactType)) { - // TODO: fix Crescent functionality activateSpaceshipArtifact(locationId, artifactId, planet, artifact); } else { activateNonSpaceshipArtifact(locationId, artifactId, wormholeTo, planet, artifact); } - - // artifact.activations++; } function activateSpaceshipArtifact( @@ -177,9 +173,6 @@ library LibArtifactUtils { ArtifactProperties memory artifact ) private { if (artifact.artifactType == ArtifactType.ShipCrescent) { - // Burn the goddam crescent - // require(artifact.activations == 0, "crescent cannot be activated more than once"); - require( planet.planetType != PlanetType.SILVER_MINE, "cannot turn a silver mine into a silver mine" @@ -240,8 +233,6 @@ library LibArtifactUtils { gs().planetActiveArtifact[locationId] = artifactId; emit ArtifactActivated(msg.sender, locationId, artifactId); - // TODO: Wormhole is broken - if (artifact.artifactType == ArtifactType.Wormhole) { require(wormholeTo != 0, "you must provide a wormholeTo to activate a wormhole"); @@ -250,8 +241,6 @@ library LibArtifactUtils { "you can only create a wormhole to a planet you own" ); require(!gs().planets[wormholeTo].destroyed, "planet destroyed"); - // TODO: Store some way to remember where a wormhole is. Maybe new data structure. - // artifact.wormholeTo = wormholeTo; gs().planetWormholes[locationId] = wormholeTo; } else if (artifact.artifactType == ArtifactType.BloomFilter) { require( diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index 57d1d3a1..86a863b3 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -47,7 +47,6 @@ struct GameStorage { mapping(uint256 => ArrivalData) planetArrivals; // Artifact stuff mapping(uint256 => uint256[]) planetArtifacts; - // TODO: Make this an array mapping(uint256 => uint256) planetActiveArtifact; // wormhole from => to. planetWormHoles[from] = to; mapping(uint256 => uint256) planetWormholes; From 2a4a02d7b58cd8efed5dd31057404037f3ccd2e3 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 16:41:18 +0100 Subject: [PATCH 25/55] remove more data structures --- eth/contracts/facets/DFGetterFacet.sol | 10 +--------- eth/contracts/libraries/LibPlanet.sol | 1 - eth/contracts/libraries/LibStorage.sol | 2 -- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 7fc87e45..9ae192ab 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -78,14 +78,6 @@ contract DFGetterFacet is WithStorage { return gs().revealedCoords[key]; } - function artifactIdToPlanetId(uint256 key) public view returns (uint256) { - return gs().artifactIdToPlanetId[key]; - } - - function artifactIdToVoyageId(uint256 key) public view returns (uint256) { - return gs().artifactIdToVoyageId[key]; - } - function planetEvents(uint256 key) public view returns (PlanetEventMetadata[] memory) { return gs().planetEvents[key]; } @@ -406,7 +398,7 @@ contract DFGetterFacet is WithStorage { // ret = new ArtifactWithMetadata[](ids.length); // for (uint256 i = 0; i < ids.length; i++) { - // Artifact memory artifact = LibArtifactUtils(address(this)).decodeArtifact(ids[i]); + // ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(ids[i]); // address owner; diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index ff0319bb..84aa20a9 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -349,7 +349,6 @@ library LibPlanet { for (uint256 i = 0; i < 12; i++) { if (artifactIdsToAddToPlanet[i] != 0) { - gs().artifactIdToVoyageId[artifactIdsToAddToPlanet[i]] = 0; LibGameUtils._putArtifactOnPlanet(location, artifactIdsToAddToPlanet[i]); } } diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index 86a863b3..58ee97a3 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -38,8 +38,6 @@ struct GameStorage { uint256 miscNonce; mapping(uint256 => Planet) planets; mapping(uint256 => RevealedCoords) revealedCoords; - mapping(uint256 => uint256) artifactIdToPlanetId; - mapping(uint256 => uint256) artifactIdToVoyageId; mapping(address => Player) players; // maps location id to planet events array mapping(uint256 => PlanetEventMetadata[]) planetEvents; From 239645f35b2e0a46b3473326ec6c390422c27b4f Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 16:47:59 +0100 Subject: [PATCH 26/55] feat: give artifacts unique ids for getter purposes --- eth/contracts/libraries/LibArtifactUtils.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index cd0326ab..c2ac2c59 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -113,7 +113,7 @@ library LibArtifactUtils { ArtifactRarity rarity = LibGameUtils.artifactRarityFromPlanetLevel( levelBonus + planet.planetLevel ); - + uint128 id = uint128(gs().miscNonce++); uint256 tokenId = encodeArtifact( uint8(CollectionType.Artifact), uint8(rarity), @@ -122,7 +122,7 @@ library LibArtifactUtils { ); DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( - tokenId, + tokenId + id, msg.sender, // discoverer args.planetId, rarity, From 061a631f19f9286030d370041a786193a213f707 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 22:56:41 +0100 Subject: [PATCH 27/55] feat: encode / decode works, need to thread through --- eth/contracts/DFTypes.sol | 67 ++++++++++---- eth/contracts/Tokens.md | 10 +-- eth/contracts/facets/DFAdminFacet.sol | 6 +- eth/contracts/facets/DFArtifactFacet.sol | 28 +++++- eth/contracts/facets/DFGetterFacet.sol | 14 ++- eth/contracts/facets/DFMoveFacet.sol | 24 ++--- eth/contracts/libraries/LibArtifact.sol | 62 +++++++++++++ eth/contracts/libraries/LibArtifactUtils.sol | 95 ++++++++------------ eth/contracts/libraries/LibGameUtils.sol | 28 ++---- eth/contracts/libraries/LibPlanet.sol | 6 +- eth/contracts/libraries/LibSpaceship.sol | 54 +++++++++++ eth/contracts/libraries/LibUtils.sol | 42 +++++++++ eth/test/DFArtifacts.test.ts | 49 +++++++--- eth/test/utils/TestUtils.ts | 12 +-- 14 files changed, 342 insertions(+), 155 deletions(-) create mode 100644 eth/contracts/libraries/LibArtifact.sol create mode 100644 eth/contracts/libraries/LibSpaceship.sol create mode 100644 eth/contracts/libraries/LibUtils.sol diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 37173772..4262ea14 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -206,6 +206,16 @@ enum ArtifactType { PhotoidCannon, BloomFilter, BlackDomain, + // TODO; remove this + ShipMothership, + ShipCrescent, + ShipWhale, + ShipGear, + ShipTitan +} + +enum SpaceshipType { + Unknown, ShipMothership, ShipCrescent, ShipWhale, @@ -223,14 +233,14 @@ enum ArtifactRarity { } // for artifact getters -struct ArtifactWithMetadata { - ArtifactProperties artifact; - Upgrade upgrade; - Upgrade timeDelayedUpgrade; // for photoid canons specifically. - address owner; - uint256 locationId; // 0 if planet is not deposited into contract or is on a voyage - uint256 voyageId; // 0 is planet is not deposited into contract or is on a planet -} +// struct ArtifactWithMetadata { +// Artifact artifact; +// Upgrade upgrade; +// Upgrade timeDelayedUpgrade; // for photoid canons specifically. +// address owner; +// uint256 locationId; // 0 if planet is not deposited into contract or is on a voyage +// uint256 voyageId; // 0 is planet is not deposited into contract or is on a planet +// } enum Biome { Unknown, @@ -246,24 +256,45 @@ enum Biome { Corrupted } -enum TokenInfo { - Unknown, - CollectionType, // Each bin of tokens gets an id (spaceships, artifacts, etc...) - ArtifactRarity, - ArtifactType, - Biome -} +// enum TokenInfo { +// Unknown, +// TokenType, // Each bin of tokens gets an id (spaceships, artifacts, etc...) +// ArtifactRarity, +// ArtifactType, +// Biome +// } -enum CollectionType { +enum TokenType { Unknown, Artifact, Spaceship } -struct ArtifactProperties { +enum ArtifactInfo { + Unknown, + TokenType, + ArtifactRarity, + ArtifactType, + Biome +} + +struct Artifact { uint256 id; - CollectionType collectionType; + TokenType tokenType; ArtifactRarity rarity; ArtifactType artifactType; Biome planetBiome; } + +// Used for accessing properties of spaceship tokenId +enum SpaceshipInfo { + Unknown, + TokenType, + SpaceshipType +} + +struct Spaceship { + uint256 id; + TokenType tokenType; + SpaceshipType spaceshipType; +} diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index 18f077c3..1d9b2bd8 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -31,7 +31,7 @@ In `DFTypes.sol`, the `TokenInfo` enum looks like this: ```js enum TokenInfo { Unknown, - CollectionType, + TokenType, ArtifactRarity, ArtifactType, Biome @@ -40,7 +40,7 @@ enum TokenInfo { Each index in `TokenInfo` refers to a chunk in the `tokenId`. -> | CollectionType | ArtifactRarity | ArtifactType | Biome | chunk5 | ... chunk 32 | +> | TokenType | ArtifactRarity | ArtifactType | Biome | chunk5 | ... chunk 32 | Each of these chunks has 256 options. So you can have 256 Artifact Rarities, Types, Biomes etc... This should be plenty, but if you need more you just use another chunk. @@ -52,7 +52,7 @@ sell either one and they have the same buffs for planets. Thus, we can represent in this information with the following encoding: -> | CollectionType.Artifact | ArtifactRarity.Epic | ArtifactType.Monolith | Biome.Ocean | ... | +> | TokenType.Artifact | ArtifactRarity.Epic | ArtifactType.Monolith | Biome.Ocean | ... | In hex: @@ -114,7 +114,7 @@ recommended in the ERC1155 Proposal. A Mothership Spaceship is represented like so -> | CollectionType.Spaceship | ArtifactRarity.Unknown | ArtifactType.ShipMothership | Biome.Unknown +> | TokenType.Spaceship | ArtifactRarity.Unknown | ArtifactType.ShipMothership | Biome.Unknown > | ... chunk 16 | uniqueId (16 chunks) | In hex: @@ -138,7 +138,7 @@ If Velorum mints their own Mothership, it would have id: `<0x02000a00><0x02>`. Velorum's Mothership has the same TokenInfo, but a unique identifier at the end. This means the contract stores my Mothership and Velorum's Mothership as completely different collections. However, because our ships share the same first 128 bits, we can still calculate the information -about the Spaceship (ArtifactType, CollectionType) just by feeding the `decodeArtifact` function the `tokenId`. +about the Spaceship (ArtifactType, TokenType) just by feeding the `decodeArtifact` function the `tokenId`. ### Transferring diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 56dd3807..33cae68d 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -13,7 +13,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Type imports -import {ArtifactProperties, SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet} from "../DFTypes.sol"; +import {Artifact, SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet} from "../DFTypes.sol"; contract DFAdminFacet is WithStorage { event AdminOwnershipChanged(uint256 loc, address newOwner); @@ -145,7 +145,7 @@ contract DFAdminFacet is WithStorage { require(LibArtifactUtils.isSpaceship(artifactType), "artifact type must be a space ship"); uint256 shipId = LibArtifactUtils.createAndPlaceSpaceship(locationId, owner, artifactType); - ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(shipId); + Artifact memory artifact = LibArtifactUtils.decodeArtifact(shipId); Planet memory planet = gs().planets[locationId]; planet = LibPlanet.applySpaceshipArrive(artifact, planet); @@ -166,7 +166,7 @@ contract DFAdminFacet is WithStorage { } function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { - ArtifactProperties memory artifact = DFArtifactFacet(address(this)).createArtifact(args); + Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact(args); // Don't put artifact on planet if no planetId given. if (args.planetId != 0) LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); emit AdminArtifactCreated(args.owner, artifact.id, args.planetId); diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index bd762b9b..86543e16 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -10,13 +10,16 @@ import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; import {LibPermissions} from "../libraries/LibPermissions.sol"; import {LibGameUtils} from "../libraries/LibGameUtils.sol"; import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; +import {LibArtifact} from "../libraries/LibArtifact.sol"; +import {LibSpaceship} from "../libraries/LibSpaceship.sol"; + import {LibPlanet} from "../libraries/LibPlanet.sol"; // Storage imports import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {ArtifactRarity, ArtifactType, Biome, CollectionType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties, TokenInfo} from "../DFTypes.sol"; +import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtifactArgs, DFPFindArtifactArgs, Spaceship} from "../DFTypes.sol"; import "hardhat/console.sol"; @@ -66,7 +69,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { function createArtifact(DFTCreateArtifactArgs memory args) public onlyAdminOrCore - returns (ArtifactProperties memory) + returns (Artifact memory) { require(args.tokenId >= 1, "artifact id must be positive"); @@ -76,7 +79,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { return getArtifact(args.tokenId); } - function getArtifact(uint256 tokenId) public pure returns (ArtifactProperties memory) { + function getArtifact(uint256 tokenId) public pure returns (Artifact memory) { return LibArtifactUtils.decodeArtifact(tokenId); } @@ -89,6 +92,23 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { return LibArtifactUtils.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); } + function testEncodeSpaceship(Spaceship memory spaceship) public pure returns (uint256) { + return LibSpaceship.encode(spaceship); + } + + function testDecodeSpaceship(uint256 shipId) public view returns (Spaceship memory) { + return LibSpaceship.decode(shipId); + } + + function testEncodeArtifact(Artifact memory artifact) public view returns (uint256) { + console.log("biome input", uint8(artifact.planetBiome)); + return LibArtifact.encode(artifact); + } + + function testDecodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { + return LibArtifact.decode(artifactId); + } + // TODO: Add ERC1155 Enumerable Wrappers // This calls the low level _transfer call which doesn't check if the msg.sender actually owns @@ -272,7 +292,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { ) internal virtual override { uint256 length = ids.length; for (uint256 i = 0; i < length; i++) { - ArtifactProperties memory artifact = getArtifact(ids[i]); + Artifact memory artifact = getArtifact(ids[i]); // Only core contract can transfer Spaceships if (LibArtifactUtils.isSpaceship(artifact.artifactType)) { require(msg.sender == gs().diamondAddress, "player cannot transfer a Spaceship"); diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 9ae192ab..9663b247 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -13,7 +13,7 @@ import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; import {WithStorage, SnarkConstants, GameConstants} from "../libraries/LibStorage.sol"; // Type imports -import {RevealedCoords, ArtifactProperties, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, ArtifactWithMetadata, Upgrade} from "../DFTypes.sol"; +import {RevealedCoords, Artifact, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, Upgrade} from "../DFTypes.sol"; import "hardhat/console.sol"; contract DFGetterFacet is WithStorage { @@ -340,13 +340,9 @@ contract DFGetterFacet is WithStorage { // }); // } - function getArtifactsOnPlanet(uint256 locationId) - public - view - returns (ArtifactProperties[] memory ret) - { + function getArtifactsOnPlanet(uint256 locationId) public view returns (Artifact[] memory ret) { uint256[] memory artifactIds = gs().planetArtifacts[locationId]; - ret = new ArtifactProperties[](artifactIds.length); + ret = new Artifact[](artifactIds.length); for (uint256 i = 0; i < artifactIds.length; i++) { ret[i] = LibArtifactUtils.decodeArtifact(artifactIds[i]); } @@ -369,7 +365,7 @@ contract DFGetterFacet is WithStorage { function getActiveArtifactOnPlanet(uint256 locationId) public view - returns (ArtifactProperties memory ret) + returns (Artifact memory ret) { uint256 artifactId = gs().planetActiveArtifact[locationId]; return LibArtifactUtils.decodeArtifact(artifactId); @@ -398,7 +394,7 @@ contract DFGetterFacet is WithStorage { // ret = new ArtifactWithMetadata[](ids.length); // for (uint256 i = 0; i < ids.length; i++) { - // ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(ids[i]); + // Artifact memory artifact = LibArtifactUtils.decodeArtifact(ids[i]); // address owner; diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 84288977..5a06ee2b 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -15,7 +15,7 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {ArrivalData, ArrivalType, ArtifactProperties, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; +import {ArrivalData, ArrivalType, Artifact, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; import "hardhat/console.sol"; contract DFMoveFacet is WithStorage { @@ -233,7 +233,7 @@ contract DFMoveFacet is WithStorage { } } - function applySpaceshipDepart(ArtifactProperties memory artifact, Planet memory planet) + function applySpaceshipDepart(Artifact memory artifact, Planet memory planet) public view returns (Planet memory) @@ -270,9 +270,7 @@ contract DFMoveFacet is WithStorage { Undo the spaceship effects that were applied when the ship arrived on the planet. */ function _removeSpaceshipEffectsFromOriginPlanet(DFPMoveArgs memory args) private { - ArtifactProperties memory movedArtifact = LibArtifactUtils.decodeArtifact( - args.movedArtifactId - ); + Artifact memory movedArtifact = LibArtifactUtils.decodeArtifact(args.movedArtifactId); Planet memory planet = applySpaceshipDepart(movedArtifact, gs().planets[args.oldLoc]); gs().planets[args.oldLoc] = planet; } @@ -289,13 +287,9 @@ contract DFMoveFacet is WithStorage { { wormholePresent = false; - ArtifactProperties memory relevantWormhole; - ArtifactProperties memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact( - args.oldLoc - ); - ArtifactProperties memory activeArtifactTo = LibArtifactUtils.getActiveArtifact( - args.newLoc - ); + Artifact memory relevantWormhole; + Artifact memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact(args.oldLoc); + Artifact memory activeArtifactTo = LibArtifactUtils.getActiveArtifact(args.newLoc); // TODO: take the greater rarity of these, or disallow wormholes between planets that // already have a wormhole between them if ( @@ -327,9 +321,7 @@ contract DFMoveFacet is WithStorage { private returns (bool photoidPresent, Upgrade memory temporaryUpgrade) { - ArtifactProperties memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact( - args.oldLoc - ); + Artifact memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact(args.oldLoc); if ( activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= @@ -450,7 +442,7 @@ contract DFMoveFacet is WithStorage { distance: args.actualDist }); // Photoids are burned _checkPhotoid, so don't remove twice - ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(args.movedArtifactId); + Artifact memory artifact = LibArtifactUtils.decodeArtifact(args.movedArtifactId); if (args.movedArtifactId != 0 && artifact.artifactType != ArtifactType.PhotoidCannon) { LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); } diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol new file mode 100644 index 00000000..7b9e5b4f --- /dev/null +++ b/eth/contracts/libraries/LibArtifact.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +/** + * Library for all things Artifacts + */ + +// Contract imports +import "hardhat/console.sol"; + +// Library imports +import {LibUtils} from "./LibUtils.sol"; + +// Storage imports + +// Type imports +import {Artifact, ArtifactInfo, ArtifactRarity, ArtifactType, Biome, TokenType} from "../DFTypes.sol"; + +library LibArtifact { + /** + * @notice Create the token ID for a Artifact with the following properties: + * @param artifact Artifact + */ + function encode(Artifact memory artifact) internal view returns (uint256) { + // x << y is equivalent to the mathematical expression x * 2**y + uint256 tokenType = LibUtils.shiftLeft( + uint8(artifact.tokenType), + uint8(ArtifactInfo.TokenType) + ); + uint256 rarity = LibUtils.shiftLeft( + uint8(artifact.rarity), + uint8(ArtifactInfo.ArtifactRarity) + ); + uint256 artifactType = LibUtils.shiftLeft( + uint8(artifact.artifactType), + uint8(ArtifactInfo.ArtifactType) + ); + uint256 biome = LibUtils.shiftLeft(uint8(artifact.planetBiome), uint8(ArtifactInfo.Biome)); + return tokenType + rarity + artifactType + biome; + } + + function decode(uint256 artifactId) internal pure returns (Artifact memory) { + bytes memory _b = abi.encodePacked(artifactId); + uint8 tokenIdx = uint8(ArtifactInfo.TokenType) - 1; // account for Unknown at 0 + uint8 rarityIdx = uint8(ArtifactInfo.ArtifactRarity) - 1; + uint8 typeIdx = uint8(ArtifactInfo.ArtifactType) - 1; + uint8 biomeIdx = uint8(ArtifactInfo.Biome) - 1; + + uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + uint8 rarity = uint8(LibUtils.calculateByteUInt(_b, rarityIdx, rarityIdx)); + uint8 artifactType = uint8(LibUtils.calculateByteUInt(_b, typeIdx, typeIdx)); + uint8 biome = uint8(LibUtils.calculateByteUInt(_b, biomeIdx, biomeIdx)); + return + Artifact({ + id: artifactId, + tokenType: TokenType(tokenType), + rarity: ArtifactRarity(rarity), + artifactType: ArtifactType(artifactType), + planetBiome: Biome(biome) + }); + } +} diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index c2ac2c59..996f9c5c 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -7,12 +7,13 @@ import {DFGetterFacet} from "../facets/DFGetterFacet.sol"; // Library imports import {LibGameUtils} from "./LibGameUtils.sol"; +import {LibUtils} from "./LibUtils.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants} from "./LibStorage.sol"; // Type imports -import {Biome, Planet, PlanetType, ArtifactType, ArtifactRarity, CollectionType, DFPFindArtifactArgs, DFTCreateArtifactArgs, ArtifactProperties, TokenInfo} from "../DFTypes.sol"; +import {Biome, Planet, PlanetType, ArtifactType, ArtifactRarity, TokenType, DFPFindArtifactArgs, DFTCreateArtifactArgs, Artifact, ArtifactInfo} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibArtifactUtils { @@ -66,7 +67,7 @@ library LibArtifactUtils { // require(gs().miscNonce < MAX UINT 128) but won't happen. uint128 id = uint128(gs().miscNonce++); uint256 tokenId = encodeArtifact( - uint8(CollectionType.Spaceship), + uint8(TokenType.Spaceship), uint8(ArtifactRarity.Unknown), uint8(shipType), uint8(Biome.Unknown) @@ -82,7 +83,7 @@ library LibArtifactUtils { // Only used for spaceships controller: owner }); - ArtifactProperties memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( + Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( createArtifactArgs ); LibGameUtils._putArtifactOnPlanet(planetId, foundArtifact.id); @@ -115,7 +116,7 @@ library LibArtifactUtils { ); uint128 id = uint128(gs().miscNonce++); uint256 tokenId = encodeArtifact( - uint8(CollectionType.Artifact), + uint8(TokenType.Artifact), uint8(rarity), uint8(artifactType), uint8(biome) @@ -132,7 +133,7 @@ library LibArtifactUtils { address(0) ); - ArtifactProperties memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( + Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( createArtifactArgs ); @@ -152,7 +153,7 @@ library LibArtifactUtils { uint256 wormholeTo ) public { Planet storage planet = gs().planets[locationId]; - ArtifactProperties memory artifact = decodeArtifact(artifactId); + Artifact memory artifact = decodeArtifact(artifactId); require( LibGameUtils.isArtifactOnPlanet(locationId, artifactId), @@ -170,7 +171,7 @@ library LibArtifactUtils { uint256 locationId, uint256 artifactId, Planet storage planet, - ArtifactProperties memory artifact + Artifact memory artifact ) private { if (artifact.artifactType == ArtifactType.ShipCrescent) { require( @@ -209,21 +210,21 @@ library LibArtifactUtils { uint256 artifactId, uint256 wormholeTo, Planet storage planet, - ArtifactProperties memory artifact + Artifact memory artifact ) private { require( planet.owner == msg.sender, "you must own the planet you are activating an artifact on" ); require( - getActiveArtifact(locationId).collectionType == CollectionType.Unknown, + getActiveArtifact(locationId).tokenType == TokenType.Unknown, "there is already an active artifact on this planet" ); require(!planet.destroyed, "planet is destroyed"); uint256 length = gs().planetArtifacts[locationId].length; require( - getPlanetArtifact(locationId, artifactId).collectionType != CollectionType.Unknown, + getPlanetArtifact(locationId, artifactId).tokenType != TokenType.Unknown, "this artifact is not on this planet" ); @@ -282,10 +283,10 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); - ArtifactProperties memory artifact = getActiveArtifact(locationId); + Artifact memory artifact = getActiveArtifact(locationId); require( - artifact.collectionType != CollectionType.Unknown, + artifact.tokenType != TokenType.Unknown, "this artifact is not activated on this planet" ); @@ -322,7 +323,7 @@ library LibArtifactUtils { ); require(planet.owner == msg.sender, "you can only deposit on a planet you own"); - ArtifactProperties memory artifact = decodeArtifact(artifactId); + Artifact memory artifact = decodeArtifact(artifactId); require( planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to deposit this artifact" @@ -345,12 +346,9 @@ library LibArtifactUtils { ); require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.owner == msg.sender, "you can only withdraw from a planet you own"); - ArtifactProperties memory artifact = getPlanetArtifact(locationId, artifactId); + Artifact memory artifact = getPlanetArtifact(locationId, artifactId); // TODO: Write is initialized function. - require( - artifact.collectionType != CollectionType.Unknown, - "this artifact is not on this planet" - ); + require(artifact.tokenType != TokenType.Unknown, "this artifact is not on this planet"); require( planet.planetLevel > uint256(artifact.rarity), @@ -382,7 +380,7 @@ library LibArtifactUtils { uint256[] memory artifactIds = gs().planetArtifacts[locationId]; for (uint256 i = 0; i < artifactIds.length; i++) { - ArtifactProperties memory artifact = decodeArtifact(artifactIds[i]); + Artifact memory artifact = decodeArtifact(artifactIds[i]); if ( // TODO: Gear is broken artifact.artifactType == ArtifactType.ShipGear // && msg.sender == artifact.controller @@ -399,22 +397,6 @@ library LibArtifactUtils { artifactType >= ArtifactType.ShipMothership && artifactType <= ArtifactType.ShipTitan; } - /** - * @notice calculate amount of bits to shift left - * @param index number of 1 byte words to shift from left - * @return shift length of left shift - */ - function calcBitShift(uint8 index) internal pure returns (uint8) { - uint8 maxVal = 32; - - require(index <= maxVal, "shift index is too high"); - require(index > 0, "shift index is too low"); - - uint256 bin = 8; - uint256 shift = 256; - return uint8(shift - (bin * index)); - } - /** * @notice Create the collection ID for a given artifact * @param _collectionType type of artifact @@ -429,34 +411,35 @@ library LibArtifactUtils { uint256 _artifactType, uint256 _biome ) public pure returns (uint256) { - uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); - uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); - uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); - uint256 biome = _biome << calcBitShift(uint8(TokenInfo.Biome)); - return collectionType + rarity + artifactType + biome; + uint256 tokenType = _collectionType << LibUtils.calcBitShift(uint8(ArtifactInfo.TokenType)); + uint256 rarity = _rarity << LibUtils.calcBitShift(uint8(ArtifactInfo.ArtifactRarity)); + uint256 artifactType = _artifactType << + LibUtils.calcBitShift(uint8(ArtifactInfo.ArtifactType)); + uint256 biome = _biome << LibUtils.calcBitShift(uint8(ArtifactInfo.Biome)); + return tokenType + rarity + artifactType + biome; } /** - * @notice Fetch the ArtifactProperties for the given id + * @notice Fetch the Artifact for the given id * @param artifactId type of artifact */ - function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { + function decodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { bytes memory _b = abi.encodePacked(artifactId); - // 0 is left most element. 0 is given the property Unknown in TokenInfo. + // 0 is left most element. 0 is given the property Unknown in ArtifactInfo. - // Note: Bit shifting requires an index greater than zero. This is why the TokenInfo has - // Unknown as the zero property, so calcBitShift(TokenInfo.Level) is correct. + // Note: Bit shifting requires an index greater than zero. This is why the ArtifactInfo has + // Unknown as the zero property, so calcBitShift(ArtifactInfo.Level) is correct. // As a consequence, we need to // offset fetching the relevant byte from the artifactId by 1. // However - uint8 collectionType = uint8(_b[uint8(TokenInfo.CollectionType) - 1]); - uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); - uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); - uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); + uint8 tokenType = uint8(_b[uint8(ArtifactInfo.TokenType) - 1]); + uint8 rarity = uint8(_b[uint8(ArtifactInfo.ArtifactRarity) - 1]); + uint8 artifactType = uint8(_b[uint8(ArtifactInfo.ArtifactType) - 1]); + uint8 biome = uint8(_b[uint8(ArtifactInfo.Biome) - 1]); - ArtifactProperties memory a = ArtifactProperties({ + Artifact memory a = Artifact({ id: artifactId, - collectionType: CollectionType(collectionType), + tokenType: TokenType(tokenType), rarity: ArtifactRarity(rarity), artifactType: ArtifactType(artifactType), planetBiome: Biome(biome) @@ -467,18 +450,18 @@ library LibArtifactUtils { // if the given planet has an activated artifact on it, then return the artifact // otherwise, return a 'null artifact' - function getActiveArtifact(uint256 locationId) public view returns (ArtifactProperties memory) { + function getActiveArtifact(uint256 locationId) public view returns (Artifact memory) { uint256 artifactId = gs().planetActiveArtifact[locationId]; if (artifactId != 0) return decodeArtifact(artifactId); return _nullArtifactProperties(); } - function _nullArtifactProperties() private pure returns (ArtifactProperties memory) { + function _nullArtifactProperties() private pure returns (Artifact memory) { return - ArtifactProperties( + Artifact( 0, - CollectionType.Unknown, + TokenType.Unknown, ArtifactRarity.Unknown, ArtifactType.Unknown, Biome.Unknown @@ -490,7 +473,7 @@ library LibArtifactUtils { function getPlanetArtifact(uint256 locationId, uint256 artifactId) public view - returns (ArtifactProperties memory) + returns (Artifact memory) { for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 1189a7df..2053cdee 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -6,11 +6,12 @@ import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; // Library imports import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; +import {LibUtils} from "./LibUtils.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; -import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, ArtifactProperties, ArtifactType, CollectionType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; +import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactType, TokenType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibGameUtils { @@ -26,17 +27,6 @@ library LibGameUtils { return LibStorage.snarkConstants(); } - // inclusive on both ends - function _calculateByteUInt( - bytes memory _b, - uint256 _startByte, - uint256 _endByte - ) public pure returns (uint256 _byteUInt) { - for (uint256 i = _startByte; i <= _endByte; i++) { - _byteUInt += uint256(uint8(_b[i])) * (256**(_endByte - i)); - } - } - function _locationIdValid(uint256 _loc) public view returns (bool) { return (_loc < (21888242871839275222246405745257275088548364400416034343698204186575808495617 / @@ -88,7 +78,7 @@ library LibGameUtils { bytes memory _b = abi.encodePacked(_location); // get the uint value of byte 4 - 6 - uint256 _planetLevelUInt = _calculateByteUInt(_b, 4, 6); + uint256 _planetLevelUInt = LibUtils.calculateByteUInt(_b, 4, 6); uint256 level; // reverse-iterate thresholds and return planet type accordingly @@ -237,11 +227,7 @@ library LibGameUtils { }); } - function timeDelayUpgrade(ArtifactProperties memory artifact) - public - pure - returns (Upgrade memory) - { + function timeDelayUpgrade(Artifact memory artifact) public pure returns (Upgrade memory) { if (artifact.artifactType == ArtifactType.PhotoidCannon) { uint256[6] memory range = [uint256(100), 200, 200, 200, 200, 200]; uint256[6] memory speedBoosts = [uint256(100), 500, 1000, 1500, 2000, 2500]; @@ -272,11 +258,7 @@ library LibGameUtils { }); } - function _getUpgradeForArtifact(ArtifactProperties memory artifact) - public - pure - returns (Upgrade memory) - { + function _getUpgradeForArtifact(Artifact memory artifact) public pure returns (Upgrade memory) { if (artifact.artifactType == ArtifactType.PlanetaryShield) { uint256[6] memory defenseMultipliersPerRarity = [uint256(100), 150, 200, 300, 450, 650]; diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 84aa20a9..934a8c96 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -14,7 +14,7 @@ import {LibArtifactUtils} from "./LibArtifactUtils.sol"; import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; // Type imports -import {ArtifactType, ArtifactProperties, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; +import {ArtifactType, Artifact, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibPlanet { @@ -289,7 +289,7 @@ library LibPlanet { for (uint256 i = 0; i < artifactsToAdd.length; i++) { // artifactsToAdd[i] - ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(artifactsToAdd[i]); + Artifact memory artifact = LibArtifactUtils.decodeArtifact(artifactsToAdd[i]); planet = applySpaceshipArrive(artifact, planet); } @@ -299,7 +299,7 @@ library LibPlanet { return (planet, eventsToRemove, artifactsToAdd); } - function applySpaceshipArrive(ArtifactProperties memory artifact, Planet memory planet) + function applySpaceshipArrive(Artifact memory artifact, Planet memory planet) public pure returns (Planet memory) diff --git a/eth/contracts/libraries/LibSpaceship.sol b/eth/contracts/libraries/LibSpaceship.sol new file mode 100644 index 00000000..32578b37 --- /dev/null +++ b/eth/contracts/libraries/LibSpaceship.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +/** + * Library for all things Spaceships + */ + +// Contract imports +import "hardhat/console.sol"; + +// Library imports +import {LibUtils} from "./LibUtils.sol"; + +// Storage imports + +// Type imports +import {Spaceship, SpaceshipInfo, SpaceshipType, TokenType} from "../DFTypes.sol"; + +library LibSpaceship { + /** + * @notice Create the token ID for a Spaceship with the following properties: + * @param spaceship Spaceship + */ + function encode(Spaceship memory spaceship) internal pure returns (uint256) { + // x << y is equivalent to the mathematical expression x * 2**y + uint256 tokenType = LibUtils.shiftLeft( + uint8(spaceship.tokenType), + uint8(SpaceshipInfo.TokenType) + ); + uint256 shipType = LibUtils.shiftLeft( + uint8(spaceship.spaceshipType), + uint8(SpaceshipInfo.SpaceshipType) + ); + return tokenType + shipType; + } + + function decode(uint256 spaceshipId) internal view returns (Spaceship memory) { + bytes memory _b = abi.encodePacked(spaceshipId); + uint8 tokenIdx = uint8(SpaceshipInfo.TokenType) - 1; + uint8 shipInfoIdx = uint8(SpaceshipInfo.SpaceshipType) - 1; + console.log("ship info indx", shipInfoIdx); + + uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + uint8 shipType = uint8(LibUtils.calculateByteUInt(_b, shipInfoIdx, shipInfoIdx)); + console.log("tokenType", tokenType); + console.log("shipType", shipType); + return + Spaceship({ + id: spaceshipId, + tokenType: TokenType(tokenType), + spaceshipType: SpaceshipType(shipType) + }); + } +} diff --git a/eth/contracts/libraries/LibUtils.sol b/eth/contracts/libraries/LibUtils.sol new file mode 100644 index 00000000..7363a683 --- /dev/null +++ b/eth/contracts/libraries/LibUtils.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +/** + * General Purpose Utilities. Must be pure functions. + */ + +library LibUtils { + /** + * @notice calculate amount of bits to shift left + * @param index number of 1 byte words to shift from left + * @return shift length of left shift + */ + function calcBitShift(uint8 index) internal pure returns (uint8) { + uint8 maxVal = 32; + + require(index <= maxVal, "shift index is too high"); + require(index > 0, "shift index is too low"); + + uint256 bin = 8; + uint256 shift = 256; + return uint8(shift - (bin * index)); + } + + // inclusive on both ends + function calculateByteUInt( + bytes memory _b, + uint256 _startByte, + uint256 _endByte + ) internal pure returns (uint256 _byteUInt) { + for (uint256 i = _startByte; i <= _endByte; i++) { + _byteUInt += uint256(uint8(_b[i])) * (256**(_endByte - i)); + } + } + + /** + * @notice x << y where y = 2^calcBitShift(index)) + */ + function shiftLeft(uint8 value, uint8 index) internal pure returns (uint256) { + return (value * 2**calcBitShift(index)); + } +} diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index b7ed4149..594faf70 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -1,4 +1,4 @@ -import { ArtifactRarity, ArtifactType, Biome, CollectionType } from '@dfdao/types'; +import { ArtifactRarity, ArtifactType, Biome, TokenType } from '@dfdao/types'; import { loadFixture, mine } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import hre from 'hardhat'; @@ -80,7 +80,7 @@ describe('DarkForestArtifacts', function () { }); describe('it tests basic artifact actions', function () { - it('logs bits for artifact', async function () { + it.only('logs bits for artifact old', async function () { // Must be valid options const _collectionType = '0x01'; const _rarity = ArtifactRarity.Legendary; @@ -92,21 +92,46 @@ describe('DarkForestArtifacts', function () { _artifactType, _biome ); - const { collectionType, rarity, planetBiome, artifactType } = - await world.contract.getArtifact(res); - expect(collectionType).to.equal(Number(_collectionType)); + const { tokenType, rarity, planetBiome, artifactType } = await world.contract.getArtifact( + res + ); + expect(tokenType).to.equal(Number(_collectionType)); expect(rarity).to.equal(Number(_rarity)); expect(planetBiome).to.equal(Number(_biome)); expect(artifactType).to.equal(Number(_artifactType)); }); - it('logs bits for spaceship', async function () { + it('encodes and decodes artifact', async function () { // Must be valid options - const _collectionType = '0x02'; // TODO: add CollectionType to @dfdao/types - const _artifactType = ArtifactType.ShipGear; - const res = await world.contract.encodeArtifact(_collectionType, 0, _artifactType, 0); - const { collectionType, artifactType } = await world.contract.getArtifact(res); - expect(collectionType).to.equal(Number(_collectionType)); - expect(artifactType).to.equal(Number(_artifactType)); + const tokenType = TokenType.Artifact; + const rarity = ArtifactRarity.Legendary; + const artifactType = ArtifactType.Colossus; + const planetBiome = Biome.DESERT; + const res = await world.contract.testEncodeArtifact({ + id: 0, + tokenType, + rarity, + artifactType, + planetBiome, + }); + const a = await world.contract.getArtifact(res); + expect(tokenType).to.equal(Number(a.tokenType)); + expect(rarity).to.equal(Number(a.rarity)); + expect(artifactType).to.equal(Number(a.artifactType)); + expect(planetBiome).to.equal(Number(a.planetBiome)); + }); + it('encodes and decodes spaceship', async function () { + // Must be valid options + const tokenType = TokenType.Spaceship; + const spaceshipType = 2; + const res = await world.contract.testEncodeSpaceship({ + id: 0, + tokenType, + spaceshipType, + }); + const a = await world.contract.testDecodeSpaceship(res); + console.log(a); + expect(tokenType).to.equal(Number(a.tokenType)); + expect(spaceshipType).to.equal(Number(a.spaceshipType)); }); // This test will fail if the artifact is special. it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index 565df44c..d0ce72cd 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -1,5 +1,5 @@ import type { DarkForest } from '@dfdao/contracts/typechain'; -import { ArtifactPropertiesStructOutput } from '@dfdao/contracts/typechain/contracts/DFToken'; +import { ArtifactStructOutput } from '@dfdao/contracts/typechain/hardhat-diamond-abi/HardhatDiamondABI.sol/DarkForest'; import { modPBigInt } from '@dfdao/hashing'; import { buildContractCallArgs, @@ -15,8 +15,8 @@ import { ArtifactTypeNames, Biome, BiomeNames, - CollectionType, - CollectionTypeNames, + TokenType, + TokenTypeNames, } from '@dfdao/types'; import { bigIntFromKey } from '@dfdao/whitelist'; import { mine, time } from '@nomicfoundation/hardhat-network-helpers'; @@ -45,9 +45,9 @@ export function hexToBigNumber(hex: string): BigNumber { return BigNumber.from(`0x${hex}`); } -export function prettyPrintToken(token: ArtifactPropertiesStructOutput) { +export function prettyPrintToken(token: ArtifactStructOutput) { console.log( - `~Token~\nCollection: ${CollectionTypeNames[token.collectionType]}\nRarity: ${ + `~Token~\nCollection: ${TokenTypeNames[token.tokenType]}\nRarity: ${ ArtifactRarityNames[token.rarity] }\nType: ${ArtifactTypeNames[token.artifactType]}\nBiome: ${BiomeNames[token.planetBiome]}` ); @@ -333,7 +333,7 @@ export async function createArtifact( owner: string, planet: TestLocation, type: ArtifactType, - collectionType = CollectionType.Artifact, + collectionType = TokenType.Artifact, { rarity, biome }: { rarity?: ArtifactRarity; biome?: Biome } = {} ) { rarity ||= ArtifactRarity.Common; From 2ee636524a45352c0de90f142a9e313da1606871 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Fri, 30 Sep 2022 11:40:08 +0100 Subject: [PATCH 28/55] rename collection type --- packages/types/src/token.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/types/src/token.ts b/packages/types/src/token.ts index 258e48da..0096ec49 100644 --- a/packages/types/src/token.ts +++ b/packages/types/src/token.ts @@ -1,18 +1,18 @@ import { Abstract } from './utility'; -export type CollectionType = Abstract; +export type TokenType = Abstract; -export const CollectionType = { - Unknown: 0 as CollectionType, - Artifact: 1 as CollectionType, - Spaceship: 2 as CollectionType, +export const TokenType = { + Unknown: 0 as TokenType, + Artifact: 1 as TokenType, + Spaceship: 2 as TokenType, } as const; /** - * Mapping from CollectionType to pretty-printed names. + * Mapping from TokenType to pretty-printed names. */ -export const CollectionTypeNames = { - [CollectionType.Unknown]: 'Unknown', - [CollectionType.Artifact]: 'Artifact', - [CollectionType.Spaceship]: 'Spaceship', +export const TokenTypeNames = { + [TokenType.Unknown]: 'Unknown', + [TokenType.Artifact]: 'Artifact', + [TokenType.Spaceship]: 'Spaceship', } as const; From cf3e2138148fa42b55efa3ebe233196ad141ed71 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Fri, 30 Sep 2022 17:57:56 +0100 Subject: [PATCH 29/55] feat: tests pass with spaceships as separate entities --- eth/contracts/facets/DFAdminFacet.sol | 36 ++++-- eth/contracts/facets/DFArtifactFacet.sol | 52 +++++--- eth/contracts/facets/DFGetterFacet.sol | 38 ++++-- eth/contracts/facets/DFMoveFacet.sol | 38 +++--- eth/contracts/libraries/LibArtifact.sol | 9 +- eth/contracts/libraries/LibArtifactUtils.sol | 127 +++++++++---------- eth/contracts/libraries/LibGameUtils.sol | 40 +++++- eth/contracts/libraries/LibPlanet.sol | 33 +++-- eth/contracts/libraries/LibSpaceship.sol | 14 +- eth/contracts/libraries/LibStorage.sol | 3 +- eth/tasks/deploy.ts | 2 +- eth/test/DFArtifacts.test.ts | 33 ++--- eth/test/DFMove.test.ts | 41 +++--- eth/test/DFSpaceShips.test.ts | 37 +++--- eth/test/utils/TestUtils.ts | 11 ++ packages/types/src/index.ts | 1 + 16 files changed, 317 insertions(+), 198 deletions(-) diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 33cae68d..dd1877b9 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -2,10 +2,12 @@ pragma solidity ^0.8.0; // Library imports -import {LibPermissions} from "../libraries/LibPermissions.sol"; +import {LibArtifact} from "../libraries/LibArtifact.sol"; +import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; import {LibGameUtils} from "../libraries/LibGameUtils.sol"; +import {LibPermissions} from "../libraries/LibPermissions.sol"; import {LibPlanet} from "../libraries/LibPlanet.sol"; -import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; +import {LibSpaceship} from "../libraries/LibSpaceship.sol"; // Storage imports import {WithStorage} from "../libraries/LibStorage.sol"; @@ -13,12 +15,12 @@ import {WithStorage} from "../libraries/LibStorage.sol"; import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Type imports -import {Artifact, SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet} from "../DFTypes.sol"; +import {Artifact, SpaceType, Spaceship, SpaceshipType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet, TokenType} from "../DFTypes.sol"; contract DFAdminFacet is WithStorage { event AdminOwnershipChanged(uint256 loc, address newOwner); event AdminPlanetCreated(uint256 loc); - event AdminGiveSpaceship(uint256 loc, address owner, ArtifactType artifactType); + event AdminGiveSpaceship(uint256 loc, address owner, SpaceshipType shipType); event PauseStateChanged(bool paused); event AdminArtifactCreated(address player, uint256 artifactId, uint256 loc); @@ -139,20 +141,19 @@ contract DFAdminFacet is WithStorage { function adminGiveSpaceShip( uint256 locationId, address owner, - ArtifactType artifactType + SpaceshipType shipType ) public onlyAdmin { require(gs().planets[locationId].isInitialized, "planet is not initialized"); - require(LibArtifactUtils.isSpaceship(artifactType), "artifact type must be a space ship"); - uint256 shipId = LibArtifactUtils.createAndPlaceSpaceship(locationId, owner, artifactType); - Artifact memory artifact = LibArtifactUtils.decodeArtifact(shipId); + uint256 shipId = LibArtifactUtils.createAndPlaceSpaceship(locationId, owner, shipType); + Spaceship memory spaceship = LibSpaceship.decode(shipId); Planet memory planet = gs().planets[locationId]; - planet = LibPlanet.applySpaceshipArrive(artifact, planet); + planet = LibPlanet.applySpaceshipArrive(spaceship, planet); gs().planets[locationId] = planet; - emit AdminGiveSpaceship(locationId, owner, artifactType); + emit AdminGiveSpaceship(locationId, owner, shipType); } function adminInitializePlanet(uint256 locationId, uint256 perlin) public onlyAdmin { @@ -166,7 +167,20 @@ contract DFAdminFacet is WithStorage { } function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { - Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact(args); + // Note: calling this in tests should supply Diamond address as args.owner + uint256 tokenId = LibArtifact.encode( + Artifact({ + id: 0, + tokenType: TokenType.Artifact, + rarity: args.rarity, + artifactType: args.artifactType, + planetBiome: args.biome + }) + ); + Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact( + tokenId, + args.owner + ); // Don't put artifact on planet if no planetId given. if (args.planetId != 0) LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); emit AdminArtifactCreated(args.owner, artifact.id, args.planetId); diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 86543e16..d668bf90 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -19,7 +19,7 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtifactArgs, DFPFindArtifactArgs, Spaceship} from "../DFTypes.sol"; +import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtifactArgs, DFPFindArtifactArgs, Spaceship, SpaceshipType} from "../DFTypes.sol"; import "hardhat/console.sol"; @@ -66,21 +66,39 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { _; } - function createArtifact(DFTCreateArtifactArgs memory args) + function createArtifact(uint256 tokenId, address owner) public onlyAdminOrCore returns (Artifact memory) { - require(args.tokenId >= 1, "artifact id must be positive"); + require(tokenId >= 1, "token id must be positive"); + require(LibArtifact.isArtifact(tokenId), "wrong token type"); + // Account, Id, Amount, Data + _mint(owner, tokenId, 1, ""); + + return getArtifact(tokenId); + } + + function createSpaceship(uint256 tokenId, address owner) + public + onlyAdminOrCore + returns (Spaceship memory) + { + require(tokenId >= 1, "token id must be positive"); + require(LibSpaceship.isShip(tokenId), "wrong token type"); // Account, Id, Amount, Data - _mint(args.owner, args.tokenId, 1, ""); + _mint(owner, tokenId, 1, ""); - return getArtifact(args.tokenId); + return getSpaceship(tokenId); } - function getArtifact(uint256 tokenId) public pure returns (Artifact memory) { - return LibArtifactUtils.decodeArtifact(tokenId); + function getArtifact(uint256 artifactId) public pure returns (Artifact memory) { + return LibArtifact.decode(artifactId); + } + + function getSpaceship(uint256 shipId) public pure returns (Spaceship memory) { + return LibSpaceship.decode(shipId); } function encodeArtifact( @@ -96,12 +114,11 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { return LibSpaceship.encode(spaceship); } - function testDecodeSpaceship(uint256 shipId) public view returns (Spaceship memory) { + function testDecodeSpaceship(uint256 shipId) public pure returns (Spaceship memory) { return LibSpaceship.decode(shipId); } - function testEncodeArtifact(Artifact memory artifact) public view returns (uint256) { - console.log("biome input", uint8(artifact.planetBiome)); + function testEncodeArtifact(Artifact memory artifact) public pure returns (uint256) { return LibArtifact.encode(artifact); } @@ -127,7 +144,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { } } - function doesArtifactExist(address owner, uint256 tokenId) public view returns (bool) { + function tokenExists(address owner, uint256 tokenId) public view returns (bool) { return balanceOf(owner, tokenId) > 0; } @@ -237,7 +254,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { uint256 id1 = LibArtifactUtils.createAndPlaceSpaceship( locationId, owner, - ArtifactType.ShipMothership + SpaceshipType.ShipMothership ); emit ArtifactFound(msg.sender, id1, locationId); } @@ -246,7 +263,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { uint256 id2 = LibArtifactUtils.createAndPlaceSpaceship( locationId, owner, - ArtifactType.ShipCrescent + SpaceshipType.ShipCrescent ); emit ArtifactFound(msg.sender, id2, locationId); } @@ -255,7 +272,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { uint256 id3 = LibArtifactUtils.createAndPlaceSpaceship( locationId, owner, - ArtifactType.ShipWhale + SpaceshipType.ShipWhale ); emit ArtifactFound(msg.sender, id3, locationId); } @@ -264,7 +281,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { uint256 id4 = LibArtifactUtils.createAndPlaceSpaceship( locationId, owner, - ArtifactType.ShipGear + SpaceshipType.ShipGear ); emit ArtifactFound(msg.sender, id4, locationId); } @@ -273,7 +290,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { uint256 id5 = LibArtifactUtils.createAndPlaceSpaceship( locationId, owner, - ArtifactType.ShipTitan + SpaceshipType.ShipTitan ); emit ArtifactFound(msg.sender, id5, locationId); @@ -292,9 +309,8 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { ) internal virtual override { uint256 length = ids.length; for (uint256 i = 0; i < length; i++) { - Artifact memory artifact = getArtifact(ids[i]); // Only core contract can transfer Spaceships - if (LibArtifactUtils.isSpaceship(artifact.artifactType)) { + if (LibSpaceship.isShip(ids[i])) { require(msg.sender == gs().diamondAddress, "player cannot transfer a Spaceship"); } } diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 9663b247..0865c634 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -5,15 +5,17 @@ pragma solidity ^0.8.0; import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Library imports -import {LibPermissions} from "../libraries/LibPermissions.sol"; -import {LibGameUtils} from "../libraries/LibGameUtils.sol"; +import {LibArtifact} from "../libraries/LibArtifact.sol"; import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; +import {LibGameUtils} from "../libraries/LibGameUtils.sol"; +import {LibPermissions} from "../libraries/LibPermissions.sol"; +import {LibSpaceship} from "../libraries/LibSpaceship.sol"; // Storage imports import {WithStorage, SnarkConstants, GameConstants} from "../libraries/LibStorage.sol"; // Type imports -import {RevealedCoords, Artifact, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, Upgrade} from "../DFTypes.sol"; +import {RevealedCoords, Artifact, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, Upgrade, Spaceship} from "../DFTypes.sol"; import "hardhat/console.sol"; contract DFGetterFacet is WithStorage { @@ -94,6 +96,10 @@ contract DFGetterFacet is WithStorage { return gs().planetArtifacts[key]; } + function planetSpaceships(uint256 key) public view returns (uint256[] memory) { + return gs().planetSpaceships[key]; + } + // ADDITIONAL UTILITY GETTERS function getNPlanets() public view returns (uint256) { @@ -344,22 +350,36 @@ contract DFGetterFacet is WithStorage { uint256[] memory artifactIds = gs().planetArtifacts[locationId]; ret = new Artifact[](artifactIds.length); for (uint256 i = 0; i < artifactIds.length; i++) { - ret[i] = LibArtifactUtils.decodeArtifact(artifactIds[i]); + ret[i] = LibArtifact.decode(artifactIds[i]); } return ret; } - function artifactExistsOnPlanet(uint256 locationId, uint256 artifactId) + function getSpaceshipsOnPlanet(uint256 locationId) public view - returns (bool) + returns (Spaceship[] memory ret) { - bool hasArtifact = false; + uint256[] memory tokenIds = gs().planetSpaceships[locationId]; + ret = new Spaceship[](tokenIds.length); + for (uint256 i = 0; i < tokenIds.length; i++) { + ret[i] = LibSpaceship.decode(tokenIds[i]); + } + return ret; + } + + // Combo on Ships and Artifacts + function tokenExistsOnPlanet(uint256 locationId, uint256 tokenId) public view returns (bool) { + bool hasToken = false; uint256[] memory artifactIds = gs().planetArtifacts[locationId]; for (uint256 i = 0; i < artifactIds.length; i++) { - if (artifactIds[i] == artifactId) hasArtifact = true; + if (artifactIds[i] == tokenId) hasToken = true; + } + uint256[] memory shipIds = gs().planetSpaceships[locationId]; + for (uint256 i = 0; i < shipIds.length; i++) { + if (shipIds[i] == tokenId) hasToken = true; } - return hasArtifact; + return hasToken; } function getActiveArtifactOnPlanet(uint256 locationId) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 5a06ee2b..9a884847 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -6,10 +6,12 @@ import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Library imports +import {LibArtifact} from "../libraries/LibArtifact.sol"; import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; import {LibGameUtils} from "../libraries/LibGameUtils.sol"; import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; import {LibPlanet} from "../libraries/LibPlanet.sol"; +import {LibSpaceship} from "../libraries/LibSpaceship.sol"; // Storage imports import {WithStorage} from "../libraries/LibStorage.sol"; @@ -200,8 +202,7 @@ contract DFMoveFacet is WithStorage { require(args.popMoved == 0, "ship moves must move 0 energy"); require(args.silverMoved == 0, "ship moves must move 0 silver"); require( - // TODO: better name for doesArtifactExist function - DFArtifactFacet(address(this)).doesArtifactExist(msg.sender, args.movedArtifactId), + DFArtifactFacet(address(this)).tokenExists(msg.sender, args.movedArtifactId), "you can only move your own ships" ); } else { @@ -227,8 +228,10 @@ contract DFMoveFacet is WithStorage { if (args.movedArtifactId != 0) { require( - gs().planetArtifacts[args.newLoc].length < 5, - "too many artifacts on this planet" + gs().planetArtifacts[args.newLoc].length + + gs().planetSpaceships[args.newLoc].length < + 5, + "too many tokens on this planet" ); } } @@ -405,11 +408,8 @@ contract DFMoveFacet is WithStorage { } } - function _isSpaceshipMove(DFPMoveArgs memory args) private view returns (bool) { - return - LibArtifactUtils.isSpaceship( - LibArtifactUtils.decodeArtifact(args.movedArtifactId).artifactType - ); + function _isSpaceshipMove(DFPMoveArgs memory args) private pure returns (bool) { + return LibSpaceship.isShip(args.movedArtifactId); } function _createArrival(DFPCreateArrivalArgs memory args) private { @@ -422,9 +422,9 @@ contract DFMoveFacet is WithStorage { planet.range, planet.populationCap ); - bool isSpaceship = LibArtifactUtils.isSpaceship( - LibArtifactUtils.decodeArtifact(args.movedArtifactId).artifactType - ); + + bool isSpaceship = LibSpaceship.isShip(args.movedArtifactId); + // space ship moves are implemented as 0-energy moves require(popArriving > 0 || isSpaceship, "Not enough forces to make move"); require(isSpaceship ? args.popMoved == 0 : true, "spaceship moves must be 0 energy moves"); @@ -441,10 +441,16 @@ contract DFMoveFacet is WithStorage { carriedArtifactId: args.movedArtifactId, distance: args.actualDist }); - // Photoids are burned _checkPhotoid, so don't remove twice - Artifact memory artifact = LibArtifactUtils.decodeArtifact(args.movedArtifactId); - if (args.movedArtifactId != 0 && artifact.artifactType != ArtifactType.PhotoidCannon) { - LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); + // Photoids are burned in _checkPhotoid, so don't remove twice + if (args.movedArtifactId != 0) { + if (isSpaceship) { + LibGameUtils._takeSpaceshipOffPlanet(args.oldLoc, args.movedArtifactId); + } else { + Artifact memory artifact = LibArtifact.decode(args.movedArtifactId); + if (artifact.artifactType != ArtifactType.PhotoidCannon) { + LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); + } + } } } diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index 7b9e5b4f..f09a0435 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -21,7 +21,7 @@ library LibArtifact { * @notice Create the token ID for a Artifact with the following properties: * @param artifact Artifact */ - function encode(Artifact memory artifact) internal view returns (uint256) { + function encode(Artifact memory artifact) internal pure returns (uint256) { // x << y is equivalent to the mathematical expression x * 2**y uint256 tokenType = LibUtils.shiftLeft( uint8(artifact.tokenType), @@ -59,4 +59,11 @@ library LibArtifact { planetBiome: Biome(biome) }); } + + function isArtifact(uint256 tokenId) internal pure returns (bool) { + bytes memory _b = abi.encodePacked(tokenId); + uint8 tokenIdx = uint8(ArtifactInfo.TokenType) - 1; + uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + return (TokenType(tokenType) == TokenType.Artifact); + } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 996f9c5c..9804d113 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -6,14 +6,16 @@ import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; import {DFGetterFacet} from "../facets/DFGetterFacet.sol"; // Library imports +import {LibArtifact} from "./LibArtifact.sol"; import {LibGameUtils} from "./LibGameUtils.sol"; +import {LibSpaceship} from "./LibSpaceship.sol"; import {LibUtils} from "./LibUtils.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants} from "./LibStorage.sol"; // Type imports -import {Biome, Planet, PlanetType, ArtifactType, ArtifactRarity, TokenType, DFPFindArtifactArgs, DFTCreateArtifactArgs, Artifact, ArtifactInfo} from "../DFTypes.sol"; +import {Biome, Planet, PlanetType, ArtifactType, ArtifactRarity, TokenType, DFPFindArtifactArgs, DFTCreateArtifactArgs, Artifact, ArtifactInfo, Spaceship, SpaceshipType} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibArtifactUtils { @@ -61,34 +63,22 @@ library LibArtifactUtils { function createAndPlaceSpaceship( uint256 planetId, address owner, - ArtifactType shipType + SpaceshipType shipType ) public returns (uint256) { - require(shipType <= ArtifactType.ShipTitan && shipType >= ArtifactType.ShipMothership); + require(shipType != SpaceshipType.Unknown, "incorrect ship type"); // require(gs().miscNonce < MAX UINT 128) but won't happen. uint128 id = uint128(gs().miscNonce++); - uint256 tokenId = encodeArtifact( - uint8(TokenType.Spaceship), - uint8(ArtifactRarity.Unknown), - uint8(shipType), - uint8(Biome.Unknown) + uint256 tokenId = LibSpaceship.encode( + Spaceship({id: 0, tokenType: TokenType.Spaceship, spaceshipType: shipType}) ); - DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs({ - tokenId: tokenId + id, - discoverer: msg.sender, - planetId: planetId, - rarity: ArtifactRarity.Unknown, - biome: Biome.Unknown, - artifactType: shipType, - owner: owner, - // Only used for spaceships - controller: owner - }); - Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( - createArtifactArgs + + Spaceship memory spaceship = DFArtifactFacet(address(this)).createSpaceship( + tokenId + id, // Make each ship unique + owner ); - LibGameUtils._putArtifactOnPlanet(planetId, foundArtifact.id); + LibGameUtils._putSpaceshipOnPlanet(planetId, spaceship.id); - return tokenId; + return spaceship.id; } function findArtifact(DFPFindArtifactArgs memory args) public returns (uint256 artifactId) { @@ -114,27 +104,19 @@ library LibArtifactUtils { ArtifactRarity rarity = LibGameUtils.artifactRarityFromPlanetLevel( levelBonus + planet.planetLevel ); - uint128 id = uint128(gs().miscNonce++); - uint256 tokenId = encodeArtifact( - uint8(TokenType.Artifact), - uint8(rarity), - uint8(artifactType), - uint8(biome) - ); - - DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( - tokenId + id, - msg.sender, // discoverer - args.planetId, - rarity, - biome, - artifactType, - args.coreAddress, // owner - address(0) + uint256 tokenId = LibArtifact.encode( + Artifact({ + id: 0, + tokenType: TokenType.Artifact, + rarity: rarity, + artifactType: artifactType, + planetBiome: biome + }) ); Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( - createArtifactArgs + tokenId, + args.coreAddress ); LibGameUtils._putArtifactOnPlanet(args.planetId, foundArtifact.id); @@ -155,25 +137,28 @@ library LibArtifactUtils { Planet storage planet = gs().planets[locationId]; Artifact memory artifact = decodeArtifact(artifactId); - require( - LibGameUtils.isArtifactOnPlanet(locationId, artifactId), - "can't active an artifact on a planet it's not on" - ); - - if (isSpaceship(artifact.artifactType)) { - activateSpaceshipArtifact(locationId, artifactId, planet, artifact); + if (LibSpaceship.isShip(artifactId)) { + require( + LibGameUtils.isSpaceshipOnPlanet(locationId, artifactId), + "can't activate a ship on a planet it's not on" + ); + activateSpaceshipArtifact(locationId, artifactId, planet); } else { + require( + LibGameUtils.isArtifactOnPlanet(locationId, artifactId), + "can't activate an artifact on a planet it's not on" + ); activateNonSpaceshipArtifact(locationId, artifactId, wormholeTo, planet, artifact); } } function activateSpaceshipArtifact( uint256 locationId, - uint256 artifactId, - Planet storage planet, - Artifact memory artifact + uint256 shipId, + Planet storage planet ) private { - if (artifact.artifactType == ArtifactType.ShipCrescent) { + Spaceship memory s = LibSpaceship.decode(shipId); + if (s.spaceshipType == SpaceshipType.ShipCrescent) { require( planet.planetType != PlanetType.SILVER_MINE, "cannot turn a silver mine into a silver mine" @@ -196,12 +181,12 @@ library LibArtifactUtils { } planet.planetType = PlanetType.SILVER_MINE; - emit ArtifactActivated(msg.sender, locationId, artifactId); + emit ArtifactActivated(msg.sender, locationId, shipId); // TODO: Why not actually burn? // burn it after use. will be owned by contract but not on a planet anyone can control - LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); - emit ArtifactDeactivated(msg.sender, locationId, artifactId); + LibGameUtils._takeSpaceshipOffPlanet(locationId, shipId); + emit ArtifactDeactivated(msg.sender, locationId, shipId); } } @@ -222,7 +207,6 @@ library LibArtifactUtils { ); require(!planet.destroyed, "planet is destroyed"); - uint256 length = gs().planetArtifacts[locationId].length; require( getPlanetArtifact(locationId, artifactId).tokenType != TokenType.Unknown, "this artifact is not on this planet" @@ -318,7 +302,7 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.planetType == PlanetType.TRADING_POST, "can only deposit on trading posts"); require( - DFArtifactFacet(address(this)).balanceOf(msg.sender, artifactId) > 0, + DFArtifactFacet(address(this)).tokenExists(msg.sender, artifactId), "you can only deposit artifacts you own" ); require(planet.owner == msg.sender, "you can only deposit on a planet you own"); @@ -328,9 +312,12 @@ library LibArtifactUtils { planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to deposit this artifact" ); - require(!isSpaceship(artifact.artifactType), "cannot deposit spaceships"); + require(!LibSpaceship.isShip(artifactId), "cannot deposit spaceships"); - require(gs().planetArtifacts[locationId].length < 5, "too many artifacts on this planet"); + require( + gs().planetArtifacts[locationId].length + gs().planetSpaceships[locationId].length < 5, + "too many tokens on this planet" + ); LibGameUtils._putArtifactOnPlanet(locationId, artifactId); // artifactId, curr owner, new owner @@ -347,8 +334,6 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.owner == msg.sender, "you can only withdraw from a planet you own"); Artifact memory artifact = getPlanetArtifact(locationId, artifactId); - // TODO: Write is initialized function. - require(artifact.tokenType != TokenType.Unknown, "this artifact is not on this planet"); require( planet.planetLevel > uint256(artifact.rarity), @@ -377,13 +362,12 @@ library LibArtifactUtils { } function containsGear(uint256 locationId) public view returns (bool) { - uint256[] memory artifactIds = gs().planetArtifacts[locationId]; - - for (uint256 i = 0; i < artifactIds.length; i++) { - Artifact memory artifact = decodeArtifact(artifactIds[i]); + uint256[] memory tokenIds = gs().planetSpaceships[locationId]; + for (uint256 i = 0; i < tokenIds.length; i++) { + Spaceship memory spaceship = LibSpaceship.decode(tokenIds[i]); if ( - // TODO: Gear is broken - artifact.artifactType == ArtifactType.ShipGear // && msg.sender == artifact.controller + spaceship.spaceshipType == SpaceshipType.ShipGear && + DFArtifactFacet(address(this)).tokenExists(msg.sender, tokenIds[i]) ) { return true; } @@ -473,14 +457,17 @@ library LibArtifactUtils { function getPlanetArtifact(uint256 locationId, uint256 artifactId) public view - returns (Artifact memory) + returns (Artifact memory a) { + bool found = false; for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { - return decodeArtifact(artifactId); + a = decodeArtifact(artifactId); + found = true; + return a; } } - return _nullArtifactProperties(); + require(found, "artifact not found"); } } diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 2053cdee..1404ac96 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -375,7 +375,10 @@ library LibGameUtils { // put on. note that this function does not transfer the artifact. function _putArtifactOnPlanet(uint256 locationId, uint256 artifactId) public { gs().planetArtifacts[locationId].push(artifactId); - uint256 length = gs().planetArtifacts[locationId].length; + } + + function _putSpaceshipOnPlanet(uint256 locationId, uint256 spaceshipId) public { + gs().planetSpaceships[locationId].push(spaceshipId); } // TODO: Why not burn ? @@ -416,6 +419,31 @@ library LibGameUtils { gs().planetArtifacts[locationId].pop(); } + function _takeSpaceshipOffPlanet(uint256 locationId, uint256 spaceshipId) public { + uint256 shipsOnThisPlanet = gs().planetSpaceships[locationId].length; + + bool hadTheShip = false; + + for (uint256 i = 0; i < shipsOnThisPlanet; i++) { + if (gs().planetSpaceships[locationId][i] == spaceshipId) { + require( + !isActivated(locationId, spaceshipId), + "you cannot take an activated spaceship off a planet" + ); + + gs().planetSpaceships[locationId][i] = gs().planetSpaceships[locationId][ + shipsOnThisPlanet - 1 + ]; + + hadTheShip = true; + break; + } + } + + require(hadTheShip, "this ship was not present on this planet"); + gs().planetSpaceships[locationId].pop(); + } + // an artifact is only considered 'activated' if this method returns true. // we do not have an `isActive` field on artifact; the times that the // artifact was last activated and deactivated are sufficent to determine @@ -435,6 +463,16 @@ library LibGameUtils { return false; } + function isSpaceshipOnPlanet(uint256 locationId, uint256 shipId) public view returns (bool) { + for (uint256 i; i < gs().planetSpaceships[locationId].length; i++) { + if (gs().planetSpaceships[locationId][i] == shipId) { + return true; + } + } + + return false; + } + // the space junk that a planet starts with function getPlanetDefaultSpaceJunk(Planet memory planet) public view returns (uint256) { if (planet.isHomePlanet) return 0; diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 934a8c96..8c61543b 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -6,15 +6,17 @@ import {DFVerifierFacet} from "../facets/DFVerifierFacet.sol"; import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; // Library imports +import {LibArtifact} from "./LibArtifact.sol"; +import {LibArtifactUtils} from "./LibArtifactUtils.sol"; import {LibGameUtils} from "./LibGameUtils.sol"; import {LibLazyUpdate} from "./LibLazyUpdate.sol"; -import {LibArtifactUtils} from "./LibArtifactUtils.sol"; +import {LibSpaceship} from "./LibSpaceship.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; // Type imports -import {ArtifactType, Artifact, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; +import {ArtifactType, Artifact, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Spaceship, SpaceshipType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibPlanet { @@ -288,10 +290,11 @@ library LibPlanet { } for (uint256 i = 0; i < artifactsToAdd.length; i++) { - // artifactsToAdd[i] - Artifact memory artifact = LibArtifactUtils.decodeArtifact(artifactsToAdd[i]); - - planet = applySpaceshipArrive(artifact, planet); + // Only apply Spaceship arrival if ship is a spaceship. + if (LibSpaceship.isShip(artifactsToAdd[i])) { + Spaceship memory spaceship = LibSpaceship.decode(artifactsToAdd[i]); + planet = applySpaceshipArrive(spaceship, planet); + } } planet = LibLazyUpdate.updatePlanet(timestamp, planet); @@ -299,7 +302,7 @@ library LibPlanet { return (planet, eventsToRemove, artifactsToAdd); } - function applySpaceshipArrive(Artifact memory artifact, Planet memory planet) + function applySpaceshipArrive(Spaceship memory spaceship, Planet memory planet) public pure returns (Planet memory) @@ -308,17 +311,17 @@ library LibPlanet { return planet; } - if (artifact.artifactType == ArtifactType.ShipMothership) { + if (spaceship.spaceshipType == SpaceshipType.ShipMothership) { if (planet.energyGroDoublers == 0) { planet.populationGrowth *= 2; } planet.energyGroDoublers++; - } else if (artifact.artifactType == ArtifactType.ShipWhale) { + } else if (spaceship.spaceshipType == SpaceshipType.ShipWhale) { if (planet.silverGroDoublers == 0) { planet.silverGrowth *= 2; } planet.silverGroDoublers++; - } else if (artifact.artifactType == ArtifactType.ShipTitan) { + } else if (spaceship.spaceshipType == SpaceshipType.ShipTitan) { planet.pausers++; } @@ -331,7 +334,7 @@ library LibPlanet { ( Planet memory planet, uint256[12] memory eventsToRemove, - uint256[12] memory artifactIdsToAddToPlanet + uint256[12] memory tokenIdsToAddToPlanet ) = getRefreshedPlanet(location, block.timestamp); gs().planets[location] = planet; @@ -348,8 +351,12 @@ library LibPlanet { } for (uint256 i = 0; i < 12; i++) { - if (artifactIdsToAddToPlanet[i] != 0) { - LibGameUtils._putArtifactOnPlanet(location, artifactIdsToAddToPlanet[i]); + if (tokenIdsToAddToPlanet[i] != 0) { + if (LibSpaceship.isShip(tokenIdsToAddToPlanet[i])) { + LibGameUtils._putSpaceshipOnPlanet(location, tokenIdsToAddToPlanet[i]); + } else if (LibArtifact.isArtifact(tokenIdsToAddToPlanet[i])) { + LibGameUtils._putArtifactOnPlanet(location, tokenIdsToAddToPlanet[i]); + } } } } diff --git a/eth/contracts/libraries/LibSpaceship.sol b/eth/contracts/libraries/LibSpaceship.sol index 32578b37..09bf5683 100644 --- a/eth/contracts/libraries/LibSpaceship.sol +++ b/eth/contracts/libraries/LibSpaceship.sol @@ -34,16 +34,15 @@ library LibSpaceship { return tokenType + shipType; } - function decode(uint256 spaceshipId) internal view returns (Spaceship memory) { + function decode(uint256 spaceshipId) internal pure returns (Spaceship memory) { bytes memory _b = abi.encodePacked(spaceshipId); + // Idx is subtracted by one because each Info enum has Unknown at the zero location. uint8 tokenIdx = uint8(SpaceshipInfo.TokenType) - 1; uint8 shipInfoIdx = uint8(SpaceshipInfo.SpaceshipType) - 1; - console.log("ship info indx", shipInfoIdx); uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); uint8 shipType = uint8(LibUtils.calculateByteUInt(_b, shipInfoIdx, shipInfoIdx)); - console.log("tokenType", tokenType); - console.log("shipType", shipType); + return Spaceship({ id: spaceshipId, @@ -51,4 +50,11 @@ library LibSpaceship { spaceshipType: SpaceshipType(shipType) }); } + + function isShip(uint256 tokenId) internal pure returns (bool) { + bytes memory _b = abi.encodePacked(tokenId); + uint8 tokenIdx = uint8(SpaceshipInfo.TokenType) - 1; + uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + return (TokenType(tokenType) == TokenType.Spaceship); + } } diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index 58ee97a3..279f10bd 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -43,8 +43,9 @@ struct GameStorage { mapping(uint256 => PlanetEventMetadata[]) planetEvents; // maps event id to arrival data mapping(uint256 => ArrivalData) planetArrivals; - // Artifact stuff + // Token stuff mapping(uint256 => uint256[]) planetArtifacts; + mapping(uint256 => uint256[]) planetSpaceships; mapping(uint256 => uint256) planetActiveArtifact; // wormhole from => to. planetWormHoles[from] = to; mapping(uint256 => uint256) planetWormholes; diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index a1c90c51..431b5ae0 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -415,7 +415,7 @@ export async function deployLibraries({}, hre: HardhatRuntimeEnvironment) { libraries: { LibGameUtils: LibGameUtils.address, LibLazyUpdate: LibLazyUpdate.address, - LibArtifactUtils: LibArtifactUtils.address, + // LibArtifactUtils: LibArtifactUtils.address, }, }); const LibPlanet = await LibPlanetFactory.deploy(); diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index 594faf70..a7c200bb 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -1,4 +1,4 @@ -import { ArtifactRarity, ArtifactType, Biome, TokenType } from '@dfdao/types'; +import { ArtifactRarity, ArtifactType, Biome, SpaceshipType, TokenType } from '@dfdao/types'; import { loadFixture, mine } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import hre from 'hardhat'; @@ -56,9 +56,10 @@ describe('DarkForestArtifacts', function () { await increaseBlockchainTime(); // Move the Gear ship into position - const gearShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (artifact) => artifact.artifactType === ArtifactType.ShipGear + const gearShip = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).find( + (ship) => ship.spaceshipType === SpaceshipType.ShipGear ); + const gearId = gearShip?.id; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, ARTIFACT_PLANET_1, 100, 0, 0, gearId) @@ -80,7 +81,7 @@ describe('DarkForestArtifacts', function () { }); describe('it tests basic artifact actions', function () { - it.only('logs bits for artifact old', async function () { + it('logs bits for artifact old', async function () { // Must be valid options const _collectionType = '0x01'; const _rarity = ArtifactRarity.Legendary; @@ -562,7 +563,7 @@ describe('DarkForestArtifacts', function () { // should not be able to withdraw newArtifactId from LVL3_SPACETIME_1 await expect( world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId) - ).to.be.revertedWith('this artifact is not on this planet'); + ).to.be.revertedWith('artifact not found'); }); it('should not be able to withdraw/deposit onto a planet that is not a trading post', async function () { @@ -597,7 +598,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.Monolith, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Legendary, biome: Biome.OCEAN } ); @@ -647,7 +648,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, from, ArtifactType.Wormhole, - CollectionType.Artifact, + TokenType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); prettyPrintToken(await world.contract.getArtifact(artifactId)); @@ -717,13 +718,13 @@ describe('DarkForestArtifacts', function () { world.user1.address, from, ArtifactType.Wormhole, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); // Move gear bc too many artifacts on SPAWN_PLANET_1, so can't receive wormhole. - const crescentShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (artifact) => artifact.artifactType === ArtifactType.ShipCrescent + const crescentShip = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).find( + (ship) => ship.spaceshipType === SpaceshipType.ShipCrescent ); await world.user1Core.move( @@ -808,7 +809,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BloomFilter, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); prettyPrintToken(await world.contract.getArtifact(newTokenId)); @@ -855,7 +856,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BloomFilter, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); @@ -890,7 +891,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BlackDomain, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); @@ -940,7 +941,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BlackDomain, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); @@ -964,7 +965,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, LVL3_SPACETIME_1, ArtifactType.PlanetaryShield, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Rare as ArtifactRarity, biome: Biome.OCEAN } ); prettyPrintToken(await world.contract.getArtifact(newTokenId)); @@ -1009,7 +1010,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.PhotoidCannon, - CollectionType.Artifact, + TokenType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); prettyPrintToken(await world.contract.getArtifact(newTokenId)); diff --git a/eth/test/DFMove.test.ts b/eth/test/DFMove.test.ts index bafc5184..b452b1f1 100644 --- a/eth/test/DFMove.test.ts +++ b/eth/test/DFMove.test.ts @@ -1,4 +1,4 @@ -import { ArtifactType } from '@dfdao/types'; +import { ArtifactType, SpaceshipType } from '@dfdao/types'; import { loadFixture, time } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { BigNumber } from 'ethers'; @@ -52,7 +52,7 @@ describe('DarkForestMove', function () { }); it('allows controller to move ships to places they do not own with infinite distance', async function () { - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; + const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id))[0]; const shipId = ship.id; await world.user1Core.move( @@ -61,15 +61,17 @@ describe('DarkForestMove', function () { await increaseBlockchainTime(); await world.user1Core.refreshPlanet(LVL2_PLANET_SPACE.id); - expect((await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).length).to.be.eq(4); - expect((await world.user1Core.getArtifactsOnPlanet(LVL2_PLANET_SPACE.id)).length).to.be.eq(1); + expect((await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).length).to.be.eq(4); + expect((await world.user1Core.getSpaceshipsOnPlanet(LVL2_PLANET_SPACE.id)).length).to.be.eq( + 1 + ); }); it('allows controller to move ships between their own planets', async function () { await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL1_ASTEROID_NEBULA); await increaseBlockchainTime(); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; + const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id))[0]; const shipId = ship.id; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_NEBULA, 1000, 0, 0, shipId) @@ -78,13 +80,13 @@ describe('DarkForestMove', function () { await increaseBlockchainTime(); await world.user1Core.refreshPlanet(LVL1_ASTEROID_NEBULA.id); - expect((await world.user1Core.getArtifactsOnPlanet(LVL1_ASTEROID_NEBULA.id)).length).to.be.eq( - 1 - ); + expect( + (await world.user1Core.getSpaceshipsOnPlanet(LVL1_ASTEROID_NEBULA.id)).length + ).to.be.eq(1); }); it('should not allow you to move enemy ships on your own planet', async function () { - const user2shipId = (await world.user2Core.getArtifactsOnPlanet(SPAWN_PLANET_2.id))[0].id; + const user2shipId = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_2.id))[0].id; await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL2_PLANET_SPACE); @@ -92,7 +94,6 @@ describe('DarkForestMove', function () { ...makeMoveArgs(SPAWN_PLANET_2, LVL2_PLANET_SPACE, 1000, 0, 0, user2shipId) ); await increaseBlockchainTime(); - await expect( world.user1Core.move( ...makeMoveArgs(LVL2_PLANET_SPACE, SPAWN_PLANET_2, 1000, 0, 0, user2shipId) @@ -115,8 +116,8 @@ describe('DarkForestMove', function () { await increaseBlockchainTime(); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).filter( - (a) => a.artifactType === ArtifactType.ShipGear + const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).filter( + (s) => s.spaceshipType === SpaceshipType.ShipGear )[0]; console.log(`gear id`, ship?.id); @@ -924,10 +925,10 @@ describe('move rate limits', function () { await world.contract.adminGiveSpaceShip( SPAWN_PLANET_1.id, world.user1.address, - ArtifactType.ShipMothership + SpaceshipType.ShipMothership ); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; + const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id))[0]; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 10, 0, 0, ship.id) @@ -937,10 +938,10 @@ describe('move rate limits', function () { await world.contract.adminGiveSpaceShip( SPAWN_PLANET_1.id, world.user1.address, - ArtifactType.ShipMothership + SpaceshipType.ShipMothership ); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; + const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id))[0]; await expect( world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 1000, 0, 0, ship.id)) @@ -949,7 +950,7 @@ describe('move rate limits', function () { await increaseBlockchainTime(); await world.user1Core.refreshPlanet(LVL2_PLANET_SPACE.id); - const numShipsOnPlanet = (await world.user1Core.getArtifactsOnPlanet(LVL2_PLANET_SPACE.id)) + const numShipsOnPlanet = (await world.user1Core.getSpaceshipsOnPlanet(LVL2_PLANET_SPACE.id)) .length; expect(numShipsOnPlanet).to.be.eq(6); @@ -963,10 +964,10 @@ describe('move rate limits', function () { await world.contract.adminGiveSpaceShip( SPAWN_PLANET_1.id, world.user1.address, - ArtifactType.ShipMothership + SpaceshipType.ShipMothership ); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; + const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id))[0]; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 10, 0, 0, ship.id) @@ -980,7 +981,7 @@ describe('move rate limits', function () { await increaseBlockchainTime(); await world.user1Core.refreshPlanet(LVL2_PLANET_SPACE.id); - const numShipsOnPlanet = (await world.user1Core.getArtifactsOnPlanet(LVL2_PLANET_SPACE.id)) + const numShipsOnPlanet = (await world.user1Core.getSpaceshipsOnPlanet(LVL2_PLANET_SPACE.id)) .length; expect(numShipsOnPlanet).to.be.eq(6); diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index d4192c82..874f6d38 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -1,13 +1,12 @@ -import { ArtifactType, PlanetType } from '@dfdao/types'; +import { PlanetType, SpaceshipType } from '@dfdao/types'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { conquerUnownedPlanet, - getArtifactOnPlanetByType, + getSpaceshipOnPlanetByType, increaseBlockchainTime, makeInitArgs, makeMoveArgs, - prettyPrintToken, } from './utils/TestUtils'; import { defaultWorldFixture, World } from './utils/TestWorld'; import { @@ -41,7 +40,9 @@ describe('DarkForestSpaceShips', function () { describe('spawning your ships', function () { it('gives you 5 space ships', async function () { - expect((await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).length).to.be.equal(5); + expect((await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).length).to.be.equal( + 5 + ); }); it('can only be done once per player', async function () { @@ -61,10 +62,10 @@ describe('DarkForestSpaceShips', function () { describe('ship transfers', function () { it('cannot transfer your own spaceship', async function () { - const motherShip = await getArtifactOnPlanetByType( + const motherShip = await getSpaceshipOnPlanetByType( world.contract, SPAWN_PLANET_1.id, - ArtifactType.ShipMothership + SpaceshipType.ShipMothership ); // Player owns ship. expect(await world.contract.balanceOf(world.user1.address, motherShip.id)).to.equal(1); @@ -81,10 +82,10 @@ describe('DarkForestSpaceShips', function () { it('cannot transfer other players spaceship', async function () { await world.user2Core.giveSpaceShips(SPAWN_PLANET_2.id); - const motherShip = await getArtifactOnPlanetByType( + const motherShip = await getSpaceshipOnPlanetByType( world.contract, SPAWN_PLANET_2.id, - ArtifactType.ShipMothership + SpaceshipType.ShipMothership ); // Other Player owns ship. expect(await world.contract.balanceOf(world.user2.address, motherShip.id)).to.equal(1); @@ -104,8 +105,10 @@ describe('DarkForestSpaceShips', function () { this.timeout(0); it('pauses energy regeneration on planets', async function () { - const titan = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (a) => a.artifactType === ArtifactType.ShipTitan + const titan = await getSpaceshipOnPlanetByType( + world.contract, + SPAWN_PLANET_1.id, + SpaceshipType.ShipTitan ); // Move Titan to planet @@ -151,11 +154,11 @@ describe('DarkForestSpaceShips', function () { describe('using the Crescent', function () { it('turns planet into an asteroid and burns crescent', async function () { - const crescent = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (a) => a.artifactType === ArtifactType.ShipCrescent + const crescent = await getSpaceshipOnPlanetByType( + world.contract, + SPAWN_PLANET_1.id, + SpaceshipType.ShipCrescent ); - if (!crescent) throw new Error('crescent not found'); - prettyPrintToken(crescent); // Move Crescent to planet await world.user1Core.move( @@ -166,7 +169,7 @@ describe('DarkForestSpaceShips', function () { await world.contract.refreshPlanet(LVL1_PLANET_DEEP_SPACE.id); const crescentNewLocId = ( - await world.contract.getArtifactsOnPlanet(LVL1_PLANET_DEEP_SPACE.id) + await world.contract.getSpaceshipsOnPlanet(LVL1_PLANET_DEEP_SPACE.id) )[0].id; expect(crescentNewLocId).to.equal(crescent?.id); @@ -177,7 +180,7 @@ describe('DarkForestSpaceShips', function () { expect(planetBeforeActivate.silverGrowth).to.be.lessThan(planetAfterActivate.silverGrowth); // Crescent is no longer on planet. expect( - (await world.contract.getArtifactsOnPlanet(LVL1_PLANET_DEEP_SPACE.id)).length + (await world.contract.getSpaceshipsOnPlanet(LVL1_PLANET_DEEP_SPACE.id)).length ).to.equal(0); // Planet was planet expect(planetBeforeActivate.planetType).to.equal(PlanetType.PLANET); @@ -186,7 +189,7 @@ describe('DarkForestSpaceShips', function () { // Cannot activate again. await expect( world.user1Core.activateArtifact(LVL1_PLANET_DEEP_SPACE.id, crescent.id, 0) - ).to.be.revertedWith("can't active an artifact on a planet it's not on"); + ).to.be.revertedWith("can't activate a ship on a planet it's not on"); }); }); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index d0ce72cd..31363352 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -15,6 +15,7 @@ import { ArtifactTypeNames, Biome, BiomeNames, + SpaceshipType, TokenType, TokenTypeNames, } from '@dfdao/types'; @@ -382,3 +383,13 @@ export async function getArtifactOnPlanetByType( (artifact) => (artifact.artifactType as ArtifactType) === artifactType )[0]; } + +export async function getSpaceshipOnPlanetByType( + contract: DarkForest, + locationId: BigNumber, + shipType: SpaceshipType +) { + return (await contract.getSpaceshipsOnPlanet(locationId)).filter( + (s) => (s.spaceshipType as SpaceshipType) === shipType + )[0]; +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index dc2b5e47..776e7a94 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -42,6 +42,7 @@ export * from './plugin'; export * from './renderer'; export * from './reveal'; export * from './setting'; +export * from './spaceship'; export * from './token'; export * from './transaction'; export * from './transactions'; From 7f7edab6027a34e7044204c48a4967b262380485 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Fri, 30 Sep 2022 17:58:09 +0100 Subject: [PATCH 30/55] spaceship type --- packages/types/src/spaceship.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 packages/types/src/spaceship.ts diff --git a/packages/types/src/spaceship.ts b/packages/types/src/spaceship.ts new file mode 100644 index 00000000..027df98a --- /dev/null +++ b/packages/types/src/spaceship.ts @@ -0,0 +1,32 @@ +import { Abstract } from './utility'; + +/** + * Abstract type representing an artifact type. + */ +export type SpaceshipType = Abstract; + +/** + * Enumeration of artifact types. + */ +export const SpaceshipType = { + Unknown: 0 as SpaceshipType, + ShipMothership: 1 as SpaceshipType, + ShipCrescent: 2 as SpaceshipType, + ShipWhale: 3 as SpaceshipType, + ShipGear: 4 as SpaceshipType, + ShipTitan: 5 as SpaceshipType, + + // Don't forget to update MIN_ARTIFACT_TYPE and/or MAX_ARTIFACT_TYPE in the `constants` package +} as const; + +/** + * Mapping from SpaceshipType to pretty-printed names. + */ +export const SpaceshipTypeNames = { + [SpaceshipType.Unknown]: 'Unknown', + [SpaceshipType.ShipMothership]: 'Mothership', + [SpaceshipType.ShipCrescent]: 'Crescent', + [SpaceshipType.ShipWhale]: 'Whale', + [SpaceshipType.ShipGear]: 'Gear', + [SpaceshipType.ShipTitan]: 'Titan', +} as const; From 4f60b35e7ee76f3810266b75e6ab39a181d15db1 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Fri, 30 Sep 2022 18:18:28 +0100 Subject: [PATCH 31/55] fix: photoid test --- eth/contracts/DFTypes.sol | 8 +------- eth/contracts/facets/DFArtifactFacet.sol | 2 -- eth/contracts/facets/DFMoveFacet.sol | 15 ++++++++------- eth/contracts/libraries/LibArtifactUtils.sol | 7 +------ eth/test/DFArtifacts.test.ts | 2 -- eth/test/DFMove.test.ts | 1 - 6 files changed, 10 insertions(+), 25 deletions(-) diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 4262ea14..475b1bc2 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -205,13 +205,7 @@ enum ArtifactType { PlanetaryShield, PhotoidCannon, BloomFilter, - BlackDomain, - // TODO; remove this - ShipMothership, - ShipCrescent, - ShipWhale, - ShipGear, - ShipTitan + BlackDomain } enum SpaceshipType { diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index d668bf90..cbd2e7fc 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -126,8 +126,6 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { return LibArtifact.decode(artifactId); } - // TODO: Add ERC1155 Enumerable Wrappers - // This calls the low level _transfer call which doesn't check if the msg.sender actually owns // the tokenId. TODO: See if this is a problem. function transferArtifact( diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 9a884847..44f1713b 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -17,7 +17,7 @@ import {LibSpaceship} from "../libraries/LibSpaceship.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {ArrivalData, ArrivalType, Artifact, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; +import {ArrivalData, ArrivalType, Artifact, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Spaceship, SpaceshipType, Upgrade} from "../DFTypes.sol"; import "hardhat/console.sol"; contract DFMoveFacet is WithStorage { @@ -236,7 +236,7 @@ contract DFMoveFacet is WithStorage { } } - function applySpaceshipDepart(Artifact memory artifact, Planet memory planet) + function applySpaceshipDepart(Spaceship memory spaceship, Planet memory planet) public view returns (Planet memory) @@ -245,21 +245,21 @@ contract DFMoveFacet is WithStorage { return planet; } - if (artifact.artifactType == ArtifactType.ShipMothership) { + if (spaceship.spaceshipType == SpaceshipType.ShipMothership) { if (planet.energyGroDoublers == 1) { planet.energyGroDoublers--; planet.populationGrowth /= 2; } else if (planet.energyGroDoublers > 1) { planet.energyGroDoublers--; } - } else if (artifact.artifactType == ArtifactType.ShipWhale) { + } else if (spaceship.spaceshipType == SpaceshipType.ShipWhale) { if (planet.silverGroDoublers == 1) { planet.silverGroDoublers--; planet.silverGrowth /= 2; } else if (planet.silverGroDoublers > 1) { planet.silverGroDoublers--; } - } else if (artifact.artifactType == ArtifactType.ShipTitan) { + } else if (spaceship.spaceshipType == SpaceshipType.ShipTitan) { // so that updating silver/energy starts from the current time, // as opposed to the last time that the planet was updated planet.lastUpdated = block.timestamp; @@ -273,8 +273,9 @@ contract DFMoveFacet is WithStorage { Undo the spaceship effects that were applied when the ship arrived on the planet. */ function _removeSpaceshipEffectsFromOriginPlanet(DFPMoveArgs memory args) private { - Artifact memory movedArtifact = LibArtifactUtils.decodeArtifact(args.movedArtifactId); - Planet memory planet = applySpaceshipDepart(movedArtifact, gs().planets[args.oldLoc]); + if (!LibSpaceship.isShip(args.movedArtifactId)) return; + Spaceship memory spaceship = LibSpaceship.decode(args.movedArtifactId); + Planet memory planet = applySpaceshipDepart(spaceship, gs().planets[args.oldLoc]); gs().planets[args.oldLoc] = planet; } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 9804d113..0d59a8bd 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -339,7 +339,7 @@ library LibArtifactUtils { planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to withdraw this artifact" ); - require(!isSpaceship(artifact.artifactType), "cannot withdraw spaceships"); + require(!LibSpaceship.isShip(artifactId), "cannot withdraw spaceships"); LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); // artifactId, curr owner, new owner @@ -376,11 +376,6 @@ library LibArtifactUtils { return false; } - function isSpaceship(ArtifactType artifactType) public pure returns (bool) { - return - artifactType >= ArtifactType.ShipMothership && artifactType <= ArtifactType.ShipTitan; - } - /** * @notice Create the collection ID for a given artifact * @param _collectionType type of artifact diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index a7c200bb..e25450d5 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -75,7 +75,6 @@ describe('DarkForestArtifacts', function () { } beforeEach('load fixture', async function () { - console.log(`loading world...`); this.timeout(0); world = await loadFixture(worldFixture); }); @@ -130,7 +129,6 @@ describe('DarkForestArtifacts', function () { spaceshipType, }); const a = await world.contract.testDecodeSpaceship(res); - console.log(a); expect(tokenType).to.equal(Number(a.tokenType)); expect(spaceshipType).to.equal(Number(a.spaceshipType)); }); diff --git a/eth/test/DFMove.test.ts b/eth/test/DFMove.test.ts index b452b1f1..a3a4850b 100644 --- a/eth/test/DFMove.test.ts +++ b/eth/test/DFMove.test.ts @@ -119,7 +119,6 @@ describe('DarkForestMove', function () { const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).filter( (s) => s.spaceshipType === SpaceshipType.ShipGear )[0]; - console.log(`gear id`, ship?.id); await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_1, 100, 0, 0, ship?.id) From 14bcaa95d709d1239ce3901dd5941f99bd1d07a1 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sat, 1 Oct 2022 14:39:40 +0100 Subject: [PATCH 32/55] update types and readme --- eth/contracts/DFTypes.sol | 8 ----- eth/contracts/Tokens.md | 64 +++++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 475b1bc2..893d328e 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -250,14 +250,6 @@ enum Biome { Corrupted } -// enum TokenInfo { -// Unknown, -// TokenType, // Each bin of tokens gets an id (spaceships, artifacts, etc...) -// ArtifactRarity, -// ArtifactType, -// Biome -// } - enum TokenType { Unknown, Artifact, diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index 1d9b2bd8..4c4d5b30 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -26,24 +26,36 @@ and, more importantly, it allows to create a copy of a token just by using the s This concept will become clearer as we examine how these rules are used for Artifacts and Spaceships. -In `DFTypes.sol`, the `TokenInfo` enum looks like this: +Each of these chunks has 256 options. So you can have 256 Artifact Rarities, Types, Biomes etc... +This should be plenty, but if you need more you just use another chunk. + +## General Token Info + +Each collection (called a Token) in Dark Forest must have a Library dedicated to it with the naming +convention `Lib.sol`. + +The first byte (from left) of the tokenId **must** correspond to the appropriate value in the +`TokenType` struct in `DFTypes.sol` ```js -enum TokenInfo { +enum TokenType { Unknown, - TokenType, - ArtifactRarity, - ArtifactType, - Biome + Artifact, // 0x01 = Artifact + Spaceship // 0x02 = Spaceship + // etc... } ``` -Each index in `TokenInfo` refers to a chunk in the `tokenId`. +The `Lib.sol`. file **must** have the following methods: -> | TokenType | ArtifactRarity | ArtifactType | Biome | chunk5 | ... chunk 32 | +1. `encode() returns (uint256 tokenId)` +2. `decode(uint256 tokenId) returns ()` +3. `is returns (bool)` -Each of these chunks has 256 options. So you can have 256 Artifact Rarities, Types, Biomes etc... -This should be plenty, but if you need more you just use another chunk. +where `` can be a struct (like Artifacts or Spaceships) or just a uint256 (like Silver). + +Additionally methods can be added to each library, but they must be `internal` functions that can be +inherited by other facets or libraries. ## Artifacts @@ -59,14 +71,14 @@ In hex: > | 0x01 | 0x03| 0x01 | 0x01 | ... Under the hood, we simply calculate the value of the given property (Epic Artifact = 3), convert it -to hex (0x03), and place it in the appropriate location (TokenInfo.ArtifactRarity = 2, so place 0x03 +to hex (0x03), and place it in the appropriate location (ArtifactInfo.ArtifactRarity = 2, so place 0x03 two chunks from the left). To decode, we just reverse this process. Given the id `0x010301010000.....`, I know that it is an Epic Monoliths Artifact from the Ocean Biome. -You can see the actual encoding and decoding take place in `DFToken.sol/encodeArtifact` and -`DFToken.sol/decode Artifact`. +You can see the actual encoding and decoding take place in `LibArtifact.sol/encode` and +`LibArtifact.sol/decode`. ### Minting @@ -107,6 +119,10 @@ your planet, you cannot control my Mothership. This means that the `planetArtifa will fail to enforce this primary rule of Spaceships. To fix this, each Spaceship must have a unique id and be owned by the account that minted it. +To differentiate between Spaceships and Artifacts, we have an additional data structure, +`mapping(uint256 =>uint256[]) planetSpaceships;` in the contract storage that keeps track of which +spaceships are on which planet. + However, we can still use our `tokenId` chunk system because we don't need all 256 bits to uniquely identify a Spaceship. We limit Spaceships to the first 16 chunks (128 bits) and save 128 for a unique id. This uses the [Split-Id](https://eips.ethereum.org/EIPS/eip-1155#split-id-bits) method @@ -114,12 +130,11 @@ recommended in the ERC1155 Proposal. A Mothership Spaceship is represented like so -> | TokenType.Spaceship | ArtifactRarity.Unknown | ArtifactType.ShipMothership | Biome.Unknown -> | ... chunk 16 | uniqueId (16 chunks) | +> | TokenType.Spaceship | SpaceshipType.ShipMothership | ... chunk 16 | uniqueId (16 chunks) | In hex: -> | 0x02 | 0x00 | 0x0a | 0x00 | ... | uniqueId (16 chunks) | +> | 0x02 | 0x01 ... | uniqueId (16 chunks) | A Spaceship's tokenId = `` @@ -138,7 +153,7 @@ If Velorum mints their own Mothership, it would have id: `<0x02000a00><0x02>`. Velorum's Mothership has the same TokenInfo, but a unique identifier at the end. This means the contract stores my Mothership and Velorum's Mothership as completely different collections. However, because our ships share the same first 128 bits, we can still calculate the information -about the Spaceship (ArtifactType, TokenType) just by feeding the `decodeArtifact` function the `tokenId`. +about the Spaceship (SpaceshipType, TokenType) just by feeding the `LibSpaceship.decode` function the `tokenId`. ### Transferring @@ -150,10 +165,12 @@ We could easily turn off this check if we wanted players to be able to buy and s ## Activating +### Artifacts + Every Artifact and one Spaceship (Crescent) must be activated on a planet to be used. -The fungible nature of Artifacts creates a challenge: How do we associate data with specific artifacts? How do I know when my -Epic Monolith is activated? +The fungible nature of Artifacts creates a challenge: How do we associate data with specific +artifacts? How do I know when my Epic Monolith is activated? There are new data structures in `LibStorage.sol` to handle this information. Because Artifact activations are always associated with planets, we can store the needed information on the relevant planets @@ -178,6 +195,15 @@ wormhole from `0xA` to `0xB`. - `planetWormholes[0xA] = 0xB` +### Spaceships + +- The only Spaceship that can be activated is the Crescent, and that is burned in the same + transaction. This means that a Spaceship Id will never be added or removed to the + `planetActiveArtifact` mapping. +- This defines a fundamental difference between Spaceships and Artifacts: Spaceship effects are + always applied on arrival / departure, or are instanenous. There is (at the moment) no concept of + activating them. + ## Deactivating If I deactivate my artifact from `0xA`, we simply undo these maneuvers: From 14236aced3122335facb868fe339f4e227df532b Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sat, 1 Oct 2022 16:26:48 +0100 Subject: [PATCH 33/55] chore: move functions that can be internal into LibArtifact and LibSpaceship --- eth/contracts/facets/DFAdminFacet.sol | 5 +- eth/contracts/facets/DFArtifactFacet.sol | 27 +- eth/contracts/facets/DFGetterFacet.sol | 2 +- eth/contracts/facets/DFMoveFacet.sol | 15 +- eth/contracts/libraries/LibArtifact.sol | 284 ++++++++++++++++++- eth/contracts/libraries/LibArtifactUtils.sol | 143 ++-------- eth/contracts/libraries/LibGameUtils.sol | 271 +----------------- eth/contracts/libraries/LibPlanet.sol | 4 +- eth/contracts/libraries/LibSpaceship.sol | 38 +++ eth/tasks/deploy.ts | 10 +- eth/test/DFArtifacts.test.ts | 45 +-- eth/test/utils/TestUtils.ts | 14 +- 12 files changed, 395 insertions(+), 463 deletions(-) diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index dd1877b9..3ee57b8e 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -12,7 +12,9 @@ import {LibSpaceship} from "../libraries/LibSpaceship.sol"; // Storage imports import {WithStorage} from "../libraries/LibStorage.sol"; +// Contract imports import {DFArtifactFacet} from "./DFArtifactFacet.sol"; +import "hardhat/console.sol"; // Type imports import {Artifact, SpaceType, Spaceship, SpaceshipType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet, TokenType} from "../DFTypes.sol"; @@ -181,8 +183,9 @@ contract DFAdminFacet is WithStorage { tokenId, args.owner ); + // Don't put artifact on planet if no planetId given. - if (args.planetId != 0) LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); + if (args.planetId != 0) LibArtifact.putArtifactOnPlanet(args.planetId, artifact.id); emit AdminArtifactCreated(args.owner, artifact.id, args.planetId); } } diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index cbd2e7fc..e92107f2 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -72,11 +72,11 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { returns (Artifact memory) { require(tokenId >= 1, "token id must be positive"); - require(LibArtifact.isArtifact(tokenId), "wrong token type"); + require(LibArtifact.isArtifact(tokenId), "token must be Artifact"); // Account, Id, Amount, Data _mint(owner, tokenId, 1, ""); - return getArtifact(tokenId); + return LibArtifact.decode(tokenId); } function createSpaceship(uint256 tokenId, address owner) @@ -85,7 +85,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { returns (Spaceship memory) { require(tokenId >= 1, "token id must be positive"); - require(LibSpaceship.isShip(tokenId), "wrong token type"); + require(LibSpaceship.isShip(tokenId), "token must be Spaceship"); // Account, Id, Amount, Data _mint(owner, tokenId, 1, ""); @@ -93,36 +93,23 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { return getSpaceship(tokenId); } - function getArtifact(uint256 artifactId) public pure returns (Artifact memory) { - return LibArtifact.decode(artifactId); - } - function getSpaceship(uint256 shipId) public pure returns (Spaceship memory) { return LibSpaceship.decode(shipId); } - function encodeArtifact( - uint256 _collectionType, - uint256 _rarity, - uint256 _artifactType, - uint256 _biome - ) public pure returns (uint256) { - return LibArtifactUtils.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); - } - - function testEncodeSpaceship(Spaceship memory spaceship) public pure returns (uint256) { + function encodeSpaceship(Spaceship memory spaceship) public pure returns (uint256) { return LibSpaceship.encode(spaceship); } - function testDecodeSpaceship(uint256 shipId) public pure returns (Spaceship memory) { + function decodeSpaceship(uint256 shipId) public pure returns (Spaceship memory) { return LibSpaceship.decode(shipId); } - function testEncodeArtifact(Artifact memory artifact) public pure returns (uint256) { + function encodeArtifact(Artifact memory artifact) public pure returns (uint256) { return LibArtifact.encode(artifact); } - function testDecodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { + function decodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { return LibArtifact.decode(artifactId); } diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 0865c634..aa15a491 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -388,7 +388,7 @@ contract DFGetterFacet is WithStorage { returns (Artifact memory ret) { uint256 artifactId = gs().planetActiveArtifact[locationId]; - return LibArtifactUtils.decodeArtifact(artifactId); + return LibArtifact.decode(artifactId); } // function bulkGetPlanetArtifacts(uint256[] calldata planetIds) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 44f1713b..a10d438c 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -290,10 +290,9 @@ contract DFMoveFacet is WithStorage { returns (bool wormholePresent, uint256 effectiveDistModifier) { wormholePresent = false; - Artifact memory relevantWormhole; - Artifact memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact(args.oldLoc); - Artifact memory activeArtifactTo = LibArtifactUtils.getActiveArtifact(args.newLoc); + Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); + Artifact memory activeArtifactTo = LibArtifact.getActiveArtifact(args.newLoc); // TODO: take the greater rarity of these, or disallow wormholes between planets that // already have a wormhole between them if ( @@ -325,7 +324,7 @@ contract DFMoveFacet is WithStorage { private returns (bool photoidPresent, Upgrade memory temporaryUpgrade) { - Artifact memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact(args.oldLoc); + Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); if ( activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= @@ -445,12 +444,14 @@ contract DFMoveFacet is WithStorage { // Photoids are burned in _checkPhotoid, so don't remove twice if (args.movedArtifactId != 0) { if (isSpaceship) { - LibGameUtils._takeSpaceshipOffPlanet(args.oldLoc, args.movedArtifactId); - } else { + LibSpaceship.takeSpaceshipOffPlanet(args.oldLoc, args.movedArtifactId); + } else if (LibArtifact.isArtifact(args.movedArtifactId)) { Artifact memory artifact = LibArtifact.decode(args.movedArtifactId); if (artifact.artifactType != ArtifactType.PhotoidCannon) { - LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); + LibArtifact.takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); } + } else { + require(false, "cannot move token of this type"); } } } diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index f09a0435..adbc62b4 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -12,11 +12,16 @@ import "hardhat/console.sol"; import {LibUtils} from "./LibUtils.sol"; // Storage imports +import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; // Type imports -import {Artifact, ArtifactInfo, ArtifactRarity, ArtifactType, Biome, TokenType} from "../DFTypes.sol"; +import {Artifact, ArtifactInfo, ArtifactRarity, ArtifactType, Biome, SpaceType, TokenType, Upgrade} from "../DFTypes.sol"; library LibArtifact { + function gs() internal pure returns (GameStorage storage) { + return LibStorage.gameStorage(); + } + /** * @notice Create the token ID for a Artifact with the following properties: * @param artifact Artifact @@ -66,4 +71,281 @@ library LibArtifact { uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); return (TokenType(tokenType) == TokenType.Artifact); } + + function _nullArtifactProperties() internal pure returns (Artifact memory) { + return + Artifact( + 0, + TokenType.Unknown, + ArtifactRarity.Unknown, + ArtifactType.Unknown, + Biome.Unknown + ); + } + + function getUpgradeForArtifact(Artifact memory artifact) + internal + pure + returns (Upgrade memory) + { + if (artifact.artifactType == ArtifactType.PlanetaryShield) { + uint256[6] memory defenseMultipliersPerRarity = [uint256(100), 150, 200, 300, 450, 650]; + + return + Upgrade({ + popCapMultiplier: 100, + popGroMultiplier: 100, + rangeMultiplier: 20, + speedMultiplier: 20, + defMultiplier: defenseMultipliersPerRarity[uint256(artifact.rarity)] + }); + } + + if (artifact.artifactType == ArtifactType.PhotoidCannon) { + uint256[6] memory def = [uint256(100), 50, 40, 30, 20, 10]; + return + Upgrade({ + popCapMultiplier: 100, + popGroMultiplier: 100, + rangeMultiplier: 100, + speedMultiplier: 100, + defMultiplier: def[uint256(artifact.rarity)] + }); + } + + if (uint256(artifact.artifactType) >= 5) { + return + Upgrade({ + popCapMultiplier: 100, + popGroMultiplier: 100, + rangeMultiplier: 100, + speedMultiplier: 100, + defMultiplier: 100 + }); + } + + Upgrade memory ret = Upgrade({ + popCapMultiplier: 100, + popGroMultiplier: 100, + rangeMultiplier: 100, + speedMultiplier: 100, + defMultiplier: 100 + }); + + if (artifact.artifactType == ArtifactType.Monolith) { + ret.popCapMultiplier += 5; + ret.popGroMultiplier += 5; + } else if (artifact.artifactType == ArtifactType.Colossus) { + ret.speedMultiplier += 5; + } else if (artifact.artifactType == ArtifactType.Spaceship) { + ret.rangeMultiplier += 5; + } else if (artifact.artifactType == ArtifactType.Pyramid) { + ret.defMultiplier += 5; + } + + if (artifact.planetBiome == Biome.Ocean) { + ret.speedMultiplier += 5; + ret.defMultiplier += 5; + } else if (artifact.planetBiome == Biome.Forest) { + ret.defMultiplier += 5; + ret.popCapMultiplier += 5; + ret.popGroMultiplier += 5; + } else if (artifact.planetBiome == Biome.Grassland) { + ret.popCapMultiplier += 5; + ret.popGroMultiplier += 5; + ret.rangeMultiplier += 5; + } else if (artifact.planetBiome == Biome.Tundra) { + ret.defMultiplier += 5; + ret.rangeMultiplier += 5; + } else if (artifact.planetBiome == Biome.Swamp) { + ret.speedMultiplier += 5; + ret.rangeMultiplier += 5; + } else if (artifact.planetBiome == Biome.Desert) { + ret.speedMultiplier += 10; + } else if (artifact.planetBiome == Biome.Ice) { + ret.rangeMultiplier += 10; + } else if (artifact.planetBiome == Biome.Wasteland) { + ret.defMultiplier += 10; + } else if (artifact.planetBiome == Biome.Lava) { + ret.popCapMultiplier += 10; + ret.popGroMultiplier += 10; + } else if (artifact.planetBiome == Biome.Corrupted) { + ret.rangeMultiplier += 5; + ret.speedMultiplier += 5; + ret.popCapMultiplier += 5; + ret.popGroMultiplier += 5; + } + + uint256 scale = 1 + (uint256(artifact.rarity) / 2); + + ret.popCapMultiplier = scale * ret.popCapMultiplier - (scale - 1) * 100; + ret.popGroMultiplier = scale * ret.popGroMultiplier - (scale - 1) * 100; + ret.speedMultiplier = scale * ret.speedMultiplier - (scale - 1) * 100; + ret.rangeMultiplier = scale * ret.rangeMultiplier - (scale - 1) * 100; + ret.defMultiplier = scale * ret.defMultiplier - (scale - 1) * 100; + + return ret; + } + + function artifactRarityFromPlanetLevel(uint256 planetLevel) + internal + pure + returns (ArtifactRarity) + { + if (planetLevel <= 1) return ArtifactRarity.Common; + else if (planetLevel <= 3) return ArtifactRarity.Rare; + else if (planetLevel <= 5) return ArtifactRarity.Epic; + else if (planetLevel <= 7) return ArtifactRarity.Legendary; + else return ArtifactRarity.Mythic; + } + + // an artifact is only considered 'activated' if this method returns true. + // we do not have an `isActive` field on artifact; the times that the + // artifact was last activated and deactivated are sufficent to determine + // whether or not the artifact is activated. + + function isActivated(uint256 locationId, uint256 artifactId) internal view returns (bool) { + return (gs().planetActiveArtifact[locationId] == artifactId); + } + + function isArtifactOnPlanet(uint256 locationId, uint256 artifactId) + internal + view + returns (bool) + { + for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { + if (gs().planetArtifacts[locationId][i] == artifactId) { + return true; + } + } + + return false; + } + + function randomArtifactTypeAndLevelBonus( + uint256 artifactSeed, + Biome biome, + SpaceType spaceType + ) internal pure returns (ArtifactType, uint256) { + uint256 lastByteOfSeed = artifactSeed % 0xFF; + uint256 secondLastByteOfSeed = ((artifactSeed - lastByteOfSeed) / 256) % 0xFF; + + ArtifactType artifactType = ArtifactType.Pyramid; + + if (lastByteOfSeed < 39) { + artifactType = ArtifactType.Monolith; + } else if (lastByteOfSeed < 78) { + artifactType = ArtifactType.Colossus; + } + // else if (lastByteOfSeed < 117) { + // artifactType = ArtifactType.Spaceship; + // } + else if (lastByteOfSeed < 156) { + artifactType = ArtifactType.Pyramid; + } else if (lastByteOfSeed < 171) { + artifactType = ArtifactType.Wormhole; + } else if (lastByteOfSeed < 186) { + artifactType = ArtifactType.PlanetaryShield; + } else if (lastByteOfSeed < 201) { + artifactType = ArtifactType.PhotoidCannon; + } else if (lastByteOfSeed < 216) { + artifactType = ArtifactType.BloomFilter; + } else if (lastByteOfSeed < 231) { + artifactType = ArtifactType.BlackDomain; + } else { + if (biome == Biome.Ice) { + artifactType = ArtifactType.PlanetaryShield; + } else if (biome == Biome.Lava) { + artifactType = ArtifactType.PhotoidCannon; + } else if (biome == Biome.Wasteland) { + artifactType = ArtifactType.BloomFilter; + } else if (biome == Biome.Corrupted) { + artifactType = ArtifactType.BlackDomain; + } else { + artifactType = ArtifactType.Wormhole; + } + artifactType = ArtifactType.PhotoidCannon; + } + + uint256 bonus = 0; + if (secondLastByteOfSeed < 4) { + bonus = 2; + } else if (secondLastByteOfSeed < 16) { + bonus = 1; + } + + return (artifactType, bonus); + } + + // planets can have multiple artifacts on them. this function updates all the + // internal contract book-keeping to reflect that the given artifact was + // put on. note that this function does not transfer the artifact. + function putArtifactOnPlanet(uint256 locationId, uint256 artifactId) internal { + gs().planetArtifacts[locationId].push(artifactId); + } + + /** + * Remove artifactId from planet with locationId if artifactId exists AND is not active. + */ + function takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) internal { + uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; + + bool hadTheArtifact = false; + + for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { + if (gs().planetArtifacts[locationId][i] == artifactId) { + require( + !isActivated(locationId, artifactId), + "you cannot take an activated artifact off a planet" + ); + + gs().planetArtifacts[locationId][i] = gs().planetArtifacts[locationId][ + artifactsOnThisPlanet - 1 + ]; + + hadTheArtifact = true; + break; + } + } + + require(hadTheArtifact, "this artifact was not present on this planet"); + gs().planetArtifacts[locationId].pop(); + } + + // if the given planet has an activated artifact on it, then return the artifact + // otherwise, return a 'null artifact' + function getActiveArtifact(uint256 locationId) internal view returns (Artifact memory) { + if (hasActiveArtifact(locationId)) { + uint256 artifactId = gs().planetActiveArtifact[locationId]; + return LibArtifact.decode(artifactId); + } else { + return LibArtifact._nullArtifactProperties(); + } + } + + // if the given planet has an activated artifact on it, then return the artifact + // otherwise, return a 'null artifact' + function hasActiveArtifact(uint256 locationId) internal view returns (bool) { + uint256 artifactId = gs().planetActiveArtifact[locationId]; + return artifactId != 0; + } + + // if the given artifact is on the given planet, then return the artifact + // otherwise, throw error + function getPlanetArtifact(uint256 locationId, uint256 artifactId) + internal + view + returns (Artifact memory a) + { + bool found = false; + for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { + if (gs().planetArtifacts[locationId][i] == artifactId) { + a = LibArtifact.decode(artifactId); + found = true; + return a; + } + } + + require(found, "artifact not found"); + } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 0d59a8bd..ec5581aa 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -76,7 +76,7 @@ library LibArtifactUtils { tokenId + id, // Make each ship unique owner ); - LibGameUtils._putSpaceshipOnPlanet(planetId, spaceship.id); + LibSpaceship.putSpaceshipOnPlanet(planetId, spaceship.id); return spaceship.id; } @@ -98,10 +98,10 @@ library LibArtifactUtils { ) ); - (ArtifactType artifactType, uint256 levelBonus) = LibGameUtils - ._randomArtifactTypeAndLevelBonus(artifactSeed, biome, planet.spaceType); + (ArtifactType artifactType, uint256 levelBonus) = LibArtifact + .randomArtifactTypeAndLevelBonus(artifactSeed, biome, planet.spaceType); - ArtifactRarity rarity = LibGameUtils.artifactRarityFromPlanetLevel( + ArtifactRarity rarity = LibArtifact.artifactRarityFromPlanetLevel( levelBonus + planet.planetLevel ); uint256 tokenId = LibArtifact.encode( @@ -119,7 +119,7 @@ library LibArtifactUtils { args.coreAddress ); - LibGameUtils._putArtifactOnPlanet(args.planetId, foundArtifact.id); + LibArtifact.putArtifactOnPlanet(args.planetId, foundArtifact.id); planet.hasTriedFindingArtifact = true; gs().players[msg.sender].score += gameConstants().ARTIFACT_POINT_VALUES[ @@ -135,20 +135,22 @@ library LibArtifactUtils { uint256 wormholeTo ) public { Planet storage planet = gs().planets[locationId]; - Artifact memory artifact = decodeArtifact(artifactId); + Artifact memory artifact = LibArtifact.decode(artifactId); if (LibSpaceship.isShip(artifactId)) { require( - LibGameUtils.isSpaceshipOnPlanet(locationId, artifactId), + LibSpaceship.isSpaceshipOnPlanet(locationId, artifactId), "can't activate a ship on a planet it's not on" ); activateSpaceshipArtifact(locationId, artifactId, planet); - } else { + } else if (LibArtifact.isArtifact(artifactId)) { require( - LibGameUtils.isArtifactOnPlanet(locationId, artifactId), + LibArtifact.isArtifactOnPlanet(locationId, artifactId), "can't activate an artifact on a planet it's not on" ); activateNonSpaceshipArtifact(locationId, artifactId, wormholeTo, planet, artifact); + } else { + require(false, "token cannot be activated"); } } @@ -185,7 +187,7 @@ library LibArtifactUtils { // TODO: Why not actually burn? // burn it after use. will be owned by contract but not on a planet anyone can control - LibGameUtils._takeSpaceshipOffPlanet(locationId, shipId); + LibSpaceship.takeSpaceshipOffPlanet(locationId, shipId); emit ArtifactDeactivated(msg.sender, locationId, shipId); } } @@ -202,13 +204,13 @@ library LibArtifactUtils { "you must own the planet you are activating an artifact on" ); require( - getActiveArtifact(locationId).tokenType == TokenType.Unknown, + !LibArtifact.hasActiveArtifact(locationId), "there is already an active artifact on this planet" ); require(!planet.destroyed, "planet is destroyed"); require( - getPlanetArtifact(locationId, artifactId).tokenType != TokenType.Unknown, + LibArtifact.getPlanetArtifact(locationId, artifactId).tokenType != TokenType.Unknown, "this artifact is not on this planet" ); @@ -250,11 +252,11 @@ library LibArtifactUtils { emit ArtifactDeactivated(msg.sender, locationId, artifactId); // burn it after use. will be owned by contract but not on a planet anyone can control - LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); + LibArtifact.takeArtifactOffPlanet(locationId, artifactId); } // this is fine even tho some artifacts are immediately deactivated, because // those artifacts do not buff the planet. - LibGameUtils._buffPlanet(locationId, LibGameUtils._getUpgradeForArtifact(artifact)); + LibGameUtils._buffPlanet(locationId, LibArtifact.getUpgradeForArtifact(artifact)); } function deactivateArtifact(uint256 locationId) public { @@ -267,15 +269,14 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); - Artifact memory artifact = getActiveArtifact(locationId); - require( - artifact.tokenType != TokenType.Unknown, - "this artifact is not activated on this planet" + LibArtifact.hasActiveArtifact(locationId), + "there is no artifact to deactivate on this planet" ); - // artifact.lastDeactivated = block.timestamp; - // LOL just pretend there is a wormhole. + Artifact memory artifact = LibArtifact.getActiveArtifact(locationId); + + // In case just pretend there is a wormhole. gs().planetWormholes[locationId] = 0; gs().planetActiveArtifact[locationId] = 0; gs().planetArtifactActivationTime[locationId] = 0; @@ -286,10 +287,10 @@ library LibArtifactUtils { artifact.artifactType == ArtifactType.PhotoidCannon; if (shouldBurn) { // burn it after use. will be owned by contract but not on a planet anyone can control - LibGameUtils._takeArtifactOffPlanet(locationId, artifact.id); + LibArtifact.takeArtifactOffPlanet(locationId, artifact.id); } - LibGameUtils._debuffPlanet(locationId, LibGameUtils._getUpgradeForArtifact(artifact)); + LibGameUtils._debuffPlanet(locationId, LibArtifact.getUpgradeForArtifact(artifact)); } function depositArtifact( @@ -307,7 +308,7 @@ library LibArtifactUtils { ); require(planet.owner == msg.sender, "you can only deposit on a planet you own"); - Artifact memory artifact = decodeArtifact(artifactId); + Artifact memory artifact = LibArtifact.decode(artifactId); require( planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to deposit this artifact" @@ -319,7 +320,7 @@ library LibArtifactUtils { "too many tokens on this planet" ); - LibGameUtils._putArtifactOnPlanet(locationId, artifactId); + LibArtifact.putArtifactOnPlanet(locationId, artifactId); // artifactId, curr owner, new owner DFArtifactFacet(address(this)).transferArtifact(artifactId, msg.sender, coreAddress); } @@ -333,14 +334,14 @@ library LibArtifactUtils { ); require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.owner == msg.sender, "you can only withdraw from a planet you own"); - Artifact memory artifact = getPlanetArtifact(locationId, artifactId); + Artifact memory artifact = LibArtifact.getPlanetArtifact(locationId, artifactId); require( planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to withdraw this artifact" ); require(!LibSpaceship.isShip(artifactId), "cannot withdraw spaceships"); - LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); + LibArtifact.takeArtifactOffPlanet(locationId, artifactId); // artifactId, curr owner, new owner DFArtifactFacet(address(this)).transferArtifact(artifactId, address(this), msg.sender); @@ -375,94 +376,4 @@ library LibArtifactUtils { return false; } - - /** - * @notice Create the collection ID for a given artifact - * @param _collectionType type of artifact - * @param _rarity rarity of artifact - * @param _artifactType of artifact - * @param _biome of artifact - * @notice this is not a struct because I can't figure out how to bit shift a uint in a struct. - */ - function encodeArtifact( - uint256 _collectionType, - uint256 _rarity, - uint256 _artifactType, - uint256 _biome - ) public pure returns (uint256) { - uint256 tokenType = _collectionType << LibUtils.calcBitShift(uint8(ArtifactInfo.TokenType)); - uint256 rarity = _rarity << LibUtils.calcBitShift(uint8(ArtifactInfo.ArtifactRarity)); - uint256 artifactType = _artifactType << - LibUtils.calcBitShift(uint8(ArtifactInfo.ArtifactType)); - uint256 biome = _biome << LibUtils.calcBitShift(uint8(ArtifactInfo.Biome)); - return tokenType + rarity + artifactType + biome; - } - - /** - * @notice Fetch the Artifact for the given id - * @param artifactId type of artifact - */ - function decodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { - bytes memory _b = abi.encodePacked(artifactId); - // 0 is left most element. 0 is given the property Unknown in ArtifactInfo. - - // Note: Bit shifting requires an index greater than zero. This is why the ArtifactInfo has - // Unknown as the zero property, so calcBitShift(ArtifactInfo.Level) is correct. - // As a consequence, we need to - // offset fetching the relevant byte from the artifactId by 1. - // However - uint8 tokenType = uint8(_b[uint8(ArtifactInfo.TokenType) - 1]); - uint8 rarity = uint8(_b[uint8(ArtifactInfo.ArtifactRarity) - 1]); - uint8 artifactType = uint8(_b[uint8(ArtifactInfo.ArtifactType) - 1]); - uint8 biome = uint8(_b[uint8(ArtifactInfo.Biome) - 1]); - - Artifact memory a = Artifact({ - id: artifactId, - tokenType: TokenType(tokenType), - rarity: ArtifactRarity(rarity), - artifactType: ArtifactType(artifactType), - planetBiome: Biome(biome) - }); - - return a; - } - - // if the given planet has an activated artifact on it, then return the artifact - // otherwise, return a 'null artifact' - function getActiveArtifact(uint256 locationId) public view returns (Artifact memory) { - uint256 artifactId = gs().planetActiveArtifact[locationId]; - if (artifactId != 0) return decodeArtifact(artifactId); - - return _nullArtifactProperties(); - } - - function _nullArtifactProperties() private pure returns (Artifact memory) { - return - Artifact( - 0, - TokenType.Unknown, - ArtifactRarity.Unknown, - ArtifactType.Unknown, - Biome.Unknown - ); - } - - // if the given artifact is on the given planet, then return the artifact - // otherwise, return a 'null' artifact - function getPlanetArtifact(uint256 locationId, uint256 artifactId) - public - view - returns (Artifact memory a) - { - bool found = false; - for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { - a = decodeArtifact(artifactId); - found = true; - return a; - } - } - - require(found, "artifact not found"); - } } diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 1404ac96..9755c863 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -6,6 +6,7 @@ import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; // Library imports import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; +import {LibArtifact} from "./LibArtifact.sol"; import {LibUtils} from "./LibUtils.sol"; // Storage imports @@ -147,61 +148,6 @@ library LibGameUtils { } } - function _randomArtifactTypeAndLevelBonus( - uint256 artifactSeed, - Biome biome, - SpaceType spaceType - ) internal pure returns (ArtifactType, uint256) { - uint256 lastByteOfSeed = artifactSeed % 0xFF; - uint256 secondLastByteOfSeed = ((artifactSeed - lastByteOfSeed) / 256) % 0xFF; - - ArtifactType artifactType = ArtifactType.Pyramid; - - if (lastByteOfSeed < 39) { - artifactType = ArtifactType.Monolith; - } else if (lastByteOfSeed < 78) { - artifactType = ArtifactType.Colossus; - } - // else if (lastByteOfSeed < 117) { - // artifactType = ArtifactType.Spaceship; - // } - else if (lastByteOfSeed < 156) { - artifactType = ArtifactType.Pyramid; - } else if (lastByteOfSeed < 171) { - artifactType = ArtifactType.Wormhole; - } else if (lastByteOfSeed < 186) { - artifactType = ArtifactType.PlanetaryShield; - } else if (lastByteOfSeed < 201) { - artifactType = ArtifactType.PhotoidCannon; - } else if (lastByteOfSeed < 216) { - artifactType = ArtifactType.BloomFilter; - } else if (lastByteOfSeed < 231) { - artifactType = ArtifactType.BlackDomain; - } else { - if (biome == Biome.Ice) { - artifactType = ArtifactType.PlanetaryShield; - } else if (biome == Biome.Lava) { - artifactType = ArtifactType.PhotoidCannon; - } else if (biome == Biome.Wasteland) { - artifactType = ArtifactType.BloomFilter; - } else if (biome == Biome.Corrupted) { - artifactType = ArtifactType.BlackDomain; - } else { - artifactType = ArtifactType.Wormhole; - } - artifactType = ArtifactType.PhotoidCannon; - } - - uint256 bonus = 0; - if (secondLastByteOfSeed < 4) { - bonus = 2; - } else if (secondLastByteOfSeed < 16) { - bonus = 1; - } - - return (artifactType, bonus); - } - // TODO v0.6: handle corrupted biomes function _getBiome(SpaceType spaceType, uint256 biomebase) public view returns (Biome) { if (spaceType == SpaceType.DEAD_SPACE) { @@ -258,221 +204,6 @@ library LibGameUtils { }); } - function _getUpgradeForArtifact(Artifact memory artifact) public pure returns (Upgrade memory) { - if (artifact.artifactType == ArtifactType.PlanetaryShield) { - uint256[6] memory defenseMultipliersPerRarity = [uint256(100), 150, 200, 300, 450, 650]; - - return - Upgrade({ - popCapMultiplier: 100, - popGroMultiplier: 100, - rangeMultiplier: 20, - speedMultiplier: 20, - defMultiplier: defenseMultipliersPerRarity[uint256(artifact.rarity)] - }); - } - - if (artifact.artifactType == ArtifactType.PhotoidCannon) { - uint256[6] memory def = [uint256(100), 50, 40, 30, 20, 10]; - return - Upgrade({ - popCapMultiplier: 100, - popGroMultiplier: 100, - rangeMultiplier: 100, - speedMultiplier: 100, - defMultiplier: def[uint256(artifact.rarity)] - }); - } - - if (uint256(artifact.artifactType) >= 5) { - return - Upgrade({ - popCapMultiplier: 100, - popGroMultiplier: 100, - rangeMultiplier: 100, - speedMultiplier: 100, - defMultiplier: 100 - }); - } - - Upgrade memory ret = Upgrade({ - popCapMultiplier: 100, - popGroMultiplier: 100, - rangeMultiplier: 100, - speedMultiplier: 100, - defMultiplier: 100 - }); - - if (artifact.artifactType == ArtifactType.Monolith) { - ret.popCapMultiplier += 5; - ret.popGroMultiplier += 5; - } else if (artifact.artifactType == ArtifactType.Colossus) { - ret.speedMultiplier += 5; - } else if (artifact.artifactType == ArtifactType.Spaceship) { - ret.rangeMultiplier += 5; - } else if (artifact.artifactType == ArtifactType.Pyramid) { - ret.defMultiplier += 5; - } - - if (artifact.planetBiome == Biome.Ocean) { - ret.speedMultiplier += 5; - ret.defMultiplier += 5; - } else if (artifact.planetBiome == Biome.Forest) { - ret.defMultiplier += 5; - ret.popCapMultiplier += 5; - ret.popGroMultiplier += 5; - } else if (artifact.planetBiome == Biome.Grassland) { - ret.popCapMultiplier += 5; - ret.popGroMultiplier += 5; - ret.rangeMultiplier += 5; - } else if (artifact.planetBiome == Biome.Tundra) { - ret.defMultiplier += 5; - ret.rangeMultiplier += 5; - } else if (artifact.planetBiome == Biome.Swamp) { - ret.speedMultiplier += 5; - ret.rangeMultiplier += 5; - } else if (artifact.planetBiome == Biome.Desert) { - ret.speedMultiplier += 10; - } else if (artifact.planetBiome == Biome.Ice) { - ret.rangeMultiplier += 10; - } else if (artifact.planetBiome == Biome.Wasteland) { - ret.defMultiplier += 10; - } else if (artifact.planetBiome == Biome.Lava) { - ret.popCapMultiplier += 10; - ret.popGroMultiplier += 10; - } else if (artifact.planetBiome == Biome.Corrupted) { - ret.rangeMultiplier += 5; - ret.speedMultiplier += 5; - ret.popCapMultiplier += 5; - ret.popGroMultiplier += 5; - } - - uint256 scale = 1 + (uint256(artifact.rarity) / 2); - - ret.popCapMultiplier = scale * ret.popCapMultiplier - (scale - 1) * 100; - ret.popGroMultiplier = scale * ret.popGroMultiplier - (scale - 1) * 100; - ret.speedMultiplier = scale * ret.speedMultiplier - (scale - 1) * 100; - ret.rangeMultiplier = scale * ret.rangeMultiplier - (scale - 1) * 100; - ret.defMultiplier = scale * ret.defMultiplier - (scale - 1) * 100; - - return ret; - } - - function artifactRarityFromPlanetLevel(uint256 planetLevel) - public - pure - returns (ArtifactRarity) - { - if (planetLevel <= 1) return ArtifactRarity.Common; - else if (planetLevel <= 3) return ArtifactRarity.Rare; - else if (planetLevel <= 5) return ArtifactRarity.Epic; - else if (planetLevel <= 7) return ArtifactRarity.Legendary; - else return ArtifactRarity.Mythic; - } - - // planets can have multiple artifacts on them. this function updates all the - // internal contract book-keeping to reflect that the given artifact was - // put on. note that this function does not transfer the artifact. - function _putArtifactOnPlanet(uint256 locationId, uint256 artifactId) public { - gs().planetArtifacts[locationId].push(artifactId); - } - - function _putSpaceshipOnPlanet(uint256 locationId, uint256 spaceshipId) public { - gs().planetSpaceships[locationId].push(spaceshipId); - } - - // TODO: Why not burn ? - // planets can have multiple artifacts on them. this function updates all the - // internal contract book-keeping to reflect that the given artifact was - // taken off the given planet. note that this function does not transfer the - // artifact. - // - // if the given artifact is not on the given planet, reverts - // if the given artifact is currently activated, reverts - - /** - * Should remove artifactId from planet with locationId if artifactId exists AND is not active. - */ - - function _takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) public { - uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; - - bool hadTheArtifact = false; - - for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { - require( - !isActivated(locationId, artifactId), - "you cannot take an activated artifact off a planet" - ); - - gs().planetArtifacts[locationId][i] = gs().planetArtifacts[locationId][ - artifactsOnThisPlanet - 1 - ]; - - hadTheArtifact = true; - break; - } - } - - require(hadTheArtifact, "this artifact was not present on this planet"); - gs().planetArtifacts[locationId].pop(); - } - - function _takeSpaceshipOffPlanet(uint256 locationId, uint256 spaceshipId) public { - uint256 shipsOnThisPlanet = gs().planetSpaceships[locationId].length; - - bool hadTheShip = false; - - for (uint256 i = 0; i < shipsOnThisPlanet; i++) { - if (gs().planetSpaceships[locationId][i] == spaceshipId) { - require( - !isActivated(locationId, spaceshipId), - "you cannot take an activated spaceship off a planet" - ); - - gs().planetSpaceships[locationId][i] = gs().planetSpaceships[locationId][ - shipsOnThisPlanet - 1 - ]; - - hadTheShip = true; - break; - } - } - - require(hadTheShip, "this ship was not present on this planet"); - gs().planetSpaceships[locationId].pop(); - } - - // an artifact is only considered 'activated' if this method returns true. - // we do not have an `isActive` field on artifact; the times that the - // artifact was last activated and deactivated are sufficent to determine - // whether or not the artifact is activated. - - function isActivated(uint256 locationId, uint256 artifactId) public view returns (bool) { - return (gs().planetActiveArtifact[locationId] == artifactId); - } - - function isArtifactOnPlanet(uint256 locationId, uint256 artifactId) public view returns (bool) { - for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { - return true; - } - } - - return false; - } - - function isSpaceshipOnPlanet(uint256 locationId, uint256 shipId) public view returns (bool) { - for (uint256 i; i < gs().planetSpaceships[locationId].length; i++) { - if (gs().planetSpaceships[locationId][i] == shipId) { - return true; - } - } - - return false; - } - // the space junk that a planet starts with function getPlanetDefaultSpaceJunk(Planet memory planet) public view returns (uint256) { if (planet.isHomePlanet) return 0; diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 8c61543b..818553fe 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -353,9 +353,9 @@ library LibPlanet { for (uint256 i = 0; i < 12; i++) { if (tokenIdsToAddToPlanet[i] != 0) { if (LibSpaceship.isShip(tokenIdsToAddToPlanet[i])) { - LibGameUtils._putSpaceshipOnPlanet(location, tokenIdsToAddToPlanet[i]); + LibSpaceship.putSpaceshipOnPlanet(location, tokenIdsToAddToPlanet[i]); } else if (LibArtifact.isArtifact(tokenIdsToAddToPlanet[i])) { - LibGameUtils._putArtifactOnPlanet(location, tokenIdsToAddToPlanet[i]); + LibArtifact.putArtifactOnPlanet(location, tokenIdsToAddToPlanet[i]); } } } diff --git a/eth/contracts/libraries/LibSpaceship.sol b/eth/contracts/libraries/LibSpaceship.sol index 09bf5683..3d143a32 100644 --- a/eth/contracts/libraries/LibSpaceship.sol +++ b/eth/contracts/libraries/LibSpaceship.sol @@ -12,11 +12,16 @@ import "hardhat/console.sol"; import {LibUtils} from "./LibUtils.sol"; // Storage imports +import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; // Type imports import {Spaceship, SpaceshipInfo, SpaceshipType, TokenType} from "../DFTypes.sol"; library LibSpaceship { + function gs() internal pure returns (GameStorage storage) { + return LibStorage.gameStorage(); + } + /** * @notice Create the token ID for a Spaceship with the following properties: * @param spaceship Spaceship @@ -57,4 +62,37 @@ library LibSpaceship { uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); return (TokenType(tokenType) == TokenType.Spaceship); } + + function isSpaceshipOnPlanet(uint256 locationId, uint256 shipId) internal view returns (bool) { + for (uint256 i; i < gs().planetSpaceships[locationId].length; i++) { + if (gs().planetSpaceships[locationId][i] == shipId) { + return true; + } + } + return false; + } + + function putSpaceshipOnPlanet(uint256 locationId, uint256 spaceshipId) internal { + gs().planetSpaceships[locationId].push(spaceshipId); + } + + function takeSpaceshipOffPlanet(uint256 locationId, uint256 spaceshipId) internal { + uint256 shipsOnThisPlanet = gs().planetSpaceships[locationId].length; + + bool hadTheShip = false; + + for (uint256 i = 0; i < shipsOnThisPlanet; i++) { + if (gs().planetSpaceships[locationId][i] == spaceshipId) { + gs().planetSpaceships[locationId][i] = gs().planetSpaceships[locationId][ + shipsOnThisPlanet - 1 + ]; + + hadTheShip = true; + break; + } + } + + require(hadTheShip, "this ship was not present on this planet"); + gs().planetSpaceships[locationId].pop(); + } } diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index 431b5ae0..adedbfb2 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -309,15 +309,9 @@ export async function deployAndCut( return [diamond, diamondInit, initReceipt] as const; } -export async function deployGetterFacet( - {}, - { LibArtifactUtils }: Libraries, - hre: HardhatRuntimeEnvironment -) { +export async function deployGetterFacet({}, {}: Libraries, hre: HardhatRuntimeEnvironment) { const factory = await hre.ethers.getContractFactory('DFGetterFacet', { - libraries: { - LibArtifactUtils, - }, + libraries: {}, }); const contract = await factory.deploy(); await contract.deployTransaction.wait(); diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index e25450d5..7b618752 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -80,40 +80,20 @@ describe('DarkForestArtifacts', function () { }); describe('it tests basic artifact actions', function () { - it('logs bits for artifact old', async function () { - // Must be valid options - const _collectionType = '0x01'; - const _rarity = ArtifactRarity.Legendary; - const _artifactType = ArtifactType.Colossus; - const _biome = Biome.DESERT; - const res = await world.contract.encodeArtifact( - _collectionType, - _rarity, - _artifactType, - _biome - ); - const { tokenType, rarity, planetBiome, artifactType } = await world.contract.getArtifact( - res - ); - expect(tokenType).to.equal(Number(_collectionType)); - expect(rarity).to.equal(Number(_rarity)); - expect(planetBiome).to.equal(Number(_biome)); - expect(artifactType).to.equal(Number(_artifactType)); - }); it('encodes and decodes artifact', async function () { // Must be valid options const tokenType = TokenType.Artifact; const rarity = ArtifactRarity.Legendary; const artifactType = ArtifactType.Colossus; const planetBiome = Biome.DESERT; - const res = await world.contract.testEncodeArtifact({ + const res = await world.contract.encodeArtifact({ id: 0, tokenType, rarity, artifactType, planetBiome, }); - const a = await world.contract.getArtifact(res); + const a = await world.contract.decodeArtifact(res); expect(tokenType).to.equal(Number(a.tokenType)); expect(rarity).to.equal(Number(a.rarity)); expect(artifactType).to.equal(Number(a.artifactType)); @@ -123,12 +103,12 @@ describe('DarkForestArtifacts', function () { // Must be valid options const tokenType = TokenType.Spaceship; const spaceshipType = 2; - const res = await world.contract.testEncodeSpaceship({ + const res = await world.contract.encodeSpaceship({ id: 0, tokenType, spaceshipType, }); - const a = await world.contract.testDecodeSpaceship(res); + const a = await world.contract.decodeSpaceship(res); expect(tokenType).to.equal(Number(a.tokenType)); expect(spaceshipType).to.equal(Number(a.spaceshipType)); }); @@ -144,10 +124,9 @@ describe('DarkForestArtifacts', function () { ); const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); - prettyPrintToken(await world.user1Core.getArtifact(artifactId)); + prettyPrintToken(await world.user1Core.decodeArtifact(artifactId)); // artifact and gear should be on planet. Gear is 0 and Artifact is 1. const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); @@ -475,7 +454,7 @@ describe('DarkForestArtifacts', function () { await increaseBlockchainTime(); // move artifact - world.user1Core.move( + await world.user1Core.move( ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) ); @@ -491,7 +470,7 @@ describe('DarkForestArtifacts', function () { world.user1Core.move( ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, 12345) ) - ).to.be.revertedWith('this artifact was not present on this planet'); + ).to.be.revertedWith('cannot move token of this type'); }); }); @@ -649,7 +628,7 @@ describe('DarkForestArtifacts', function () { TokenType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.getArtifact(artifactId)); + prettyPrintToken(await world.contract.decodeArtifact(artifactId)); activateAndConfirm(world.user1Core, from.id, artifactId, to.id); @@ -810,7 +789,7 @@ describe('DarkForestArtifacts', function () { TokenType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.getArtifact(newTokenId)); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); @@ -858,7 +837,7 @@ describe('DarkForestArtifacts', function () { { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.getArtifact(newTokenId)); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move( @@ -966,7 +945,7 @@ describe('DarkForestArtifacts', function () { TokenType.Artifact, { rarity: ArtifactRarity.Rare as ArtifactRarity, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.getArtifact(newTokenId)); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); const planetBeforeActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); await activateAndConfirm(world.user1Core, LVL3_SPACETIME_1.id, newTokenId); @@ -1011,7 +990,7 @@ describe('DarkForestArtifacts', function () { TokenType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.getArtifact(newTokenId)); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); await world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index 31363352..a288174b 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -333,14 +333,20 @@ export async function createArtifact( contract: DarkForest, owner: string, planet: TestLocation, - type: ArtifactType, - collectionType = TokenType.Artifact, + artifactType: ArtifactType, + tokenType = TokenType.Artifact, { rarity, biome }: { rarity?: ArtifactRarity; biome?: Biome } = {} ) { rarity ||= ArtifactRarity.Common; biome ||= Biome.FOREST; - const tokenId = await contract.encodeArtifact(collectionType, rarity, type, biome); + const tokenId = await contract.encodeArtifact({ + id: 0, + tokenType, + rarity, + artifactType, + planetBiome: biome, + }); await contract.adminGiveArtifact({ tokenId, discoverer: owner, @@ -348,7 +354,7 @@ export async function createArtifact( planetId: planet.id, rarity: rarity.toString(), biome: biome.toString(), - artifactType: type.toString(), + artifactType: artifactType.toString(), controller: ZERO_ADDRESS, }); From fd187735ff31d4c12b7df185df2429f0422343d9 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sat, 1 Oct 2022 22:54:11 +0100 Subject: [PATCH 34/55] feat: working DFTokenFacet for silver mint --- eth/contracts/DFDiamond.sol | 12 +-- eth/contracts/facets/DFArtifactFacet.sol | 58 +++-------- eth/contracts/facets/DFMoveFacet.sol | 2 +- eth/contracts/facets/DFTokenFacet.sol | 103 +++++++++++++++++++ eth/contracts/libraries/LibArtifactUtils.sol | 4 +- eth/contracts/libraries/LibPlanet.sol | 5 +- eth/tasks/deploy.ts | 12 +++ eth/test/DFArtifacts.test.ts | 76 -------------- eth/test/DFLobby.test.ts | 15 +-- eth/utils/diamond.ts | 2 +- 10 files changed, 153 insertions(+), 136 deletions(-) create mode 100644 eth/contracts/facets/DFTokenFacet.sol diff --git a/eth/contracts/DFDiamond.sol b/eth/contracts/DFDiamond.sol index ab2050ab..9a1be719 100644 --- a/eth/contracts/DFDiamond.sol +++ b/eth/contracts/DFDiamond.sol @@ -13,9 +13,9 @@ import {ERC165, IERC165, ERC165Storage} from "@solidstate/contracts/introspectio import {DiamondBase, DiamondBaseStorage} from "@solidstate/contracts/proxy/diamond/base/DiamondBase.sol"; import {DiamondReadable, IDiamondReadable} from "@solidstate/contracts/proxy/diamond/readable/DiamondReadable.sol"; import {DiamondWritable, IDiamondWritable} from "@solidstate/contracts/proxy/diamond/writable/DiamondWritable.sol"; -import {IERC721} from "@solidstate/contracts/token/ERC721/IERC721.sol"; -import {IERC721Metadata} from "@solidstate/contracts/token/ERC721/metadata/IERC721Metadata.sol"; -import {IERC721Enumerable} from "@solidstate/contracts/token/ERC721/enumerable/IERC721Enumerable.sol"; +import {IERC1155} from "@solidstate/contracts/token/ERC1155/IERC1155.sol"; +import {IERC1155Metadata} from "@solidstate/contracts/token/ERC1155/metadata/IERC1155Metadata.sol"; +import {IERC1155Enumerable} from "@solidstate/contracts/token/ERC1155/enumerable/IERC1155Enumerable.sol"; /** * @title SolidState "Diamond" proxy reference implementation @@ -58,9 +58,9 @@ contract DFDiamond is DiamondBase, DiamondReadable, DiamondWritable, Ownable, ER erc165.setSupportedInterface(type(IERC173).interfaceId, true); // Store ERC721 interface - erc165.setSupportedInterface(type(IERC721).interfaceId, true); - erc165.setSupportedInterface(type(IERC721Metadata).interfaceId, true); - erc165.setSupportedInterface(type(IERC721Enumerable).interfaceId, true); + erc165.setSupportedInterface(type(IERC1155).interfaceId, true); + erc165.setSupportedInterface(type(IERC1155Metadata).interfaceId, true); + erc165.setSupportedInterface(type(IERC1155Enumerable).interfaceId, true); // register Diamond diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index e92107f2..b66d17bf 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.0; // Contract imports -import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; +import {DFTokenFacet} from "./DFTokenFacet.sol"; // Library Imports import {LibPermissions} from "../libraries/LibPermissions.sol"; @@ -23,7 +23,7 @@ import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtif import "hardhat/console.sol"; -contract DFArtifactFacet is WithStorage, SolidStateERC1155 { +contract DFArtifactFacet is WithStorage { event PlanetProspected(address player, uint256 loc); event ArtifactFound(address player, uint256 artifactId, uint256 loc); event ArtifactDeposited(address player, uint256 artifactId, uint256 loc); @@ -50,18 +50,18 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { _; } - modifier onlyAdminOrCore() { + modifier onlyAdmin() { require( - msg.sender == gs().diamondAddress || msg.sender == LibPermissions.contractOwner(), - "Only the Core or Admin addresses can fiddle with artifacts." + msg.sender == LibPermissions.contractOwner(), + "Only Admin address can perform this action." ); _; } - modifier onlyAdmin() { + modifier onlyAdminOrCore() { require( - msg.sender == LibPermissions.contractOwner(), - "Only Admin address can perform this action." + msg.sender == gs().diamondAddress || msg.sender == LibPermissions.contractOwner(), + "Only the Core or Admin addresses can fiddle with artifacts." ); _; } @@ -74,11 +74,15 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { require(tokenId >= 1, "token id must be positive"); require(LibArtifact.isArtifact(tokenId), "token must be Artifact"); // Account, Id, Amount, Data - _mint(owner, tokenId, 1, ""); + DFTokenFacet(address(this)).mint(owner, tokenId, 1); return LibArtifact.decode(tokenId); } + function tokenIsOwnedBy(address owner, uint256 tokenId) public view returns (bool) { + return DFTokenFacet(address(this)).balanceOf(owner, tokenId) > 0; + } + function createSpaceship(uint256 tokenId, address owner) public onlyAdminOrCore @@ -88,7 +92,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { require(LibSpaceship.isShip(tokenId), "token must be Spaceship"); // Account, Id, Amount, Data - _mint(owner, tokenId, 1, ""); + DFTokenFacet(address(this)).mint(owner, tokenId, 1); return getSpaceship(tokenId); } @@ -122,17 +126,13 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { ) public onlyAdminOrCore { if (newOwner == address(0)) { // account, id, amount. - _burn(owner, tokenId, 1); + DFTokenFacet(address(this)).burn(owner, tokenId, 1); } else { // sender receiver id amount data - _transfer(owner, owner, newOwner, tokenId, 1, ""); + DFTokenFacet(address(this)).transfer(owner, owner, newOwner, tokenId, 1, ""); } } - function tokenExists(address owner, uint256 tokenId) public view returns (bool) { - return balanceOf(owner, tokenId) > 0; - } - function findArtifact( uint256[2] memory _a, uint256[2][2] memory _b, @@ -283,30 +283,4 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { gs().players[msg.sender].claimedShips = true; } - - function _beforeTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual override { - uint256 length = ids.length; - for (uint256 i = 0; i < length; i++) { - // Only core contract can transfer Spaceships - if (LibSpaceship.isShip(ids[i])) { - require(msg.sender == gs().diamondAddress, "player cannot transfer a Spaceship"); - } - } - } - - /** - * @notice set per-token metadata URI - * @param tokenId token whose metadata URI to set - * @param tokenURI per-token URI - */ - function setTokenURI(uint256 tokenId, string memory tokenURI) public { - _setTokenURI(tokenId, tokenURI); - } } diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index a10d438c..7955a70b 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -202,7 +202,7 @@ contract DFMoveFacet is WithStorage { require(args.popMoved == 0, "ship moves must move 0 energy"); require(args.silverMoved == 0, "ship moves must move 0 silver"); require( - DFArtifactFacet(address(this)).tokenExists(msg.sender, args.movedArtifactId), + DFArtifactFacet(address(this)).tokenIsOwnedBy(msg.sender, args.movedArtifactId), "you can only move your own ships" ); } else { diff --git a/eth/contracts/facets/DFTokenFacet.sol b/eth/contracts/facets/DFTokenFacet.sol new file mode 100644 index 00000000..a69d5ca0 --- /dev/null +++ b/eth/contracts/facets/DFTokenFacet.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +// Contract imports +import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; + +// Library Imports +import {LibPermissions} from "../libraries/LibPermissions.sol"; +import {LibSpaceship} from "../libraries/LibSpaceship.sol"; + +// Storage imports +import {WithStorage} from "../libraries/LibStorage.sol"; + +import "hardhat/console.sol"; + +contract DFTokenFacet is WithStorage, SolidStateERC1155 { + modifier onlyAdminOrCore() { + require( + msg.sender == gs().diamondAddress || msg.sender == LibPermissions.contractOwner(), + "Only the Core or Admin addresses can fiddle with tokens." + ); + _; + } + + function _beforeTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual override { + uint256 length = ids.length; + for (uint256 i = 0; i < length; i++) { + // Only core contract can transfer Spaceships + if (LibSpaceship.isShip(ids[i])) { + require(msg.sender == gs().diamondAddress, "player cannot transfer a Spaceship"); + } + } + } + + /** + * @notice set per-token metadata URI + * @param tokenId token whose metadata URI to set + * @param tokenURI per-token URI + */ + function setTokenURI(uint256 tokenId, string memory tokenURI) public { + _setTokenURI(tokenId, tokenURI); + } + + /** + * @notice ERC1155 mint + * @param owner of new tokens + * @param tokenId tokenId to mint + * @param amount amount of tokens to mint + */ + function mint( + address owner, + uint256 tokenId, + uint256 amount + ) public onlyAdminOrCore { + _mint(owner, tokenId, amount, ""); + } + + /** + * @notice burn given quantity of tokens held by given address + * @param account holder of tokens to burn + * @param id token ID + * @param amount quantity of tokens to burn + */ + function burn( + address account, + uint256 id, + uint256 amount + ) public onlyAdminOrCore { + _burn(account, id, amount); + } + + /** + * @notice transfer tokens between given addresses + * @dev ERC1155Receiver implementation is not checked + * @param operator executor of transfer + * @param sender sender of tokens + * @param recipient receiver of tokens + * @param id token ID + * @param amount quantity of tokens to transfer + * @param data data payload + */ + function transfer( + address operator, + address sender, + address recipient, + uint256 id, + uint256 amount, + bytes memory data + ) public onlyAdminOrCore { + _transfer(operator, sender, recipient, id, amount, data); + } + + function tokenExists(address owner, uint256 tokenId) public view returns (bool) { + return balanceOf(owner, tokenId) > 0; + } +} diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index ec5581aa..a28eeab5 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -303,7 +303,7 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.planetType == PlanetType.TRADING_POST, "can only deposit on trading posts"); require( - DFArtifactFacet(address(this)).tokenExists(msg.sender, artifactId), + DFArtifactFacet(address(this)).tokenIsOwnedBy(msg.sender, artifactId), "you can only deposit artifacts you own" ); require(planet.owner == msg.sender, "you can only deposit on a planet you own"); @@ -368,7 +368,7 @@ library LibArtifactUtils { Spaceship memory spaceship = LibSpaceship.decode(tokenIds[i]); if ( spaceship.spaceshipType == SpaceshipType.ShipGear && - DFArtifactFacet(address(this)).tokenExists(msg.sender, tokenIds[i]) + DFArtifactFacet(address(this)).tokenIsOwnedBy(msg.sender, tokenIds[i]) ) { return true; } diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 818553fe..35cd7544 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; // Contract imports import {DFVerifierFacet} from "../facets/DFVerifierFacet.sol"; -import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; +import {DFTokenFacet} from "../facets/DFTokenFacet.sol"; // Library imports import {LibArtifact} from "./LibArtifact.sol"; @@ -380,6 +380,9 @@ library LibPlanet { // so any of those values coming from the contracts need to be divided by // `CONTRACT_PRECISION` to get their true integer value. uint256 scoreGained = silverToWithdraw / 1000; + // increase silver token count; + uint256 tokenId = 3 << 248; + DFTokenFacet(address(this)).mint(msg.sender, tokenId, scoreGained); scoreGained = (scoreGained * gameConstants().SILVER_SCORE_VALUE) / 100; gs().players[msg.sender].score += scoreGained; } diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index adedbfb2..4f956bd8 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -246,6 +246,7 @@ export async function deployAndCut( const coreFacet = await deployCoreFacet({}, libraries, hre); const moveFacet = await deployMoveFacet({}, libraries, hre); const captureFacet = await deployCaptureFacet({}, libraries, hre); + const tokenFacet = await deployTokenFacet({}, libraries, hre); const artifactFacet = await deployArtifactFacet( { diamondAddress: diamond.address }, libraries, @@ -270,6 +271,7 @@ export async function deployAndCut( ...changes.getFacetCuts('DFAdminFacet', adminFacet), ...changes.getFacetCuts('DFLobbyFacet', lobbyFacet), ...changes.getFacetCuts('DFRewardFacet', rewardFacet), + ...changes.getFacetCuts('DFTokenFacet', tokenFacet), ]; if (isDev) { @@ -369,6 +371,16 @@ export async function deployVerifierFacet({}, {}: Libraries, hre: HardhatRuntime return contract; } +export async function deployTokenFacet({}, {}: Libraries, hre: HardhatRuntimeEnvironment) { + const factory = await hre.ethers.getContractFactory('DFTokenFacet', { + libraries: {}, + }); + const contract = await factory.deploy(); + await contract.deployTransaction.wait(); + console.log(`DFTokenFacet deployed to: ${contract.address}`); + return contract; +} + export async function deployArtifactFacet( {}, { LibGameUtils, LibPlanet, LibArtifactUtils }: Libraries, diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index 7b618752..2b081f24 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -2,13 +2,11 @@ import { ArtifactRarity, ArtifactType, Biome, SpaceshipType, TokenType } from '@ import { loadFixture, mine } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import hre from 'hardhat'; -import { TestLocation } from './utils/TestLocation'; import { activateAndConfirm, conquerUnownedPlanet, createArtifact, getArtifactsOnPlanet, - getArtifactsOwnedBy, getCurrentTime, getStatSum, increaseBlockchainTime, @@ -30,7 +28,6 @@ import { LVL3_UNOWNED_NEBULA, LVL4_UNOWNED_DEEP_SPACE, LVL6_SPACETIME, - SPACE_PERLIN, SPAWN_PLANET_1, SPAWN_PLANET_2, ZERO_PLANET, @@ -347,79 +344,6 @@ describe('DarkForestArtifacts', function () { "you can't find an artifact on this planet" ); }); - // TODO: Why do we need this test? - it.skip('should mint randomly', async function () { - // This can take upwards of 90000ms in CI - this.timeout(0); - - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); - - /* eslint-disable @typescript-eslint/no-explicit-any */ - let artifacts: any; - let prevLocation = SPAWN_PLANET_1; - - for (let i = 0; i < 20; i++) { - // byte #8 is 18_16 = 24_10 so it's a ruins planet - const randomHex = - `00007c2512896efb182d462faee0000fb33d58930eb9e6b4fbae6d048e9c44` + - (i >= 10 ? i.toString()[0] : 0) + - '' + - (i % 10); - - const planetWithArtifactLoc = new TestLocation({ - hex: randomHex, - perlin: SPACE_PERLIN, - distFromOrigin: 1998, - }); - - await world.contract.adminInitializePlanet( - planetWithArtifactLoc.id, - planetWithArtifactLoc.perlin - ); - - await world.contract.adminGiveSpaceShip( - planetWithArtifactLoc.id, - world.user1.address, - ArtifactType.ShipGear - ); - - await increaseBlockchainTime(); - - await world.user1Core.move( - ...makeMoveArgs(prevLocation, planetWithArtifactLoc, 0, 80000, 0) - ); // move 80000 from asteroids but 160000 from ruins since ruins are higher level - await increaseBlockchainTime(); - - await world.user1Core.prospectPlanet(planetWithArtifactLoc.id); - await increaseBlockchainTime(); - - await world.user1Core.findArtifact(...makeFindArtifactArgs(planetWithArtifactLoc)); - await increaseBlockchainTime(); - - const artifactsOnPlanet = await getArtifactsOnPlanet(world, planetWithArtifactLoc.id); - const artifactId = artifactsOnPlanet[0].id; - - await world.user1Core.move( - ...makeMoveArgs(planetWithArtifactLoc, LVL3_SPACETIME_1, 0, 40000, 0, artifactId) - ); - await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, artifactId); - artifacts = await getArtifactsOwnedBy(world.contract, world.user1.address); - - expect(artifacts[artifacts.length - 1].planetBiome).to.eq(4); // tundra - expect(artifacts[artifacts.length - 1].discoverer).to.eq(world.user1.address); - expect(artifacts[artifacts.length - 1].rarity).to.be.at.least(1); - - prevLocation = planetWithArtifactLoc; - } - - const artifactTypeSet = new Set(); - - for (let i = 0; i < artifacts.length; i++) { - artifactTypeSet.add(artifacts[i].artifactType); - } - - expect(artifactTypeSet.size).to.be.greaterThan(1); - }); it('should not mint an artifact on the same planet twice', async function () { await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); diff --git a/eth/test/DFLobby.test.ts b/eth/test/DFLobby.test.ts index 4648a370..1e315436 100644 --- a/eth/test/DFLobby.test.ts +++ b/eth/test/DFLobby.test.ts @@ -5,9 +5,9 @@ import hre, { ethers } from 'hardhat'; import { createArena, defaultWorldFixture, World } from './utils/TestWorld'; const _INTERFACE_ID_IERC165 = '0x01ffc9a7'; -const _INTERFACE_ID_IERC721 = '0x80ac58cd'; -const _INTERFACE_ID_IERC721METADATA = '0x5b5e139f'; -const _INTERFACE_ID_IERC721ENUMERABLE = '0x780e9d63'; +const _INTERFACE_ID_IERC1155 = '0xd9b67a26'; +const _INTERFACE_ID_IERC1155METADATA = '0x0e89341c'; +const _INTERFACE_ID_IERC1155ENUMERABLE = '0x464FCF40'; const _INTERFACE_ID_IDIAMOND_READABLE = '0x48e2b093'; const _INTERFACE_ID_IDIAMOND_WRITABLE = '0x1f931c1c'; const _INTERFACE_ID_IERC173 = '0x7f5828d0'; @@ -52,11 +52,12 @@ describe('DarkForestLobby', function () { expect(await lobby.supportsInterface(_INTERFACE_ID_IDIAMOND_WRITABLE)).to.equal(true); expect(await lobby.supportsInterface(_INTERFACE_ID_IERC173)).to.equal(true); }); - it('new Lobby has correct ERC721 interfaces', async function () { - expect(await lobby.supportsInterface(_INTERFACE_ID_IERC721)).to.equal(true); - expect(await lobby.supportsInterface(_INTERFACE_ID_IERC721METADATA)).to.equal(true); - expect(await lobby.supportsInterface(_INTERFACE_ID_IERC721ENUMERABLE)).to.equal(true); + it('new Lobby has correct ERC1155 interfaces', async function () { + expect(await lobby.supportsInterface(_INTERFACE_ID_IERC1155)).to.equal(true); + expect(await lobby.supportsInterface(_INTERFACE_ID_IERC1155METADATA)).to.equal(true); + expect(await lobby.supportsInterface(_INTERFACE_ID_IERC1155ENUMERABLE)).to.equal(true); }); + it('test fallback', async function () { expect(await lobby.getFallbackAddress()).to.equal(ethers.constants.AddressZero); }); diff --git a/eth/utils/diamond.ts b/eth/utils/diamond.ts index ce8d589b..80664ea5 100644 --- a/eth/utils/diamond.ts +++ b/eth/utils/diamond.ts @@ -41,7 +41,7 @@ export function toSignature(abiElement: unknown): string { const signaturesToIgnore = [ // The SolidState contracts adds a `supportsInterface` function, // but we already provide that function through DiamondLoupeFacet - ['DFArtifactFacet$', 'supportsInterface(bytes4)'], + ['DFTokenFacet$', 'supportsInterface(bytes4)'], ] as const; const eventSignatures = new Set(); From ac025dc0edb427574629827302e24b79a321bcdc Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sun, 2 Oct 2022 20:46:47 +0100 Subject: [PATCH 35/55] fix: silver token type --- eth/contracts/DFTypes.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 893d328e..94d64ef4 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -253,7 +253,8 @@ enum Biome { enum TokenType { Unknown, Artifact, - Spaceship + Spaceship, + Silver } enum ArtifactInfo { From 9413f1911ab383baafce9196bf6492d2db7f827f Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sun, 2 Oct 2022 21:14:48 +0100 Subject: [PATCH 36/55] chore: rename withdraw silver tests check silver balance --- eth/contracts/DFTypes.sol | 5 ++ eth/contracts/facets/DFTokenFacet.sol | 6 +++ eth/contracts/libraries/LibPlanet.sol | 6 ++- eth/contracts/libraries/LibSilver.sol | 48 +++++++++++++++++++ .../{DFScoringRound2.test.ts => DFSilver.ts} | 12 ++--- 5 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 eth/contracts/libraries/LibSilver.sol rename eth/test/{DFScoringRound2.test.ts => DFSilver.ts} (87%) diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 94d64ef4..29d17930 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -285,3 +285,8 @@ struct Spaceship { TokenType tokenType; SpaceshipType spaceshipType; } + +enum SilverInfo { + Unknown, + TokenType +} diff --git a/eth/contracts/facets/DFTokenFacet.sol b/eth/contracts/facets/DFTokenFacet.sol index a69d5ca0..338fbc59 100644 --- a/eth/contracts/facets/DFTokenFacet.sol +++ b/eth/contracts/facets/DFTokenFacet.sol @@ -6,6 +6,7 @@ import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateE // Library Imports import {LibPermissions} from "../libraries/LibPermissions.sol"; +import {LibSilver} from "../libraries/LibSilver.sol"; import {LibSpaceship} from "../libraries/LibSpaceship.sol"; // Storage imports @@ -100,4 +101,9 @@ contract DFTokenFacet is WithStorage, SolidStateERC1155 { function tokenExists(address owner, uint256 tokenId) public view returns (bool) { return balanceOf(owner, tokenId) > 0; } + + function getSilverBalance(address player) public view returns (uint256) { + uint256 silverId = LibSilver.create(); + return balanceOf(player, silverId); + } } diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 35cd7544..49b3eb06 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -10,6 +10,7 @@ import {LibArtifact} from "./LibArtifact.sol"; import {LibArtifactUtils} from "./LibArtifactUtils.sol"; import {LibGameUtils} from "./LibGameUtils.sol"; import {LibLazyUpdate} from "./LibLazyUpdate.sol"; +import {LibSilver} from "./LibSilver.sol"; import {LibSpaceship} from "./LibSpaceship.sol"; // Storage imports @@ -381,8 +382,9 @@ library LibPlanet { // `CONTRACT_PRECISION` to get their true integer value. uint256 scoreGained = silverToWithdraw / 1000; // increase silver token count; - uint256 tokenId = 3 << 248; - DFTokenFacet(address(this)).mint(msg.sender, tokenId, scoreGained); + uint256 silverId = LibSilver.create(); + DFTokenFacet(address(this)).mint(msg.sender, silverId, scoreGained); + scoreGained = (scoreGained * gameConstants().SILVER_SCORE_VALUE) / 100; gs().players[msg.sender].score += scoreGained; } diff --git a/eth/contracts/libraries/LibSilver.sol b/eth/contracts/libraries/LibSilver.sol new file mode 100644 index 00000000..82c1d3fb --- /dev/null +++ b/eth/contracts/libraries/LibSilver.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +/** + * Library for all things Silver + */ + +// Contract imports +import "hardhat/console.sol"; + +// Library imports +import {LibUtils} from "./LibUtils.sol"; + +// Type imports +import {SilverInfo, TokenType} from "../DFTypes.sol"; + +library LibSilver { + /** + * @notice Create the token ID for Silver. + * Takes no args because silver is a fungible resource. + */ + function create() internal pure returns (uint256) { + // x << y is equivalent to the mathematical expression x * 2**y + uint256 tokenType = LibUtils.shiftLeft( + uint8(TokenType.Silver), + uint8(SilverInfo.TokenType) + ); + return tokenType; + } + + function decode(uint256 silverId) internal pure returns (uint256) { + bytes memory _b = abi.encodePacked(silverId); + // Idx is subtracted by one because each Info enum has Unknown at the zero location. + uint8 tokenIdx = uint8(SilverInfo.TokenType) - 1; + + uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + + require(tokenType == uint8(TokenType.Silver), "token is not silver"); + return silverId; + } + + function isSilver(uint256 tokenId) internal pure returns (bool) { + bytes memory _b = abi.encodePacked(tokenId); + uint8 tokenIdx = uint8(SilverInfo.TokenType) - 1; + uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + return (tokenType == uint8(TokenType.Silver)); + } +} diff --git a/eth/test/DFScoringRound2.test.ts b/eth/test/DFSilver.ts similarity index 87% rename from eth/test/DFScoringRound2.test.ts rename to eth/test/DFSilver.ts index 79fffe5c..8eea3e22 100644 --- a/eth/test/DFScoringRound2.test.ts +++ b/eth/test/DFSilver.ts @@ -4,7 +4,7 @@ import { conquerUnownedPlanet, feedSilverToCap, makeInitArgs } from './utils/Tes import { defaultWorldFixture, World } from './utils/TestWorld'; import { LVL1_ASTEROID_1, LVL3_SPACETIME_1, SPAWN_PLANET_1 } from './utils/WorldConstants'; -describe('DFScoringRound2', async function () { +describe.only('DFSilver', async function () { // Bump the time out so that the test doesn't timeout during // initial fixture creation this.timeout(1000 * 60); @@ -42,7 +42,7 @@ describe('DFScoringRound2', async function () { // FIXME(blaine): This should have been done client-side because this type of // division isn't supposed to be done in the contract. That's the whole point of // `CONTRACT_PRECISION` - expect((await world.contract.players(world.user1.address)).score).to.equal( + expect(await world.contract.getSilverBalance(world.user1.address)).to.equal( withdrawnAmount.div(1000) ); }); @@ -54,7 +54,7 @@ describe('DFScoringRound2', async function () { world.user1Core.withdrawSilver(LVL3_SPACETIME_1.id, withdrawnAmount) ).to.be.revertedWith('tried to withdraw more silver than exists on planet'); - expect((await world.contract.players(world.user1.address)).score).to.equal(0); + expect(await world.contract.getSilverBalance(world.user1.address)).to.equal(0); }); it("doesn't allow player to withdraw silver from non-trading post", async function () { @@ -64,7 +64,7 @@ describe('DFScoringRound2', async function () { world.user1Core.withdrawSilver(LVL1_ASTEROID_1.id, withdrawnAmount) ).to.be.revertedWith('can only withdraw silver from trading posts'); - expect((await world.contract.players(world.user1.address)).score).to.equal(0); + expect(await world.contract.getSilverBalance(world.user1.address)).to.equal(0); }); it("doesn't allow player to withdraw silver from planet that is not theirs", async function () { @@ -74,7 +74,7 @@ describe('DFScoringRound2', async function () { world.user2Core.withdrawSilver(LVL3_SPACETIME_1.id, withdrawnAmount) ).to.be.revertedWith('you must own this planet'); - expect((await world.contract.players(world.user1.address)).score).to.equal(0); - expect((await world.contract.players(world.user2.address)).score).to.equal(0); + expect(await world.contract.getSilverBalance(world.user1.address)).to.equal(0); + expect(await world.contract.getSilverBalance(world.user2.address)).to.equal(0); }); }); From c78429d93fc452fad851f7ed07a6391fb416134f Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sun, 2 Oct 2022 21:17:24 +0100 Subject: [PATCH 37/55] chore: explain silver precision --- eth/contracts/Tokens.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index 4c4d5b30..d6baf747 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -231,5 +231,6 @@ doesn't change. ## Silver -We can give silver a `tokenId` as well. This would be useful it silver was a fungible resource that -could be used to buy multiple things in game. Easy enough to make it non-transferrable as well. +Silver is a fungible token with TokenType = 3. Although silver on planets is multiplied by 1000 for +precision, Silver the token is exact. As such, any function that converts from silver on planets to +Silver must divide by 1000. From 3a44ae70e91fc025350a5332bd340b5a34eacee6 Mon Sep 17 00:00:00 2001 From: cha0sg0d <42984734+cha0sg0d@users.noreply.github.com> Date: Mon, 3 Oct 2022 06:14:46 -0400 Subject: [PATCH 38/55] Apply tweaks from code reivew Co-authored-by: Blaine Bublitz --- eth/contracts/DFTypes.sol | 9 --------- eth/contracts/facets/DFAdminFacet.sol | 1 - eth/contracts/facets/DFArtifactFacet.sol | 1 - eth/contracts/facets/DFGetterFacet.sol | 1 - eth/contracts/facets/DFMoveFacet.sol | 1 - eth/contracts/libraries/LibArtifact.sol | 7 ------- eth/contracts/libraries/LibGameUtils.sol | 1 - eth/contracts/libraries/LibPlanet.sol | 1 - eth/contracts/libraries/LibSpaceship.sol | 2 -- eth/hardhat.config.ts | 1 - eth/package.json | 1 - eth/tasks/deploy.ts | 1 - 12 files changed, 27 deletions(-) diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 893d328e..b0f29eba 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -226,15 +226,6 @@ enum ArtifactRarity { Mythic } -// for artifact getters -// struct ArtifactWithMetadata { -// Artifact artifact; -// Upgrade upgrade; -// Upgrade timeDelayedUpgrade; // for photoid canons specifically. -// address owner; -// uint256 locationId; // 0 if planet is not deposited into contract or is on a voyage -// uint256 voyageId; // 0 is planet is not deposited into contract or is on a planet -// } enum Biome { Unknown, diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 3ee57b8e..64bd9acf 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -14,7 +14,6 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Contract imports import {DFArtifactFacet} from "./DFArtifactFacet.sol"; -import "hardhat/console.sol"; // Type imports import {Artifact, SpaceType, Spaceship, SpaceshipType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet, TokenType} from "../DFTypes.sol"; diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index e92107f2..14df6686 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -21,7 +21,6 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtifactArgs, DFPFindArtifactArgs, Spaceship, SpaceshipType} from "../DFTypes.sol"; -import "hardhat/console.sol"; contract DFArtifactFacet is WithStorage, SolidStateERC1155 { event PlanetProspected(address player, uint256 loc); diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index aa15a491..145f8f39 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -16,7 +16,6 @@ import {WithStorage, SnarkConstants, GameConstants} from "../libraries/LibStorag // Type imports import {RevealedCoords, Artifact, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, Upgrade, Spaceship} from "../DFTypes.sol"; -import "hardhat/console.sol"; contract DFGetterFacet is WithStorage { // FIRST-LEVEL GETTERS - mirrors the solidity autogenerated toplevel getters, but for GameStorage diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index a10d438c..97bdf8ff 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -18,7 +18,6 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {ArrivalData, ArrivalType, Artifact, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Spaceship, SpaceshipType, Upgrade} from "../DFTypes.sol"; -import "hardhat/console.sol"; contract DFMoveFacet is WithStorage { modifier notPaused() { diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index adbc62b4..3da3142b 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -5,8 +5,6 @@ pragma solidity ^0.8.0; * Library for all things Artifacts */ -// Contract imports -import "hardhat/console.sol"; // Library imports import {LibUtils} from "./LibUtils.sol"; @@ -277,9 +275,6 @@ library LibArtifact { return (artifactType, bonus); } - // planets can have multiple artifacts on them. this function updates all the - // internal contract book-keeping to reflect that the given artifact was - // put on. note that this function does not transfer the artifact. function putArtifactOnPlanet(uint256 locationId, uint256 artifactId) internal { gs().planetArtifacts[locationId].push(artifactId); } @@ -323,8 +318,6 @@ library LibArtifact { } } - // if the given planet has an activated artifact on it, then return the artifact - // otherwise, return a 'null artifact' function hasActiveArtifact(uint256 locationId) internal view returns (bool) { uint256 artifactId = gs().planetActiveArtifact[locationId]; return artifactId != 0; diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 9755c863..4bc35df0 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -13,7 +13,6 @@ import {LibUtils} from "./LibUtils.sol"; import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactType, TokenType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; -import "hardhat/console.sol"; library LibGameUtils { function gs() internal pure returns (GameStorage storage) { diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 818553fe..9d317390 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -17,7 +17,6 @@ import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStora // Type imports import {ArtifactType, Artifact, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Spaceship, SpaceshipType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; -import "hardhat/console.sol"; library LibPlanet { function gs() internal pure returns (GameStorage storage) { diff --git a/eth/contracts/libraries/LibSpaceship.sol b/eth/contracts/libraries/LibSpaceship.sol index 3d143a32..a350f98f 100644 --- a/eth/contracts/libraries/LibSpaceship.sol +++ b/eth/contracts/libraries/LibSpaceship.sol @@ -5,8 +5,6 @@ pragma solidity ^0.8.0; * Library for all things Spaceships */ -// Contract imports -import "hardhat/console.sol"; // Library imports import {LibUtils} from "./LibUtils.sol"; diff --git a/eth/hardhat.config.ts b/eth/hardhat.config.ts index 20a56f9d..fc7dee6e 100644 --- a/eth/hardhat.config.ts +++ b/eth/hardhat.config.ts @@ -219,7 +219,6 @@ const config: HardhatUserConfig = { // This plugin will combine all ABIs from any Smart Contract with `Facet` in the name or path and output it as `DarkForest.json` name: 'DarkForest', include: ['Facet', 'DFDiamond'], - exclude: ['Old'], // We explicitly set `strict` to `true` because we want to validate our facets don't accidentally provide overlapping functions strict: true, // We use our diamond utils to filter some functions we ignore from the combined ABI diff --git a/eth/package.json b/eth/package.json index 45969814..3f2c2f4f 100644 --- a/eth/package.json +++ b/eth/package.json @@ -20,7 +20,6 @@ "@projectsophon/workspace": "^2.0.0", "@solidstate/contracts": "^0.0.41", "@solidstate/hardhat-4byte-uploader": "^1.0.2", - "@solidstate/spec": "^0.0.41", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.3", "@types/chai": "^4.2.14", diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index adedbfb2..8066ff3f 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -409,7 +409,6 @@ export async function deployLibraries({}, hre: HardhatRuntimeEnvironment) { libraries: { LibGameUtils: LibGameUtils.address, LibLazyUpdate: LibLazyUpdate.address, - // LibArtifactUtils: LibArtifactUtils.address, }, }); const LibPlanet = await LibPlanetFactory.deploy(); From 0485338554d9f9bf586ce321e3552aa67528bbfa Mon Sep 17 00:00:00 2001 From: cha0sg0d <42984734+cha0sg0d@users.noreply.github.com> Date: Mon, 3 Oct 2022 06:20:52 -0400 Subject: [PATCH 39/55] More tweaks Co-authored-by: Blaine Bublitz --- eth/contracts/libraries/LibArtifact.sol | 1 - eth/contracts/libraries/LibArtifactUtils.sol | 2 -- 2 files changed, 3 deletions(-) diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index 3da3142b..417e98a5 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -262,7 +262,6 @@ library LibArtifact { } else { artifactType = ArtifactType.Wormhole; } - artifactType = ArtifactType.PhotoidCannon; } uint256 bonus = 0; diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index ec5581aa..728e8d44 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -16,7 +16,6 @@ import {LibStorage, GameStorage, GameConstants} from "./LibStorage.sol"; // Type imports import {Biome, Planet, PlanetType, ArtifactType, ArtifactRarity, TokenType, DFPFindArtifactArgs, DFTCreateArtifactArgs, Artifact, ArtifactInfo, Spaceship, SpaceshipType} from "../DFTypes.sol"; -import "hardhat/console.sol"; library LibArtifactUtils { function gs() internal pure returns (GameStorage storage) { @@ -247,7 +246,6 @@ library LibArtifactUtils { } if (shouldDeactivateAndBurn) { - // artifact.lastDeactivated = block.timestamp; // immediately deactivate gs().planetActiveArtifact[locationId] = 0; // immediately remove activate artifact emit ArtifactDeactivated(msg.sender, locationId, artifactId); From f119e915bc3f8c007206523d5d95ab65723dd8c8 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 11:26:43 +0100 Subject: [PATCH 40/55] fix: return when found in getter --- eth/contracts/facets/DFGetterFacet.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 145f8f39..4b005e61 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -372,11 +372,11 @@ contract DFGetterFacet is WithStorage { bool hasToken = false; uint256[] memory artifactIds = gs().planetArtifacts[locationId]; for (uint256 i = 0; i < artifactIds.length; i++) { - if (artifactIds[i] == tokenId) hasToken = true; + if (artifactIds[i] == tokenId) return true; } uint256[] memory shipIds = gs().planetSpaceships[locationId]; for (uint256 i = 0; i < shipIds.length; i++) { - if (shipIds[i] == tokenId) hasToken = true; + if (shipIds[i] == tokenId) return true; } return hasToken; } From 12d000b3bc8bc573f5ed10a2137cc848243c8336 Mon Sep 17 00:00:00 2001 From: cha0sg0d <42984734+cha0sg0d@users.noreply.github.com> Date: Mon, 3 Oct 2022 06:29:41 -0400 Subject: [PATCH 41/55] README tweaks Co-authored-by: Blaine Bublitz --- eth/contracts/Tokens.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index 4c4d5b30..f00caf30 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -8,9 +8,9 @@ The fundamental data structure in ERC1155 is `mapping(uint256 => mapping(address `balances[tokenId][myAddress]` = number of tokens I have of a given collection. -The `uint 256 tokenId`, which identifies a _set_ of tokens, is represented in the following way: +The `uint256` `tokenId`, which identifies a _set_ of tokens, is represented in the following way: -Each `uint256 tokenId` is broken down into 32 chunks of 8 bits each (32\*8 = 256). +Each `uint256` `tokenId` is broken down into 32 chunks of 8 bits each (32\*8 = 256). > | chunk1 | chunk 2 | ... chunk32 |. @@ -55,7 +55,7 @@ The `Lib.sol`. file **must** have the following methods: where `` can be a struct (like Artifacts or Spaceships) or just a uint256 (like Silver). Additionally methods can be added to each library, but they must be `internal` functions that can be -inherited by other facets or libraries. +inlined into other facets or libraries. ## Artifacts @@ -217,7 +217,7 @@ For the wormhole, we do the same: ## Simulteanous Activate and Deactivate -Some Bloom Filters (Artifacts) and Crescents (Spaceships) are burned on use. +Some Artifacts (Bloom Filters) and Spaceships (Crescents) are burned on use. All we do is make sure that we call the Activate and Deactivate functions in the same transaction. From a2c9bce8f758a6e78aa63d694e87a52ef916e04f Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 11:31:42 +0100 Subject: [PATCH 42/55] feat: explain chunk properties in README --- eth/contracts/Tokens.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index f00caf30..70ae09bd 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -18,7 +18,11 @@ Chunks are used from left to right, so a token that has a value of `0xff` in chu Another way to visualize the `tokenId` is by highlighting each chunk: `0x**ff**_00_**00**_00_**00**_00_...` -Each chunk allows for 2^8 (256) unique pieces of information. If you need more than 256 properties of a token, you can use an additional chunk +Each chunk allows for 2^8 (256) unique pieces of information. If you need more than 256 properties +of a token, you can use an additional chunk. + +For example, if you wanted to add a new property to an Artifact called `Weather`, you use the next +chunk(s) to encode that value. You would have 256 options for what `Weather` an Artifact could have. This architecture allows us to encode information about a Dark Forest token in the `tokenId` itself, and, more importantly, it allows to create a copy of a token just by using the same `tokenId`. From f2a8b738c1e521674a188ab6f0b91f2bfc3fcdf2 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 13:42:15 +0100 Subject: [PATCH 43/55] fix: ordering of args for artifact events --- eth/contracts/facets/DFArtifactFacet.sol | 5 ++--- eth/contracts/libraries/LibArtifactUtils.sol | 8 ++++---- eth/contracts/libraries/LibPlanet.sol | 2 -- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 14df6686..4ae35d12 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -21,7 +21,6 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtifactArgs, DFPFindArtifactArgs, Spaceship, SpaceshipType} from "../DFTypes.sol"; - contract DFArtifactFacet is WithStorage, SolidStateERC1155 { event PlanetProspected(address player, uint256 loc); event ArtifactFound(address player, uint256 artifactId, uint256 loc); @@ -170,7 +169,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { LibArtifactUtils.depositArtifact(locationId, artifactId, address(this)); - emit ArtifactDeposited(msg.sender, locationId, artifactId); + emit ArtifactDeposited(msg.sender, artifactId, locationId); } // withdraws the given artifact from the given planet. you must own the planet, @@ -180,7 +179,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { LibArtifactUtils.withdrawArtifact(locationId, artifactId); - emit ArtifactWithdrawn(msg.sender, locationId, artifactId); + emit ArtifactWithdrawn(msg.sender, artifactId, locationId); } // activates the given artifact on the given planet. the artifact must have diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 728e8d44..a513a6f4 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -182,12 +182,12 @@ library LibArtifactUtils { } planet.planetType = PlanetType.SILVER_MINE; - emit ArtifactActivated(msg.sender, locationId, shipId); + emit ArtifactActivated(msg.sender, shipId, locationId); // TODO: Why not actually burn? // burn it after use. will be owned by contract but not on a planet anyone can control LibSpaceship.takeSpaceshipOffPlanet(locationId, shipId); - emit ArtifactDeactivated(msg.sender, locationId, shipId); + emit ArtifactDeactivated(msg.sender, shipId, locationId); } } @@ -217,7 +217,7 @@ library LibArtifactUtils { gs().planetArtifactActivationTime[locationId] = block.timestamp; gs().planetActiveArtifact[locationId] = artifactId; - emit ArtifactActivated(msg.sender, locationId, artifactId); + emit ArtifactActivated(msg.sender, artifactId, locationId); if (artifact.artifactType == ArtifactType.Wormhole) { require(wormholeTo != 0, "you must provide a wormholeTo to activate a wormhole"); @@ -248,7 +248,7 @@ library LibArtifactUtils { if (shouldDeactivateAndBurn) { gs().planetActiveArtifact[locationId] = 0; // immediately remove activate artifact - emit ArtifactDeactivated(msg.sender, locationId, artifactId); + emit ArtifactDeactivated(msg.sender, artifactId, locationId); // burn it after use. will be owned by contract but not on a planet anyone can control LibArtifact.takeArtifactOffPlanet(locationId, artifactId); } diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 9d317390..e3bed06d 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -32,8 +32,6 @@ library LibPlanet { } // also need to copy some of DFCore's event signatures - event ArtifactActivated(address player, uint256 artifactId, uint256 loc); - event ArtifactDeactivated(address player, uint256 artifactId, uint256 loc); event PlanetUpgraded(address player, uint256 loc, uint256 branch, uint256 toBranchLevel); function revealLocation( From 6471c89950df7c09ee764f61232372d7c03a4cea Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 13:58:56 +0100 Subject: [PATCH 44/55] fix: split up getActiveArtifact into two checks --- eth/contracts/facets/DFMoveFacet.sol | 70 +++++++++++++++---------- eth/contracts/libraries/LibArtifact.sol | 10 ++-- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 97bdf8ff..af8ff2f6 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -17,7 +17,7 @@ import {LibSpaceship} from "../libraries/LibSpaceship.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {ArrivalData, ArrivalType, Artifact, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Spaceship, SpaceshipType, Upgrade} from "../DFTypes.sol"; +import {ArrivalData, ArrivalType, Artifact, ArtifactRarity, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Spaceship, SpaceshipType, Upgrade} from "../DFTypes.sol"; contract DFMoveFacet is WithStorage { modifier notPaused() { @@ -289,28 +289,40 @@ contract DFMoveFacet is WithStorage { returns (bool wormholePresent, uint256 effectiveDistModifier) { wormholePresent = false; - Artifact memory relevantWormhole; - Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); - Artifact memory activeArtifactTo = LibArtifact.getActiveArtifact(args.newLoc); - // TODO: take the greater rarity of these, or disallow wormholes between planets that - // already have a wormhole between them - if ( - activeArtifactFrom.artifactType == ArtifactType.Wormhole && - gs().planetWormholes[args.oldLoc] == args.newLoc - ) { - relevantWormhole = activeArtifactFrom; - wormholePresent = true; - } else if ( - activeArtifactTo.artifactType == ArtifactType.Wormhole && - gs().planetWormholes[args.newLoc] == args.oldLoc - ) { - relevantWormhole = activeArtifactTo; - wormholePresent = true; + ArtifactRarity wormholeRarity = ArtifactRarity.Unknown; + + // Check from Loc + if (LibArtifact.hasActiveArtifact(args.oldLoc)) { + Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); + // If active artifact is a Wormhole and destination is newLoc + if ( + activeArtifactFrom.artifactType == ArtifactType.Wormhole && + gs().planetWormholes[args.oldLoc] == args.newLoc + ) { + wormholeRarity = activeArtifactFrom.rarity; + wormholePresent = true; + } + } + // Check to loc + if (LibArtifact.hasActiveArtifact(args.newLoc)) { + Artifact memory activeArtifactTo = LibArtifact.getActiveArtifact(args.newLoc); + // If active artifact is a Wormhole and destination is fromLoc + if ( + activeArtifactTo.artifactType == ArtifactType.Wormhole && + gs().planetWormholes[args.newLoc] == args.oldLoc + ) { + // Ensures higher rarity wormhole will be used. + // TODO: Make sure client knows this. + if (activeArtifactTo.rarity > wormholeRarity) { + wormholeRarity = activeArtifactTo.rarity; + } + wormholePresent = true; + } } if (wormholePresent) { uint256[6] memory speedBoosts = [uint256(1), 2, 4, 8, 16, 32]; - effectiveDistModifier = speedBoosts[uint256(relevantWormhole.rarity)]; + effectiveDistModifier = speedBoosts[uint256(wormholeRarity)]; } } @@ -323,15 +335,17 @@ contract DFMoveFacet is WithStorage { private returns (bool photoidPresent, Upgrade memory temporaryUpgrade) { - Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); - if ( - activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && - block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= - gameConstants().PHOTOID_ACTIVATION_DELAY - ) { - photoidPresent = true; - LibArtifactUtils.deactivateArtifact(args.oldLoc); - temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); + if (LibArtifact.hasActiveArtifact(args.oldLoc)) { + Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); + if ( + activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && + block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= + gameConstants().PHOTOID_ACTIVATION_DELAY + ) { + photoidPresent = true; + LibArtifactUtils.deactivateArtifact(args.oldLoc); + temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); + } } } diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index 417e98a5..2a195085 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.0; * Library for all things Artifacts */ - // Library imports import {LibUtils} from "./LibUtils.sol"; @@ -309,12 +308,9 @@ library LibArtifact { // if the given planet has an activated artifact on it, then return the artifact // otherwise, return a 'null artifact' function getActiveArtifact(uint256 locationId) internal view returns (Artifact memory) { - if (hasActiveArtifact(locationId)) { - uint256 artifactId = gs().planetActiveArtifact[locationId]; - return LibArtifact.decode(artifactId); - } else { - return LibArtifact._nullArtifactProperties(); - } + require(hasActiveArtifact(locationId), "planet does not have an active artifact"); + uint256 artifactId = gs().planetActiveArtifact[locationId]; + return LibArtifact.decode(artifactId); } function hasActiveArtifact(uint256 locationId) internal view returns (bool) { From a3d9212b414a2bd40e2a334f9dae94c8ebb118f8 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 14:06:38 +0100 Subject: [PATCH 45/55] fix: remove @solidstate/spec --- package-lock.json | 68 ----------------------------------------------- 1 file changed, 68 deletions(-) diff --git a/package-lock.json b/package-lock.json index 38345b42..3a964e28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -225,7 +225,6 @@ "@projectsophon/workspace": "^2.0.0", "@solidstate/contracts": "^0.0.41", "@solidstate/hardhat-4byte-uploader": "^1.0.2", - "@solidstate/spec": "^0.0.41", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.3", "@types/chai": "^4.2.14", @@ -7114,24 +7113,6 @@ } } }, - "node_modules/@solidstate/library": { - "version": "0.0.41", - "resolved": "https://registry.npmjs.org/@solidstate/library/-/library-0.0.41.tgz", - "integrity": "sha512-JWn6wkN4QI/054XEjnzZe/ooNg0q2yq9kHe3EZEQyONSWzLYAFINr/c23dhUu4CnuR1gh2HeOpyOIufpIYSRvw==", - "dev": true, - "dependencies": { - "eth-permit": "^0.1.10" - } - }, - "node_modules/@solidstate/spec": { - "version": "0.0.41", - "resolved": "https://registry.npmjs.org/@solidstate/spec/-/spec-0.0.41.tgz", - "integrity": "sha512-yBKDVFr/tymCncdkJ0lpW9mvH3JXRD9VK1jjWg8nVio7CVXL3B5UWL5s/3Z1WQUGuyTJ4QpxT2jgbEohIgz0ZQ==", - "dev": true, - "dependencies": { - "@solidstate/library": "^0.0.41" - } - }, "node_modules/@spectrum-web-components/action-button": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@spectrum-web-components/action-button/-/action-button-0.7.3.tgz", @@ -15458,15 +15439,6 @@ "resolved": "eth", "link": true }, - "node_modules/eth-permit": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/eth-permit/-/eth-permit-0.1.10.tgz", - "integrity": "sha512-cMAjWvGEaCQoWSlKWBqkBMQvhCtXzVlkTCnYBxGRsCxznbBZSiYuOsHpTAb4EerRwQJkRgwd3vPvcZh3OA7Siw==", - "dev": true, - "dependencies": { - "utf8": "^3.0.0" - } - }, "node_modules/ethereum-cryptography": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", @@ -31128,12 +31100,6 @@ "node": ">=6.14.2" } }, - "node_modules/utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "dev": true - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -37521,24 +37487,6 @@ } } }, - "@solidstate/library": { - "version": "0.0.41", - "resolved": "https://registry.npmjs.org/@solidstate/library/-/library-0.0.41.tgz", - "integrity": "sha512-JWn6wkN4QI/054XEjnzZe/ooNg0q2yq9kHe3EZEQyONSWzLYAFINr/c23dhUu4CnuR1gh2HeOpyOIufpIYSRvw==", - "dev": true, - "requires": { - "eth-permit": "^0.1.10" - } - }, - "@solidstate/spec": { - "version": "0.0.41", - "resolved": "https://registry.npmjs.org/@solidstate/spec/-/spec-0.0.41.tgz", - "integrity": "sha512-yBKDVFr/tymCncdkJ0lpW9mvH3JXRD9VK1jjWg8nVio7CVXL3B5UWL5s/3Z1WQUGuyTJ4QpxT2jgbEohIgz0ZQ==", - "dev": true, - "requires": { - "@solidstate/library": "^0.0.41" - } - }, "@spectrum-web-components/action-button": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@spectrum-web-components/action-button/-/action-button-0.7.3.tgz", @@ -43765,7 +43713,6 @@ "@projectsophon/workspace": "^2.0.0", "@solidstate/contracts": "^0.0.41", "@solidstate/hardhat-4byte-uploader": "^1.0.2", - "@solidstate/spec": "^0.0.41", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.3", "@types/chai": "^4.2.14", @@ -43896,15 +43843,6 @@ } } }, - "eth-permit": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/eth-permit/-/eth-permit-0.1.10.tgz", - "integrity": "sha512-cMAjWvGEaCQoWSlKWBqkBMQvhCtXzVlkTCnYBxGRsCxznbBZSiYuOsHpTAb4EerRwQJkRgwd3vPvcZh3OA7Siw==", - "dev": true, - "requires": { - "utf8": "^3.0.0" - } - }, "ethereum-cryptography": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", @@ -55413,12 +55351,6 @@ "node-gyp-build": "^4.3.0" } }, - "utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "dev": true - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", From 2abbff918bb015f2ccac4800989d09e1582e62fd Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 15:19:49 +0100 Subject: [PATCH 46/55] feat: create for Spaceship and Artifact --- eth/contracts/facets/DFAdminFacet.sol | 11 +-- eth/contracts/facets/DFArtifactFacet.sol | 22 +++--- eth/contracts/facets/DFGetterFacet.sol | 4 ++ eth/contracts/libraries/LibArtifact.sol | 74 ++++++++++++-------- eth/contracts/libraries/LibArtifactUtils.sol | 25 ++----- eth/contracts/libraries/LibSpaceship.sol | 21 ++++-- eth/test/DFArtifacts.test.ts | 72 ++++++++----------- eth/test/utils/TestUtils.ts | 20 ++---- 8 files changed, 118 insertions(+), 131 deletions(-) diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 64bd9acf..7a04df0c 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -169,15 +169,8 @@ contract DFAdminFacet is WithStorage { function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { // Note: calling this in tests should supply Diamond address as args.owner - uint256 tokenId = LibArtifact.encode( - Artifact({ - id: 0, - tokenType: TokenType.Artifact, - rarity: args.rarity, - artifactType: args.artifactType, - planetBiome: args.biome - }) - ); + uint256 tokenId = LibArtifact.create(args.rarity, args.artifactType, args.biome); + Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact( tokenId, args.owner diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 4ae35d12..2e792b56 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -88,26 +88,26 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { // Account, Id, Amount, Data _mint(owner, tokenId, 1, ""); - return getSpaceship(tokenId); + return getSpaceshipFromId(tokenId); } - function getSpaceship(uint256 shipId) public pure returns (Spaceship memory) { + function getSpaceshipFromId(uint256 shipId) public pure returns (Spaceship memory) { return LibSpaceship.decode(shipId); } - function encodeSpaceship(Spaceship memory spaceship) public pure returns (uint256) { - return LibSpaceship.encode(spaceship); + function createSpaceshipId(SpaceshipType spaceshipType) public pure returns (uint256) { + return LibSpaceship.create(spaceshipType); } - function decodeSpaceship(uint256 shipId) public pure returns (Spaceship memory) { - return LibSpaceship.decode(shipId); - } - - function encodeArtifact(Artifact memory artifact) public pure returns (uint256) { - return LibArtifact.encode(artifact); + function createArtifactId( + ArtifactRarity rarity, + ArtifactType artifactType, + Biome biome + ) public pure returns (uint256) { + return LibArtifact.create(rarity, artifactType, biome); } - function decodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { + function getArtifactFromId(uint256 artifactId) public pure returns (Artifact memory) { return LibArtifact.decode(artifactId); } diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 4b005e61..9ac4419a 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -381,6 +381,10 @@ contract DFGetterFacet is WithStorage { return hasToken; } + function hasActiveArtifact(uint256 locationId) public view returns (bool) { + return LibArtifact.hasActiveArtifact(locationId); + } + function getActiveArtifactOnPlanet(uint256 locationId) public view diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index 2a195085..60b392c8 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -21,23 +21,30 @@ library LibArtifact { /** * @notice Create the token ID for a Artifact with the following properties: - * @param artifact Artifact + * @param _rarity Artifact + * @param _artifactType Artifact + * @param _biome Artifact */ - function encode(Artifact memory artifact) internal pure returns (uint256) { + function create( + ArtifactRarity _rarity, + ArtifactType _artifactType, + Biome _biome + ) internal pure returns (uint256) { // x << y is equivalent to the mathematical expression x * 2**y + require(isValidArtifactRarity(_rarity), "artifact rarity is not valid"); + require(isValidArtifactType(_artifactType), "artifact type is not valid"); + require(isValidBiome(_biome), "artifact biome is not valid"); + uint256 tokenType = LibUtils.shiftLeft( - uint8(artifact.tokenType), - uint8(ArtifactInfo.TokenType) - ); - uint256 rarity = LibUtils.shiftLeft( - uint8(artifact.rarity), - uint8(ArtifactInfo.ArtifactRarity) + uint8(TokenType.Artifact), // value + uint8(ArtifactInfo.TokenType) // chunk position in tokenId ); + uint256 rarity = LibUtils.shiftLeft(uint8(_rarity), uint8(ArtifactInfo.ArtifactRarity)); uint256 artifactType = LibUtils.shiftLeft( - uint8(artifact.artifactType), + uint8(_artifactType), uint8(ArtifactInfo.ArtifactType) ); - uint256 biome = LibUtils.shiftLeft(uint8(artifact.planetBiome), uint8(ArtifactInfo.Biome)); + uint256 biome = LibUtils.shiftLeft(uint8(_biome), uint8(ArtifactInfo.Biome)); return tokenType + rarity + artifactType + biome; } @@ -48,20 +55,40 @@ library LibArtifact { uint8 typeIdx = uint8(ArtifactInfo.ArtifactType) - 1; uint8 biomeIdx = uint8(ArtifactInfo.Biome) - 1; - uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); - uint8 rarity = uint8(LibUtils.calculateByteUInt(_b, rarityIdx, rarityIdx)); - uint8 artifactType = uint8(LibUtils.calculateByteUInt(_b, typeIdx, typeIdx)); - uint8 biome = uint8(LibUtils.calculateByteUInt(_b, biomeIdx, biomeIdx)); + TokenType tokenType = TokenType(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + ArtifactRarity rarity = ArtifactRarity( + LibUtils.calculateByteUInt(_b, rarityIdx, rarityIdx) + ); + ArtifactType artifactType = ArtifactType(LibUtils.calculateByteUInt(_b, typeIdx, typeIdx)); + Biome biome = Biome(LibUtils.calculateByteUInt(_b, biomeIdx, biomeIdx)); + + require(isArtifact(artifactId), "token type is not artifact"); + require(isValidArtifactRarity(rarity), "artifact rarity is not valid"); + require(isValidArtifactType(artifactType), "artifact type is not valid"); + require(isValidBiome(biome), "artifact biome is not valid"); + return Artifact({ id: artifactId, - tokenType: TokenType(tokenType), - rarity: ArtifactRarity(rarity), - artifactType: ArtifactType(artifactType), - planetBiome: Biome(biome) + tokenType: tokenType, + rarity: rarity, + artifactType: artifactType, + planetBiome: biome }); } + function isValidArtifactRarity(ArtifactRarity rarity) internal pure returns (bool) { + return (rarity >= ArtifactRarity.Common && rarity <= ArtifactRarity.Mythic); + } + + function isValidArtifactType(ArtifactType artifactType) internal pure returns (bool) { + return (artifactType >= ArtifactType.Monolith && artifactType <= ArtifactType.BlackDomain); + } + + function isValidBiome(Biome biome) internal pure returns (bool) { + return (biome >= Biome.Ocean && biome <= Biome.Corrupted); + } + function isArtifact(uint256 tokenId) internal pure returns (bool) { bytes memory _b = abi.encodePacked(tokenId); uint8 tokenIdx = uint8(ArtifactInfo.TokenType) - 1; @@ -69,17 +96,6 @@ library LibArtifact { return (TokenType(tokenType) == TokenType.Artifact); } - function _nullArtifactProperties() internal pure returns (Artifact memory) { - return - Artifact( - 0, - TokenType.Unknown, - ArtifactRarity.Unknown, - ArtifactType.Unknown, - Biome.Unknown - ); - } - function getUpgradeForArtifact(Artifact memory artifact) internal pure diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index a513a6f4..d0f8cbfd 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -65,16 +65,10 @@ library LibArtifactUtils { SpaceshipType shipType ) public returns (uint256) { require(shipType != SpaceshipType.Unknown, "incorrect ship type"); - // require(gs().miscNonce < MAX UINT 128) but won't happen. - uint128 id = uint128(gs().miscNonce++); - uint256 tokenId = LibSpaceship.encode( - Spaceship({id: 0, tokenType: TokenType.Spaceship, spaceshipType: shipType}) - ); - Spaceship memory spaceship = DFArtifactFacet(address(this)).createSpaceship( - tokenId + id, // Make each ship unique - owner - ); + uint256 tokenId = LibSpaceship.create(shipType) + uint128(gs().miscNonce++); + + Spaceship memory spaceship = DFArtifactFacet(address(this)).createSpaceship(tokenId, owner); LibSpaceship.putSpaceshipOnPlanet(planetId, spaceship.id); return spaceship.id; @@ -103,15 +97,7 @@ library LibArtifactUtils { ArtifactRarity rarity = LibArtifact.artifactRarityFromPlanetLevel( levelBonus + planet.planetLevel ); - uint256 tokenId = LibArtifact.encode( - Artifact({ - id: 0, - tokenType: TokenType.Artifact, - rarity: rarity, - artifactType: artifactType, - planetBiome: biome - }) - ); + uint256 tokenId = LibArtifact.create(rarity, artifactType, biome); Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( tokenId, @@ -134,7 +120,6 @@ library LibArtifactUtils { uint256 wormholeTo ) public { Planet storage planet = gs().planets[locationId]; - Artifact memory artifact = LibArtifact.decode(artifactId); if (LibSpaceship.isShip(artifactId)) { require( @@ -143,6 +128,8 @@ library LibArtifactUtils { ); activateSpaceshipArtifact(locationId, artifactId, planet); } else if (LibArtifact.isArtifact(artifactId)) { + Artifact memory artifact = LibArtifact.decode(artifactId); + require( LibArtifact.isArtifactOnPlanet(locationId, artifactId), "can't activate an artifact on a planet it's not on" diff --git a/eth/contracts/libraries/LibSpaceship.sol b/eth/contracts/libraries/LibSpaceship.sol index a350f98f..3cf955c3 100644 --- a/eth/contracts/libraries/LibSpaceship.sol +++ b/eth/contracts/libraries/LibSpaceship.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.0; * Library for all things Spaceships */ - // Library imports import {LibUtils} from "./LibUtils.sol"; @@ -22,16 +21,17 @@ library LibSpaceship { /** * @notice Create the token ID for a Spaceship with the following properties: - * @param spaceship Spaceship + * @param spaceshipType SpaceshipType. */ - function encode(Spaceship memory spaceship) internal pure returns (uint256) { - // x << y is equivalent to the mathematical expression x * 2**y + function create(SpaceshipType spaceshipType) internal pure returns (uint256) { + require(isValidShipType(spaceshipType), "spaceship type is not valid"); + uint256 tokenType = LibUtils.shiftLeft( - uint8(spaceship.tokenType), + uint8(TokenType.Spaceship), uint8(SpaceshipInfo.TokenType) ); uint256 shipType = LibUtils.shiftLeft( - uint8(spaceship.spaceshipType), + uint8(spaceshipType), uint8(SpaceshipInfo.SpaceshipType) ); return tokenType + shipType; @@ -46,6 +46,9 @@ library LibSpaceship { uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); uint8 shipType = uint8(LibUtils.calculateByteUInt(_b, shipInfoIdx, shipInfoIdx)); + require(isShip(spaceshipId), "token type is not spaceship"); + require(isValidShipType(SpaceshipType(shipType)), "spaceship type is not valid"); + return Spaceship({ id: spaceshipId, @@ -58,7 +61,11 @@ library LibSpaceship { bytes memory _b = abi.encodePacked(tokenId); uint8 tokenIdx = uint8(SpaceshipInfo.TokenType) - 1; uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); - return (TokenType(tokenType) == TokenType.Spaceship); + return (tokenType == uint8(TokenType.Spaceship)); + } + + function isValidShipType(SpaceshipType shipType) internal pure returns (bool) { + return (shipType >= SpaceshipType.ShipMothership && shipType <= SpaceshipType.ShipTitan); } function isSpaceshipOnPlanet(uint256 locationId, uint256 shipId) internal view returns (bool) { diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index 7b618752..bd353312 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -86,14 +86,8 @@ describe('DarkForestArtifacts', function () { const rarity = ArtifactRarity.Legendary; const artifactType = ArtifactType.Colossus; const planetBiome = Biome.DESERT; - const res = await world.contract.encodeArtifact({ - id: 0, - tokenType, - rarity, - artifactType, - planetBiome, - }); - const a = await world.contract.decodeArtifact(res); + const res = await world.contract.createArtifactId(rarity, artifactType, planetBiome); + const a = await world.contract.getArtifactFromId(res); expect(tokenType).to.equal(Number(a.tokenType)); expect(rarity).to.equal(Number(a.rarity)); expect(artifactType).to.equal(Number(a.artifactType)); @@ -103,12 +97,8 @@ describe('DarkForestArtifacts', function () { // Must be valid options const tokenType = TokenType.Spaceship; const spaceshipType = 2; - const res = await world.contract.encodeSpaceship({ - id: 0, - tokenType, - spaceshipType, - }); - const a = await world.contract.decodeSpaceship(res); + const res = await world.contract.createSpaceshipId(spaceshipType); + const a = await world.contract.getSpaceshipFromId(res); expect(tokenType).to.equal(Number(a.tokenType)); expect(spaceshipType).to.equal(Number(a.spaceshipType)); }); @@ -126,7 +116,7 @@ describe('DarkForestArtifacts', function () { const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); - prettyPrintToken(await world.user1Core.decodeArtifact(artifactId)); + prettyPrintToken(await world.user1Core.getArtifactFromId(artifactId)); // artifact and gear should be on planet. Gear is 0 and Artifact is 1. const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); @@ -575,8 +565,8 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.Monolith, - TokenType.Artifact, - { rarity: ArtifactRarity.Legendary, biome: Biome.OCEAN } + ArtifactRarity.Legendary, + Biome.OCEAN ); // deposit fails on low level trading post, succeeds on high level trading post @@ -625,10 +615,10 @@ describe('DarkForestArtifacts', function () { world.user1.address, from, ArtifactType.Wormhole, - TokenType.Artifact, - { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } + artifactRarities[i] as ArtifactRarity, + Biome.OCEAN ); - prettyPrintToken(await world.contract.decodeArtifact(artifactId)); + prettyPrintToken(await world.contract.getArtifactFromId(artifactId)); activateAndConfirm(world.user1Core, from.id, artifactId, to.id); @@ -695,8 +685,8 @@ describe('DarkForestArtifacts', function () { world.user1.address, from, ArtifactType.Wormhole, - TokenType.Artifact, - { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ArtifactRarity.Common, + Biome.OCEAN ); // Move gear bc too many artifacts on SPAWN_PLANET_1, so can't receive wormhole. @@ -786,10 +776,10 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BloomFilter, - TokenType.Artifact, - { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ArtifactRarity.Common, + Biome.OCEAN ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifactFromId(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); @@ -833,11 +823,11 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BloomFilter, - TokenType.Artifact, - { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ArtifactRarity.Common, + Biome.OCEAN ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifactFromId(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move( @@ -868,8 +858,8 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BlackDomain, - TokenType.Artifact, - { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ArtifactRarity.Common, + Biome.OCEAN ); await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); @@ -918,8 +908,8 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BlackDomain, - TokenType.Artifact, - { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ArtifactRarity.Common, + Biome.OCEAN ); await increaseBlockchainTime(); // so that trading post can fill up to max energy @@ -942,10 +932,10 @@ describe('DarkForestArtifacts', function () { world.user1.address, LVL3_SPACETIME_1, ArtifactType.PlanetaryShield, - TokenType.Artifact, - { rarity: ArtifactRarity.Rare as ArtifactRarity, biome: Biome.OCEAN } + ArtifactRarity.Rare as ArtifactRarity, + Biome.OCEAN ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifactFromId(newTokenId)); const planetBeforeActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); await activateAndConfirm(world.user1Core, LVL3_SPACETIME_1.id, newTokenId); @@ -959,11 +949,7 @@ describe('DarkForestArtifacts', function () { // Burned on deactivate await world.user1Core.deactivateArtifact(LVL3_SPACETIME_1.id); - expect((await getArtifactsOnPlanet(world, LVL3_SPACETIME_1.id)).length).to.equal(0); - expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal(0); - expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( - 0 - ); + testDeactivate(world, LVL3_SPACETIME_3.id); }); }); @@ -987,10 +973,10 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.PhotoidCannon, - TokenType.Artifact, - { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } + artifactRarities[i] as ArtifactRarity, + Biome.OCEAN ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifactFromId(newTokenId)); await world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index a288174b..d37932a6 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -16,7 +16,6 @@ import { Biome, BiomeNames, SpaceshipType, - TokenType, TokenTypeNames, } from '@dfdao/types'; import { bigIntFromKey } from '@dfdao/whitelist'; @@ -310,8 +309,9 @@ export async function user1MintArtifactPlanet(user1Core: DarkForest) { await increaseBlockchainTime(); const findArtifactTx = await user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); const findArtifactReceipt = await findArtifactTx.wait(); - // 0th event is erc721 transfer (i think); 1st event is UpdateArtifact, 2nd argument of this event is artifactId - const artifactId = findArtifactReceipt.events?.[1].args?.[1]; + // 0th event is erc721 transfer (i think); 1st event is UpdateArtifact, 2nd argument of this event + // is artifactId + const artifactId = findArtifactReceipt.events?.[1].args?.artifactId; return artifactId as BigNumber; } @@ -334,19 +334,13 @@ export async function createArtifact( owner: string, planet: TestLocation, artifactType: ArtifactType, - tokenType = TokenType.Artifact, - { rarity, biome }: { rarity?: ArtifactRarity; biome?: Biome } = {} + rarity?: ArtifactRarity, + biome?: Biome ) { rarity ||= ArtifactRarity.Common; biome ||= Biome.FOREST; - const tokenId = await contract.encodeArtifact({ - id: 0, - tokenType, - rarity, - artifactType, - planetBiome: biome, - }); + const tokenId = await contract.createArtifactId(rarity, artifactType, biome); await contract.adminGiveArtifact({ tokenId, discoverer: owner, @@ -363,7 +357,7 @@ export async function createArtifact( export async function testDeactivate(world: World, locationId: BigNumberish) { expect((await getArtifactsOnPlanet(world, locationId)).length).to.equal(0); - expect((await world.contract.getActiveArtifactOnPlanet(locationId)).id).to.equal(0); + expect(await world.contract.hasActiveArtifact(locationId)).to.equal(false); expect(await world.contract.getArtifactActivationTimeOnPlanet(locationId)).to.equal(0); } From c8d33a18d95a8fea2c81bd65b264ab6922f83d6c Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 15:31:11 +0100 Subject: [PATCH 47/55] feat: simply business logic bc of asserts in token create --- eth/contracts/facets/DFMoveFacet.sol | 19 +++++++++++-------- eth/contracts/libraries/LibArtifactUtils.sol | 5 +---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index af8ff2f6..c27fa294 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -120,7 +120,9 @@ contract DFMoveFacet is WithStorage { arrivalType = ArrivalType.Wormhole; } - if (!_isSpaceshipMove(args)) { + if (_isSpaceshipMove(args)) { + _removeSpaceshipEffectsFromOriginPlanet(args.oldLoc, args.movedArtifactId); + } else if (_isArtifactMove(args)) { (bool photoidPresent, Upgrade memory newTempUpgrade) = _checkPhotoid(args); if (photoidPresent) { temporaryUpgrade = newTempUpgrade; @@ -128,8 +130,6 @@ contract DFMoveFacet is WithStorage { } } - _removeSpaceshipEffectsFromOriginPlanet(args); - uint256 popMoved = args.popMoved; uint256 silverMoved = args.silverMoved; uint256 remainingOriginPlanetPopulation = gs().planets[args.oldLoc].population - popMoved; @@ -271,11 +271,10 @@ contract DFMoveFacet is WithStorage { /** Undo the spaceship effects that were applied when the ship arrived on the planet. */ - function _removeSpaceshipEffectsFromOriginPlanet(DFPMoveArgs memory args) private { - if (!LibSpaceship.isShip(args.movedArtifactId)) return; - Spaceship memory spaceship = LibSpaceship.decode(args.movedArtifactId); - Planet memory planet = applySpaceshipDepart(spaceship, gs().planets[args.oldLoc]); - gs().planets[args.oldLoc] = planet; + function _removeSpaceshipEffectsFromOriginPlanet(uint256 originLoc, uint256 shipId) private { + Spaceship memory spaceship = LibSpaceship.decode(shipId); + Planet memory planet = applySpaceshipDepart(spaceship, gs().planets[originLoc]); + gs().planets[originLoc] = planet; } /** @@ -425,6 +424,10 @@ contract DFMoveFacet is WithStorage { return LibSpaceship.isShip(args.movedArtifactId); } + function _isArtifactMove(DFPMoveArgs memory args) private pure returns (bool) { + return LibArtifact.isArtifact(args.movedArtifactId); + } + function _createArrival(DFPCreateArrivalArgs memory args) private { // enter the arrival data for event id Planet memory planet = gs().planets[args.oldLoc]; diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index d0f8cbfd..c41dc3cb 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -64,8 +64,6 @@ library LibArtifactUtils { address owner, SpaceshipType shipType ) public returns (uint256) { - require(shipType != SpaceshipType.Unknown, "incorrect ship type"); - uint256 tokenId = LibSpaceship.create(shipType) + uint128(gs().miscNonce++); Spaceship memory spaceship = DFArtifactFacet(address(this)).createSpaceship(tokenId, owner); @@ -298,7 +296,6 @@ library LibArtifactUtils { planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to deposit this artifact" ); - require(!LibSpaceship.isShip(artifactId), "cannot deposit spaceships"); require( gs().planetArtifacts[locationId].length + gs().planetSpaceships[locationId].length < 5, @@ -325,7 +322,7 @@ library LibArtifactUtils { planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to withdraw this artifact" ); - require(!LibSpaceship.isShip(artifactId), "cannot withdraw spaceships"); + LibArtifact.takeArtifactOffPlanet(locationId, artifactId); // artifactId, curr owner, new owner From 0f0a61cc6be4006f54640312c790e01d3664abcf Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 29 Sep 2022 01:57:50 -0700 Subject: [PATCH 48/55] chore: Remove lodash from the BrowserChecks util (#19) --- client/src/Frontend/Utils/BrowserChecks.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/src/Frontend/Utils/BrowserChecks.ts b/client/src/Frontend/Utils/BrowserChecks.ts index 0f1ba1e2..f9c3303e 100644 --- a/client/src/Frontend/Utils/BrowserChecks.ts +++ b/client/src/Frontend/Utils/BrowserChecks.ts @@ -1,5 +1,3 @@ -import _ from 'lodash'; - export const enum Incompatibility { NoIDB = 'no_idb', NotRopsten = 'not_ropsten', @@ -56,7 +54,11 @@ const checkFeatures = async (): Promise => { export const unsupportedFeatures = async (): Promise => { const features = await checkFeatures(); - return _.keys(features).filter((f: Incompatibility) => features[f]) as Incompatibility[]; + const incompats = Object.keys(features).filter( + (f: Incompatibility) => features[f] + ) as Incompatibility[]; + + return incompats; }; export const isFirefox = () => navigator.userAgent.indexOf('Firefox') > 0; From 4e5708d01db23cb299ce9e8e38ff6c05f9cae4aa Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 29 Sep 2022 01:59:07 -0700 Subject: [PATCH 49/55] fix: Always indicate contract owner is whitelisted (#20) --- client/src/Frontend/Pages/GameLandingPage.tsx | 5 +---- eth/contracts/facets/DFWhitelistFacet.sol | 3 +++ eth/test/DFWhitelist.test.ts | 8 ++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/client/src/Frontend/Pages/GameLandingPage.tsx b/client/src/Frontend/Pages/GameLandingPage.tsx index cccfd5b2..67a7bad0 100644 --- a/client/src/Frontend/Pages/GameLandingPage.tsx +++ b/client/src/Frontend/Pages/GameLandingPage.tsx @@ -430,14 +430,11 @@ export function GameLandingPage({ match, location }: RouteComponentProps<{ contr loadDiamondContract ); const isWhitelisted = await whitelist.isWhitelisted(playerAddress); - // TODO(#2329): isWhitelisted should just check the contractOwner - const adminAddress = address(await whitelist.adminAddress()); terminal.current?.println(''); terminal.current?.print('Checking if whitelisted... '); - // TODO(#2329): isWhitelisted should just check the contractOwner - if (isWhitelisted || playerAddress === adminAddress) { + if (isWhitelisted) { terminal.current?.println('Player whitelisted.'); terminal.current?.println(''); terminal.current?.println(`Welcome, player ${playerAddress}.`); diff --git a/eth/contracts/facets/DFWhitelistFacet.sol b/eth/contracts/facets/DFWhitelistFacet.sol index d7d2f400..b215a763 100644 --- a/eth/contracts/facets/DFWhitelistFacet.sol +++ b/eth/contracts/facets/DFWhitelistFacet.sol @@ -26,6 +26,9 @@ contract DFWhitelistFacet is WithStorage { if (!ws().enabled) { return true; } + if (LibPermissions.contractOwner() == _addr) { + return true; + } return ws().allowedAccounts[_addr]; } diff --git a/eth/test/DFWhitelist.test.ts b/eth/test/DFWhitelist.test.ts index 5f528ae3..68ac0ffb 100644 --- a/eth/test/DFWhitelist.test.ts +++ b/eth/test/DFWhitelist.test.ts @@ -25,6 +25,14 @@ describe('DarkForestWhitelist', function () { world = await loadFixture(worldFixture); }); + it('always indicates that the admin is whitelisted', async function () { + expect(await world.contract.isWhitelisted(world.deployer.address)).to.eq(true); + }); + + it('indicates that an address is not whitelisted before it uses a key', async function () { + expect(await world.contract.isWhitelisted(world.user1.address)).to.eq(false); + }); + it('allows a user to register with a valid key', async function () { const whitelistArgs = await makeWhitelistArgs(keys[0], world.user1.address as EthAddress); await world.user1Core.useKey(...whitelistArgs); From 535146f222df3f6ac897678847271aa0cd688662 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 29 Sep 2022 01:59:59 -0700 Subject: [PATCH 50/55] chore: Update committed contract addresses (#23) --- packages/contracts/index.d.ts | 4 ++-- packages/contracts/index.js | 4 ++-- packages/contracts/index.js.map | 2 +- packages/contracts/index.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/contracts/index.d.ts b/packages/contracts/index.d.ts index 8fb5872b..a9369a13 100644 --- a/packages/contracts/index.d.ts +++ b/packages/contracts/index.d.ts @@ -45,9 +45,9 @@ export declare const START_BLOCK = 0; /** * The address for the DarkForest contract. */ -export declare const CONTRACT_ADDRESS = "0x8950bab77f29E8f81e6F78AEA0a79bADD88Eeb13"; +export declare const CONTRACT_ADDRESS = "0x627a72bbE16416Ae722BA05876C5cB2dcb0Dc6BB"; /** * The address for the initalizer contract. Useful for lobbies. */ -export declare const INIT_ADDRESS = "0x500cf53555c09948f4345594F9523E7B444cD67E"; +export declare const INIT_ADDRESS = "0x1aE9623899dDc2bB42217eF985a3d98E6E7623C1"; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/contracts/index.js b/packages/contracts/index.js index 29de010f..65760619 100644 --- a/packages/contracts/index.js +++ b/packages/contracts/index.js @@ -48,9 +48,9 @@ exports.START_BLOCK = 0; /** * The address for the DarkForest contract. */ -exports.CONTRACT_ADDRESS = '0x8950bab77f29E8f81e6F78AEA0a79bADD88Eeb13'; +exports.CONTRACT_ADDRESS = '0x627a72bbE16416Ae722BA05876C5cB2dcb0Dc6BB'; /** * The address for the initalizer contract. Useful for lobbies. */ -exports.INIT_ADDRESS = '0x500cf53555c09948f4345594F9523E7B444cD67E'; +exports.INIT_ADDRESS = '0x1aE9623899dDc2bB42217eF985a3d98E6E7623C1'; //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/contracts/index.js.map b/packages/contracts/index.js.map index fe4d9b6b..73ae82a7 100644 --- a/packages/contracts/index.js.map +++ b/packages/contracts/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;;;AAEH;;GAEG;AACU,QAAA,OAAO,GAAG,WAAW,CAAC;AACnC;;GAEG;AACU,QAAA,UAAU,GAAG,KAAK,CAAC;AAChC;;GAEG;AACU,QAAA,WAAW,GAAG,CAAC,CAAC;AAC7B;;GAEG;AACU,QAAA,gBAAgB,GAAG,4CAA4C,CAAC;AAC7E;;GAEG;AACU,QAAA,YAAY,GAAG,4CAA4C,CAAC","sourcesContent":["/**\n * This package contains deployed contract addresses, ABIs, and Typechain types\n * for the Dark Forest game.\n *\n * ## Installation\n *\n * You can install this package using [`npm`](https://www.npmjs.com) or\n * [`yarn`](https://classic.yarnpkg.com/lang/en/) by running:\n *\n * ```bash\n * npm install --save @dfdao/contracts\n * ```\n * ```bash\n * yarn add @dfdao/contracts\n * ```\n *\n * When using this in a plugin, you might want to load it with [skypack](https://www.skypack.dev)\n *\n * ```js\n * import * as contracts from 'http://cdn.skypack.dev/@dfdao/contracts'\n * ```\n *\n * ## Typechain\n *\n * The Typechain types can be found in the `typechain` directory.\n *\n * ## ABIs\n *\n * The contract ABIs can be found in the `abis` directory.\n *\n * @packageDocumentation\n */\n\n/**\n * The name of the network where these contracts are deployed.\n */\nexport const NETWORK = 'localhost';\n/**\n * The id of the network where these contracts are deployed.\n */\nexport const NETWORK_ID = 31337;\n/**\n * The block in which the DarkForest contract was initialized.\n */\nexport const START_BLOCK = 0;\n/**\n * The address for the DarkForest contract.\n */\nexport const CONTRACT_ADDRESS = '0x8950bab77f29E8f81e6F78AEA0a79bADD88Eeb13';\n/**\n * The address for the initalizer contract. Useful for lobbies.\n */\nexport const INIT_ADDRESS = '0x500cf53555c09948f4345594F9523E7B444cD67E';"]} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;;;AAEH;;GAEG;AACU,QAAA,OAAO,GAAG,WAAW,CAAC;AACnC;;GAEG;AACU,QAAA,UAAU,GAAG,KAAK,CAAC;AAChC;;GAEG;AACU,QAAA,WAAW,GAAG,CAAC,CAAC;AAC7B;;GAEG;AACU,QAAA,gBAAgB,GAAG,4CAA4C,CAAC;AAC7E;;GAEG;AACU,QAAA,YAAY,GAAG,4CAA4C,CAAC","sourcesContent":["/**\n * This package contains deployed contract addresses, ABIs, and Typechain types\n * for the Dark Forest game.\n *\n * ## Installation\n *\n * You can install this package using [`npm`](https://www.npmjs.com) or\n * [`yarn`](https://classic.yarnpkg.com/lang/en/) by running:\n *\n * ```bash\n * npm install --save @dfdao/contracts\n * ```\n * ```bash\n * yarn add @dfdao/contracts\n * ```\n *\n * When using this in a plugin, you might want to load it with [skypack](https://www.skypack.dev)\n *\n * ```js\n * import * as contracts from 'http://cdn.skypack.dev/@dfdao/contracts'\n * ```\n *\n * ## Typechain\n *\n * The Typechain types can be found in the `typechain` directory.\n *\n * ## ABIs\n *\n * The contract ABIs can be found in the `abis` directory.\n *\n * @packageDocumentation\n */\n\n/**\n * The name of the network where these contracts are deployed.\n */\nexport const NETWORK = 'localhost';\n/**\n * The id of the network where these contracts are deployed.\n */\nexport const NETWORK_ID = 31337;\n/**\n * The block in which the DarkForest contract was initialized.\n */\nexport const START_BLOCK = 0;\n/**\n * The address for the DarkForest contract.\n */\nexport const CONTRACT_ADDRESS = '0x627a72bbE16416Ae722BA05876C5cB2dcb0Dc6BB';\n/**\n * The address for the initalizer contract. Useful for lobbies.\n */\nexport const INIT_ADDRESS = '0x1aE9623899dDc2bB42217eF985a3d98E6E7623C1';"]} \ No newline at end of file diff --git a/packages/contracts/index.ts b/packages/contracts/index.ts index 8d78358e..052b9541 100644 --- a/packages/contracts/index.ts +++ b/packages/contracts/index.ts @@ -46,8 +46,8 @@ export const START_BLOCK = 0; /** * The address for the DarkForest contract. */ -export const CONTRACT_ADDRESS = '0x8950bab77f29E8f81e6F78AEA0a79bADD88Eeb13'; +export const CONTRACT_ADDRESS = '0x627a72bbE16416Ae722BA05876C5cB2dcb0Dc6BB'; /** * The address for the initalizer contract. Useful for lobbies. */ -export const INIT_ADDRESS = '0x500cf53555c09948f4345594F9523E7B444cD67E'; \ No newline at end of file +export const INIT_ADDRESS = '0x1aE9623899dDc2bB42217eF985a3d98E6E7623C1'; \ No newline at end of file From 619277f6378e1d7e9bf9795738868f20c6db70b3 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 2 Oct 2022 17:32:14 -0700 Subject: [PATCH 51/55] feat: Rework PersistentChunkStore to use Yjs (#22) chore: Split non-chunk storage out of the ChunkStore(#21) chore: Trim unused types and move remaining into ChunkUtils --- client/package.json | 4 +- client/src/Backend/GameLogic/GameManager.ts | 46 +- client/src/Backend/GameLogic/GameUIManager.ts | 2 +- .../GameLogic/InitialGameStateDownloader.tsx | 12 +- client/src/Backend/Miner/ChunkUtils.ts | 118 ++--- client/src/Backend/Miner/MinerManager.ts | 3 +- client/src/Backend/Storage/OtherStore.ts | 244 +++++++++ .../Backend/Storage/PersistentChunkStore.ts | 466 ++---------------- client/src/Backend/Storage/ReaderDataStore.ts | 7 +- client/src/Frontend/Game/ModalManager.ts | 21 +- .../_types/darkforest/api/ChunkStoreTypes.ts | 44 -- package-lock.json | 88 +++- 12 files changed, 469 insertions(+), 586 deletions(-) create mode 100644 client/src/Backend/Storage/OtherStore.ts delete mode 100644 client/src/_types/darkforest/api/ChunkStoreTypes.ts diff --git a/client/package.json b/client/package.json index 52043bd4..99519934 100644 --- a/client/package.json +++ b/client/package.json @@ -56,7 +56,9 @@ "sortablejs": "^1.10.2", "styled-components": "^5.3.3", "ts-dedent": "^2.0.0", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "yjs": "^13.5.41", + "y-indexeddb": "^9.0.9" }, "scripts": { "declarations": "tsc -p tsconfig.decs.json", diff --git a/client/src/Backend/GameLogic/GameManager.ts b/client/src/Backend/GameLogic/GameManager.ts index 6041a58e..28831c0d 100644 --- a/client/src/Backend/GameLogic/GameManager.ts +++ b/client/src/Backend/GameLogic/GameManager.ts @@ -136,6 +136,7 @@ import { verifyTwitterHandle, } from '../Network/UtilityServerAPI'; import { SerializedPlugin } from '../Plugins/SerializedPlugin'; +import OtherStore from '../Storage/OtherStore'; import PersistentChunkStore from '../Storage/PersistentChunkStore'; import { easeInAnimation, emojiEaseOutAnimation } from '../Utils/Animation'; import SnarkArgsHelper from '../Utils/SnarkArgsHelper'; @@ -207,6 +208,8 @@ class GameManager extends EventEmitter { * or something like that. */ private readonly persistentChunkStore: PersistentChunkStore; + // Literally other storage stuff that people dumped into the chunk store + private readonly otherStore: OtherStore; /** * Responsible for generating snark proofs. @@ -368,6 +371,7 @@ class GameManager extends EventEmitter { contractsAPI: ContractsAPI, contractConstants: ContractConstants, persistentChunkStore: PersistentChunkStore, + otherStore: OtherStore, snarkHelper: SnarkArgsHelper, homeLocation: WorldLocation | undefined, useMockHash: boolean, @@ -474,6 +478,7 @@ class GameManager extends EventEmitter { this.contractsAPI = contractsAPI; this.persistentChunkStore = persistentChunkStore; + this.otherStore = otherStore; this.snarkHelper = snarkHelper; this.useMockHash = useMockHash; this.paused = paused; @@ -588,20 +593,25 @@ class GameManager extends EventEmitter { terminal.current?.println('Loading game data from disk...'); - const persistentChunkStore = await PersistentChunkStore.create({ account, contractAddress }); + const persistentChunkStore = new PersistentChunkStore({ account, contractAddress }); + const otherStore = await OtherStore.create({ account, contractAddress }); terminal.current?.println('Downloading data from Ethereum blockchain...'); terminal.current?.println('(the contract is very big. this may take a while)'); terminal.current?.newline(); - const initialState = await gameStateDownloader.download(contractsAPI, persistentChunkStore); - const possibleHomes = await persistentChunkStore.getHomeLocations(); + const initialState = await gameStateDownloader.download( + contractsAPI, + persistentChunkStore, + otherStore + ); + const possibleHomes = await otherStore.getHomeLocations(); terminal.current?.println(''); terminal.current?.println('Building Index...'); - await persistentChunkStore.saveTouchedPlanetIds(initialState.allTouchedPlanetIds); - await persistentChunkStore.saveRevealedCoords(initialState.allRevealedCoords); + await otherStore.saveTouchedPlanetIds(initialState.allTouchedPlanetIds); + await otherStore.saveRevealedCoords(initialState.allRevealedCoords); const knownArtifacts: Map = new Map(); @@ -632,7 +642,7 @@ class GameManager extends EventEmitter { for (const loc of possibleHomes) { if (initialState.allTouchedPlanetIds.includes(loc.hash)) { homeLocation = loc; - await persistentChunkStore.confirmHomeLocation(loc); + await otherStore.confirmHomeLocation(loc); break; } } @@ -666,6 +676,7 @@ class GameManager extends EventEmitter { contractsAPI, initialState.contractConstants, persistentChunkStore, + otherStore, snarkHelper, homeLocation, useMockHash, @@ -751,12 +762,12 @@ class GameManager extends EventEmitter { gameManager.entityStore.onTxIntent(tx); }) .on(ContractsAPIEvent.TxSubmitted, (tx: Transaction) => { - gameManager.persistentChunkStore.onEthTxSubmit(tx); + gameManager.otherStore.onEthTxSubmit(tx); gameManager.onTxSubmit(tx); }) .on(ContractsAPIEvent.TxConfirmed, async (tx: Transaction) => { if (!tx.hash) return; // this should never happen - gameManager.persistentChunkStore.onEthTxComplete(tx.hash); + gameManager.otherStore.onEthTxComplete(tx.hash); if (isUnconfirmedRevealTx(tx)) { await gameManager.hardRefreshPlanet(tx.intent.locationId); @@ -832,7 +843,7 @@ class GameManager extends EventEmitter { .on(ContractsAPIEvent.TxErrored, async (tx: Transaction) => { gameManager.entityStore.clearUnconfirmedTxIntent(tx); if (tx.hash) { - gameManager.persistentChunkStore.onEthTxComplete(tx.hash); + gameManager.otherStore.onEthTxComplete(tx.hash); } gameManager.onTxReverted(tx); }) @@ -844,7 +855,7 @@ class GameManager extends EventEmitter { gameManager.setRadius(newRadius); }); - const unconfirmedTxs = await persistentChunkStore.getUnconfirmedSubmittedEthTxs(); + const unconfirmedTxs = await otherStore.getUnconfirmedSubmittedEthTxs(); const confirmationQueue = new ThrottledConcurrentQueue({ invocationIntervalMs: 1000, maxInvocationsPerIntervalMs: 10, @@ -1590,6 +1601,9 @@ class GameManager extends EventEmitter { getChunkStore(): PersistentChunkStore { return this.persistentChunkStore; } + getOtherStore(): OtherStore { + return this.otherStore; + } /** * The perlin value at each coordinate determines the space type. There are four space @@ -1912,7 +1926,7 @@ class GameManager extends EventEmitter { ); this.terminal.current?.println(''); - await this.persistentChunkStore.addHomeLocation(planet.location); + await this.otherStore.addHomeLocation(planet.location); const getArgs = async () => { const args = await this.snarkHelper.getInitArgs( @@ -2012,7 +2026,7 @@ class GameManager extends EventEmitter { */ async addAccount(coords: WorldCoords): Promise { const loc: WorldLocation = this.locationFromCoords(coords); - await this.persistentChunkStore.addHomeLocation(loc); + await this.otherStore.addHomeLocation(loc); this.initMiningManager(coords); this.homeLocation = loc; return true; @@ -2983,7 +2997,7 @@ class GameManager extends EventEmitter { * all of the information about those planets from the blockchain. */ addNewChunk(chunk: Chunk): GameManager { - this.persistentChunkStore.addChunk(chunk, true); + this.persistentChunkStore.addChunk(chunk); for (const planetLocation of chunk.planetLocations) { this.entityStore.addPlanetLocation(planetLocation); @@ -3012,7 +3026,7 @@ class GameManager extends EventEmitter { ); const planetIdsToUpdate: LocationId[] = []; for (const chunk of chunks) { - this.persistentChunkStore.addChunk(chunk, true); + this.persistentChunkStore.addChunk(chunk); for (const planetLocation of chunk.planetLocations) { this.entityStore.addPlanetLocation(planetLocation); @@ -3236,14 +3250,14 @@ class GameManager extends EventEmitter { * Load the serialized versions of all the plugins that this player has. */ public async loadPlugins(): Promise { - return this.persistentChunkStore.loadPlugins(); + return this.otherStore.loadPlugins(); } /** * Overwrites all the saved plugins to equal the given array of plugins. */ public async savePlugins(savedPlugins: SerializedPlugin[]): Promise { - await this.persistentChunkStore.savePlugins(savedPlugins); + await this.otherStore.savePlugins(savedPlugins); } /** diff --git a/client/src/Backend/GameLogic/GameUIManager.ts b/client/src/Backend/GameLogic/GameUIManager.ts index ab90941d..764d6877 100644 --- a/client/src/Backend/GameLogic/GameUIManager.ts +++ b/client/src/Backend/GameLogic/GameUIManager.ts @@ -201,7 +201,7 @@ class GameUIManager extends EventEmitter { const uiEmitter = UIEmitter.getInstance(); const uiManager = new GameUIManager(gameManager, terminalHandle); - const modalManager = await ModalManager.create(gameManager.getChunkStore()); + const modalManager = await ModalManager.create(gameManager.getOtherStore()); uiManager.setModalManager(modalManager); diff --git a/client/src/Backend/GameLogic/InitialGameStateDownloader.tsx b/client/src/Backend/GameLogic/InitialGameStateDownloader.tsx index 675e0327..300ed60d 100644 --- a/client/src/Backend/GameLogic/InitialGameStateDownloader.tsx +++ b/client/src/Backend/GameLogic/InitialGameStateDownloader.tsx @@ -18,6 +18,7 @@ import { TerminalHandle } from '../../Frontend/Views/Terminal'; import { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes'; import { AddressTwitterMap } from '../../_types/darkforest/api/UtilityServerAPITypes'; import { tryGetAllTwitters } from '../Network/UtilityServerAPI'; +import OtherStore from '../Storage/OtherStore'; import PersistentChunkStore from '../Storage/PersistentChunkStore'; import { ContractsAPI } from './ContractsAPI'; @@ -61,7 +62,8 @@ export class InitialGameStateDownloader { async download( contractsAPI: ContractsAPI, - persistentChunkStore: PersistentChunkStore + persistentChunkStore: PersistentChunkStore, + otherStore: OtherStore ): Promise { /** * In development we use the same contract address every time we deploy, @@ -69,10 +71,10 @@ export class InitialGameStateDownloader { */ const storedTouchedPlanetIds = import.meta.env.DEV ? [] - : await persistentChunkStore.getSavedTouchedPlanetIds(); + : await otherStore.getSavedTouchedPlanetIds(); const storedRevealedCoords = import.meta.env.DEV ? [] - : await persistentChunkStore.getSavedRevealedCoords(); + : await otherStore.getSavedRevealedCoords(); this.terminal.printElement(); this.terminal.newline(); @@ -98,7 +100,9 @@ export class InitialGameStateDownloader { const arrivals: Map = new Map(); const planetVoyageIdMap: Map = new Map(); - const minedChunks = Array.from(await persistentChunkStore.allChunks()); + // Ensure that all chunks have been loaded from indexeddb + await persistentChunkStore.chunksLoaded(); + const minedChunks = Array.from(persistentChunkStore.allChunks()); const minedPlanetIds = new Set( _.flatMap(minedChunks, (c) => c.planetLocations).map((l) => l.hash) ); diff --git a/client/src/Backend/Miner/ChunkUtils.ts b/client/src/Backend/Miner/ChunkUtils.ts index f0eb8c20..982b27cd 100644 --- a/client/src/Backend/Miner/ChunkUtils.ts +++ b/client/src/Backend/Miner/ChunkUtils.ts @@ -1,5 +1,20 @@ +import type { Abstract } from '@dfdao/types'; import { Chunk, Rectangle, WorldCoords, WorldLocation } from '@dfdao/types'; -import { BucketId, ChunkId, PersistedChunk } from '../../_types/darkforest/api/ChunkStoreTypes'; +import { Map } from 'yjs'; + +/** + * one of "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + */ +type BucketId = Abstract; +type ChunkId = Abstract; + +/** + * Abstract interface shared between different types of chunk stores. Currently we have one that + * writes to IndexedDB, and one that simply throws away the data. + */ +export interface ChunkStore { + hasMinedChunk: (chunkFootprint: Rectangle) => boolean; +} /** * Deterministically assigns a bucket ID to a rectangle, based on its position and size in the @@ -27,48 +42,6 @@ export function getChunkKey(chunkLoc: Rectangle): ChunkId { `${chunkLoc.bottomLeft.y}`) as ChunkId; } -/** - * Converts from the in-game representation of a chunk to its persisted representation. - */ -export function toPersistedChunk(chunk: Chunk): PersistedChunk { - const planetLocations = chunk.planetLocations.map((location) => ({ - x: location.coords.x, - y: location.coords.y, - h: location.hash, - p: location.perlin, - b: location.biomebase, - })); - - return { - x: chunk.chunkFootprint.bottomLeft.x, - y: chunk.chunkFootprint.bottomLeft.y, - s: chunk.chunkFootprint.sideLength, - l: planetLocations, - p: chunk.perlin, - }; -} - -/** - * Converts from the persisted representation of a chunk to the in-game representation of a chunk. - */ -export const toExploredChunk = (chunk: PersistedChunk): Chunk => { - const planetLocations = chunk.l.map((location) => ({ - coords: { x: location.x, y: location.y }, - hash: location.h, - perlin: location.p, - biomebase: location.b, - })); - - return { - chunkFootprint: { - bottomLeft: { x: chunk.x, y: chunk.y }, - sideLength: chunk.s, - }, - planetLocations, - perlin: chunk.p, - }; -}; - /** * An aligned chunk is one whose corner's coordinates are multiples of its side length, and its side * length is a power of two between {@link MIN_CHUNK_SIZE} and {@link MAX_CHUNK_SIZE} inclusive. @@ -85,7 +58,7 @@ export const toExploredChunk = (chunk: PersistedChunk): Chunk => { * that the four chunks, if merged, would result in an "aligned" chunk whose side length is double * the given chunk. */ -export const getSiblingLocations = (chunkLoc: Rectangle): [Rectangle, Rectangle, Rectangle] => { +function getSiblingLocations(chunkLoc: Rectangle): [Rectangle, Rectangle, Rectangle] { const doubleSideLen = 2 * chunkLoc.sideLength; const newBottomLeftX = Math.floor(chunkLoc.bottomLeft.x / doubleSideLen) * doubleSideLen; const newBottomLeftY = Math.floor(chunkLoc.bottomLeft.y / doubleSideLen) * doubleSideLen; @@ -109,7 +82,7 @@ export const getSiblingLocations = (chunkLoc: Rectangle): [Rectangle, Rectangle, } } return [siblingLocs[0], siblingLocs[1], siblingLocs[2]]; -}; +} /** * Returns the unique aligned chunk (for definition of "aligned" see comment on @@ -134,7 +107,7 @@ export function getChunkOfSideLengthContainingPoint( * At a high level, call this function to update an efficient quadtree-like store containing all of * the chunks that a player has either mined or imported in their client. * - * More speecifically, adds the given new chunk to the given map of chunks. If the map of chunks + * More specifically, adds the given new chunk to the given map of chunks. If the map of chunks * contains all of the "sibling" chunks to this new chunk, then instead of adding it, we merge the 4 * sibling chunks, and add the merged chunk to the map and remove the existing sibling chunks. This * function is recursive, which means that if the newly created merged chunk can also be merged with @@ -149,21 +122,44 @@ export function getChunkOfSideLengthContainingPoint( * `existingChunks` map. `onAdd` will be called exactly once, whereas `onRemove` only ever be called * for sibling chunks that existed prior to this function being called. */ -export function addToChunkMap( - existingChunks: Map, - newChunk: Chunk, - onAdd?: (arg: Chunk) => void, - onRemove?: (arg: Chunk) => void, +export function processChunkInMap( + existingChunks: Map, + chunk: Chunk, + onAdd: (arg: Chunk) => void, + onRemove: (arg: Chunk) => void, maxChunkSize?: number ) { - let sideLength = newChunk.chunkFootprint.sideLength; + for ( + let clearingSideLen = 16; + clearingSideLen < chunk.chunkFootprint.sideLength; + clearingSideLen *= 2 + ) { + for (let x = 0; x < chunk.chunkFootprint.sideLength; x += clearingSideLen) { + for (let y = 0; y < chunk.chunkFootprint.sideLength; y += clearingSideLen) { + const queryChunk: Rectangle = { + bottomLeft: { + x: chunk.chunkFootprint.bottomLeft.x + x, + y: chunk.chunkFootprint.bottomLeft.y + y, + }, + sideLength: clearingSideLen, + }; + const queryChunkKey = getChunkKey(queryChunk); + const exploredChunk = existingChunks.get(queryChunkKey); + if (exploredChunk) { + onRemove(exploredChunk); + } + } + } + } + + let sideLength = chunk.chunkFootprint.sideLength; let chunkToAdd: Chunk = { chunkFootprint: { - bottomLeft: newChunk.chunkFootprint.bottomLeft, + bottomLeft: chunk.chunkFootprint.bottomLeft, sideLength, }, - planetLocations: [...newChunk.planetLocations], - perlin: newChunk.perlin, + planetLocations: [...chunk.planetLocations], + perlin: chunk.perlin, }; while (!maxChunkSize || sideLength < maxChunkSize) { const siblingLocs = getSiblingLocations(chunkToAdd.chunkFootprint); @@ -181,12 +177,8 @@ export function addToChunkMap( for (const siblingLoc of siblingLocs) { const siblingKey = getChunkKey(siblingLoc); const sibling = existingChunks.get(siblingKey); - if (onRemove !== undefined && sibling) { - onRemove(sibling); - } else { - existingChunks.delete(siblingKey); - } if (sibling) { + onRemove(sibling); planetLocations = planetLocations.concat(sibling.planetLocations); newPerlin += sibling.perlin / 4; } @@ -201,9 +193,5 @@ export function addToChunkMap( perlin: Math.floor(newPerlin * 1000) / 1000, }; } - if (onAdd !== undefined) { - onAdd(chunkToAdd); - } else { - existingChunks.set(getChunkKey(chunkToAdd.chunkFootprint), chunkToAdd); - } + onAdd(chunkToAdd); } diff --git a/client/src/Backend/Miner/MinerManager.ts b/client/src/Backend/Miner/MinerManager.ts index 4dc3f939..533e6c9d 100644 --- a/client/src/Backend/Miner/MinerManager.ts +++ b/client/src/Backend/Miner/MinerManager.ts @@ -2,9 +2,8 @@ import { perlin } from '@dfdao/hashing'; import { Chunk, PerlinConfig, Rectangle } from '@dfdao/types'; import { EventEmitter } from 'events'; import _ from 'lodash'; -import { ChunkStore } from '../../_types/darkforest/api/ChunkStoreTypes'; import { HashConfig, MinerWorkerMessage } from '../../_types/global/GlobalTypes'; -import { getChunkKey } from './ChunkUtils'; +import { ChunkStore, getChunkKey } from './ChunkUtils'; import DefaultWorker from './miner.worker.ts?worker'; import { MiningPattern } from './MiningPatterns'; diff --git a/client/src/Backend/Storage/OtherStore.ts b/client/src/Backend/Storage/OtherStore.ts new file mode 100644 index 00000000..238dda12 --- /dev/null +++ b/client/src/Backend/Storage/OtherStore.ts @@ -0,0 +1,244 @@ +import { + ClaimedCoords, + EthAddress, + LocationId, + ModalId, + ModalPosition, + PersistedTransaction, + RevealedCoords, + Transaction, + WorldLocation, +} from '@dfdao/types'; +import { IDBPDatabase, openDB } from 'idb'; +import stringify from 'json-stable-stringify'; +import { SerializedPlugin } from '../Plugins/SerializedPlugin'; + +const enum ObjectStore { + DEFAULT = 'default', + UNCONFIRMED_ETH_TXS = 'unminedEthTxs', + PLUGINS = 'plugins', + /** + * Store modal positions so that we can keep modal panes open across sessions. + */ + MODAL_POS = 'modalPositions', +} + +interface OtherStoreConfig { + db: IDBPDatabase; + contractAddress: EthAddress; + account: EthAddress; +} + +export const MODAL_POSITIONS_KEY = 'modal_positions'; + +class OtherStore { + private db: IDBPDatabase; + private confirmedTxHashes: Set; + private account: EthAddress; + private contractAddress: EthAddress; + + constructor({ db, account, contractAddress }: OtherStoreConfig) { + this.db = db; + this.confirmedTxHashes = new Set(); + this.account = account; + this.contractAddress = contractAddress; + } + + destroy(): void { + // no-op; we don't actually destroy the instance, we leave the db connection open in case we need it in the future + } + + /** + * NOTE! if you're creating a new object store, it will not be *added* to existing dark forest + * accounts. This creation code runs once per account. Therefore, if you're adding a new object + * store, and need to test it out, you must either clear the indexed db databse for this account, + * or create a brand new account. + */ + static async create({ + account, + contractAddress, + }: Omit): Promise { + const db = await openDB(`darkforest-${contractAddress}-${account}`, 1, { + upgrade(db) { + db.createObjectStore(ObjectStore.DEFAULT); + db.createObjectStore(ObjectStore.UNCONFIRMED_ETH_TXS); + db.createObjectStore(ObjectStore.PLUGINS); + db.createObjectStore(ObjectStore.MODAL_POS); + }, + }); + + const localStorageManager = new OtherStore({ db, account, contractAddress }); + + return localStorageManager; + } + + /** + * Important! This sets the key in indexed db per account and per contract. This means the same + * client can connect to multiple different dark forest contracts, with multiple different + * accounts, and the persistent storage will not overwrite data that is not relevant for the + * current configuration of the client. + */ + private async getKey( + key: string, + objStore: ObjectStore = ObjectStore.DEFAULT + ): Promise { + return await this.db.get(objStore, `${this.contractAddress}-${this.account}-${key}`); + } + + /** + * Important! This sets the key in indexed db per account and per contract. This means the same + * client can connect to multiple different dark forest contracts, with multiple different + * accounts, and the persistent storage will not overwrite data that is not relevant for the + * current configuration of the client. + */ + private async setKey( + key: string, + value: string, + objStore: ObjectStore = ObjectStore.DEFAULT + ): Promise { + await this.db.put(objStore, value, `${this.contractAddress}-${this.account}-${key}`); + } + + private async removeKey(key: string, objStore: ObjectStore = ObjectStore.DEFAULT): Promise { + await this.db.delete(objStore, `${this.contractAddress}-${this.account}-${key}`); + } + + /** + * we keep a list rather than a single location, since client/contract can + * often go out of sync on initialization - if client thinks that init + * failed but is wrong, it will prompt user to initialize with new home coords, + * which bricks the user's account. + */ + public async getHomeLocations(): Promise { + const homeLocations = await this.getKey('homeLocations'); + let parsed: WorldLocation[] = []; + if (homeLocations) { + parsed = JSON.parse(homeLocations) as WorldLocation[]; + } + + return parsed; + } + + public async addHomeLocation(location: WorldLocation): Promise { + let locationList = await this.getHomeLocations(); + if (locationList) { + locationList.push(location); + } else { + locationList = [location]; + } + locationList = Array.from(new Set(locationList)); + await this.setKey('homeLocations', stringify(locationList)); + } + + public async confirmHomeLocation(location: WorldLocation): Promise { + await this.setKey('homeLocations', stringify([location])); + } + + public async getSavedTouchedPlanetIds(): Promise { + const touchedPlanetIds = await this.getKey('touchedPlanetIds'); + + if (touchedPlanetIds) { + const parsed = JSON.parse(touchedPlanetIds) as LocationId[]; + return parsed; + } + + return []; + } + + public async getSavedRevealedCoords(): Promise { + const revealedPlanetIds = await this.getKey('revealedPlanetIds'); + + if (revealedPlanetIds) { + const parsed = JSON.parse(revealedPlanetIds); + // changed the type on 6/1/21 to include revealer field + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (parsed.length === 0 || !(parsed[0] as any).revealer) { + return []; + } + return parsed as RevealedCoords[]; + } + + return []; + } + public async getSavedClaimedCoords(): Promise { + const claimedPlanetIds = await this.getKey('claimedPlanetIds'); + + if (claimedPlanetIds) { + const parsed = JSON.parse(claimedPlanetIds); + // changed the type on 6/1/21 to include revealer field + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (parsed.length === 0 || !(parsed[0] as any).revealer) { + return []; + } + return parsed as ClaimedCoords[]; + } + + return []; + } + + public async saveTouchedPlanetIds(ids: LocationId[]) { + await this.setKey('touchedPlanetIds', stringify(ids)); + } + + public async saveRevealedCoords(revealedCoordTups: RevealedCoords[]) { + await this.setKey('revealedPlanetIds', stringify(revealedCoordTups)); + } + + public async saveClaimedCoords(claimedCoordTupps: ClaimedCoords[]) { + await this.setKey('claimedPlanetIds', stringify(claimedCoordTupps)); + } + + /** + * Whenever a transaction is submitted, it is persisted. When the transaction either fails or + * succeeds, it is un-persisted. The reason we persist submitted transactions is to be able to + * wait for them upon a fresh start of the game if you close the game before a transaction + * confirms. + */ + public async onEthTxSubmit(tx: Transaction): Promise { + // in case the tx was mined and saved already + if (!tx.hash || this.confirmedTxHashes.has(tx.hash)) return; + const ser: PersistedTransaction = { hash: tx.hash, intent: tx.intent }; + await this.db.put(ObjectStore.UNCONFIRMED_ETH_TXS, JSON.parse(JSON.stringify(ser)), tx.hash); + } + + /** + * Partner function to {@link OtherStore#onEthTxSubmit} + */ + public async onEthTxComplete(txHash: string): Promise { + this.confirmedTxHashes.add(txHash); + await this.db.delete(ObjectStore.UNCONFIRMED_ETH_TXS, txHash); + } + + public async getUnconfirmedSubmittedEthTxs(): Promise { + const ret: PersistedTransaction[] = await this.db.getAll(ObjectStore.UNCONFIRMED_ETH_TXS); + return ret; + } + + public async loadPlugins(): Promise { + const savedPlugins = await this.getKey('plugins', ObjectStore.PLUGINS); + + if (!savedPlugins) { + return []; + } + + return JSON.parse(savedPlugins) as SerializedPlugin[]; + } + + public async savePlugins(plugins: SerializedPlugin[]): Promise { + await this.setKey('plugins', JSON.stringify(plugins), ObjectStore.PLUGINS); + } + + public async saveModalPositions(modalPositions: Map): Promise { + if (!this.db.objectStoreNames.contains(ObjectStore.MODAL_POS)) return; + const serialized = JSON.stringify(Array.from(modalPositions.entries())); + await this.setKey(MODAL_POSITIONS_KEY, serialized, ObjectStore.MODAL_POS); + } + + public async loadModalPositions(): Promise> { + if (!this.db.objectStoreNames.contains(ObjectStore.MODAL_POS)) return new Map(); + const winPos = await this.getKey(MODAL_POSITIONS_KEY, ObjectStore.MODAL_POS); + return new Map(winPos ? JSON.parse(winPos) : null); + } +} + +export default OtherStore; diff --git a/client/src/Backend/Storage/PersistentChunkStore.ts b/client/src/Backend/Storage/PersistentChunkStore.ts index 609d3922..ae386c9f 100644 --- a/client/src/Backend/Storage/PersistentChunkStore.ts +++ b/client/src/Backend/Storage/PersistentChunkStore.ts @@ -1,121 +1,32 @@ -import { - Chunk, - ClaimedCoords, - DiagnosticUpdater, - EthAddress, - LocationId, - ModalId, - ModalPosition, - PersistedTransaction, - Rectangle, - RevealedCoords, - Transaction, - WorldLocation, -} from '@dfdao/types'; -import { IDBPDatabase, openDB } from 'idb'; -import stringify from 'json-stable-stringify'; -import _ from 'lodash'; +import { Chunk, DiagnosticUpdater, EthAddress, Rectangle } from '@dfdao/types'; +import { IndexeddbPersistence } from 'y-indexeddb'; +import { Doc, Map } from 'yjs'; import { MAX_CHUNK_SIZE } from '../../Frontend/Utils/constants'; -import { ChunkId, ChunkStore, PersistedChunk } from '../../_types/darkforest/api/ChunkStoreTypes'; import { - addToChunkMap, + ChunkStore, getChunkKey, getChunkOfSideLengthContainingPoint, - toExploredChunk, - toPersistedChunk, + processChunkInMap, } from '../Miner/ChunkUtils'; -import { SerializedPlugin } from '../Plugins/SerializedPlugin'; - -const enum ObjectStore { - DEFAULT = 'default', - BOARD = 'knownBoard', - UNCONFIRMED_ETH_TXS = 'unminedEthTxs', - PLUGINS = 'plugins', - /** - * Store modal positions so that we can keep modal panes open across sessions. - */ - MODAL_POS = 'modalPositions', -} - -const enum DBActionType { - UPDATE, - DELETE, -} - -interface DBAction { - type: DBActionType; - dbKey: T; - dbValue?: Chunk; -} - -type DBTx = DBAction[]; - -interface DebouncedFunc void> { - (...args: Parameters): ReturnType | undefined; - cancel(): void; -} - -interface PersistentChunkStoreConfig { - db: IDBPDatabase; - contractAddress: EthAddress; - account: EthAddress; -} - -export const MODAL_POSITIONS_KEY = 'modal_positions'; class PersistentChunkStore implements ChunkStore { private diagnosticUpdater?: DiagnosticUpdater; - private db: IDBPDatabase; - private queuedChunkWrites: DBTx[]; - private throttledSaveChunkCacheToDisk: DebouncedFunc<() => Promise>; - private nUpdatesLastTwoMins = 0; // we save every 5s, unless this goes above 50 - private chunkMap: Map; - private confirmedTxHashes: Set; - private account: EthAddress; - private contractAddress: EthAddress; + private doc: Doc; + private chunkMap: Map; + private db: IndexeddbPersistence; + + constructor(config: { account: EthAddress; contractAddress: string }) { + this.doc = new Doc(); + this.chunkMap = this.doc.getMap('chunks'); - constructor({ db, account, contractAddress }: PersistentChunkStoreConfig) { - this.db = db; - this.queuedChunkWrites = []; - this.confirmedTxHashes = new Set(); - this.throttledSaveChunkCacheToDisk = _.throttle( - this.persistQueuedChunks, - 2000 // TODO + this.db = new IndexeddbPersistence( + `darkforest-${config.contractAddress}-${config.account}-chunks`, + this.doc ); - this.chunkMap = new Map(); - this.account = account; - this.contractAddress = contractAddress; } destroy(): void { - // no-op; we don't actually destroy the instance, we leave the db connection open in case we need it in the future - } - - /** - * NOTE! if you're creating a new object store, it will not be *added* to existing dark forest - * accounts. This creation code runs once per account. Therefore, if you're adding a new object - * store, and need to test it out, you must either clear the indexed db databse for this account, - * or create a brand new account. - */ - static async create({ - account, - contractAddress, - }: Omit): Promise { - const db = await openDB(`darkforest-${contractAddress}-${account}`, 1, { - upgrade(db) { - db.createObjectStore(ObjectStore.DEFAULT); - db.createObjectStore(ObjectStore.BOARD); - db.createObjectStore(ObjectStore.UNCONFIRMED_ETH_TXS); - db.createObjectStore(ObjectStore.PLUGINS); - db.createObjectStore(ObjectStore.MODAL_POS); - }, - }); - - const localStorageManager = new PersistentChunkStore({ db, account, contractAddress }); - - await localStorageManager.loadChunks(); - - return localStorageManager; + this.doc.destroy(); } public setDiagnosticUpdater(diagnosticUpdater?: DiagnosticUpdater) { @@ -123,179 +34,10 @@ class PersistentChunkStore implements ChunkStore { } /** - * Important! This sets the key in indexed db per account and per contract. This means the same - * client can connect to multiple different dark forest contracts, with multiple different - * accounts, and the persistent storage will not overwrite data that is not relevant for the - * current configuration of the client. + * A function to await if you need to be sure all chunks have been loaded from indexeddb */ - private async getKey( - key: string, - objStore: ObjectStore = ObjectStore.DEFAULT - ): Promise { - return await this.db.get(objStore, `${this.contractAddress}-${this.account}-${key}`); - } - - /** - * Important! This sets the key in indexed db per account and per contract. This means the same - * client can connect to multiple different dark forest contracts, with multiple different - * accounts, and the persistent storage will not overwrite data that is not relevant for the - * current configuration of the client. - */ - private async setKey( - key: string, - value: string, - objStore: ObjectStore = ObjectStore.DEFAULT - ): Promise { - await this.db.put(objStore, value, `${this.contractAddress}-${this.account}-${key}`); - } - - private async removeKey(key: string, objStore: ObjectStore = ObjectStore.DEFAULT): Promise { - await this.db.delete(objStore, `${this.contractAddress}-${this.account}-${key}`); - } - - private async bulkSetKeyInCollection( - updateChunkTxs: DBTx[], - collection: ObjectStore - ): Promise { - const tx = this.db.transaction(collection, 'readwrite'); - updateChunkTxs.forEach((updateChunkTx) => { - updateChunkTx.forEach(({ type, dbKey: key, dbValue: value }) => { - if (type === DBActionType.UPDATE) { - tx.store.put(toPersistedChunk(value as Chunk), key); - } else if (type === DBActionType.DELETE) { - tx.store.delete(key); - } - }); - }); - await tx.done; - } - - /** - * This function loads all chunks persisted in the user's storage into the game. - */ - private async loadChunks(): Promise { - // we can't bulk get all chunks, since idb will crash/hang - // we also can't assign random non-primary keys and query on ranges - // so we append a random alphanumeric character to the front of keys - // and then bulk query for keys starting with 0, then 1, then 2, etc. - // see the `getBucket` function in `ChunkUtils.ts` for more information. - const borders = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ~'; - let chunkCount = 0; - - for (let idx = 0; idx < borders.length - 1; idx += 1) { - const bucketOfChunks = await this.db.getAll( - ObjectStore.BOARD, - IDBKeyRange.bound(borders[idx], borders[idx + 1], false, true) - ); - - bucketOfChunks.forEach((chunk: PersistedChunk) => { - this.addChunk(toExploredChunk(chunk), false); - }); - - chunkCount += bucketOfChunks.length; - } - - console.log(`loaded ${chunkCount} chunks from local storage`); - } - - /** - * Rather than saving a chunk immediately after it's mined, we queue up new chunks, and - * periodically save them. This function gets all of the queued new chunks, and persists them to - * indexed db. - */ - private async persistQueuedChunks() { - const toSave = [...this.queuedChunkWrites]; // make a copy - this.queuedChunkWrites = []; - this.diagnosticUpdater && - this.diagnosticUpdater.updateDiagnostics((d) => { - d.chunkUpdates = 0; - }); - await this.bulkSetKeyInCollection(toSave, ObjectStore.BOARD); - } - - /** - * we keep a list rather than a single location, since client/contract can - * often go out of sync on initialization - if client thinks that init - * failed but is wrong, it will prompt user to initialize with new home coords, - * which bricks the user's account. - */ - public async getHomeLocations(): Promise { - const homeLocations = await this.getKey('homeLocations'); - let parsed: WorldLocation[] = []; - if (homeLocations) { - parsed = JSON.parse(homeLocations) as WorldLocation[]; - } - - return parsed; - } - - public async addHomeLocation(location: WorldLocation): Promise { - let locationList = await this.getHomeLocations(); - if (locationList) { - locationList.push(location); - } else { - locationList = [location]; - } - locationList = Array.from(new Set(locationList)); - await this.setKey('homeLocations', stringify(locationList)); - } - - public async confirmHomeLocation(location: WorldLocation): Promise { - await this.setKey('homeLocations', stringify([location])); - } - - public async getSavedTouchedPlanetIds(): Promise { - const touchedPlanetIds = await this.getKey('touchedPlanetIds'); - - if (touchedPlanetIds) { - const parsed = JSON.parse(touchedPlanetIds) as LocationId[]; - return parsed; - } - - return []; - } - - public async getSavedRevealedCoords(): Promise { - const revealedPlanetIds = await this.getKey('revealedPlanetIds'); - - if (revealedPlanetIds) { - const parsed = JSON.parse(revealedPlanetIds); - // changed the type on 6/1/21 to include revealer field - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (parsed.length === 0 || !(parsed[0] as any).revealer) { - return []; - } - return parsed as RevealedCoords[]; - } - - return []; - } - public async getSavedClaimedCoords(): Promise { - const claimedPlanetIds = await this.getKey('claimedPlanetIds'); - - if (claimedPlanetIds) { - const parsed = JSON.parse(claimedPlanetIds); - // changed the type on 6/1/21 to include revealer field - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (parsed.length === 0 || !(parsed[0] as any).revealer) { - return []; - } - return parsed as ClaimedCoords[]; - } - - return []; - } - - public async saveTouchedPlanetIds(ids: LocationId[]) { - await this.setKey('touchedPlanetIds', stringify(ids)); - } - - public async saveRevealedCoords(revealedCoordTups: RevealedCoords[]) { - await this.setKey('revealedPlanetIds', stringify(revealedCoordTups)); - } - - public async saveClaimedCoords(claimedCoordTupps: ClaimedCoords[]) { - await this.setKey('claimedPlanetIds', stringify(claimedCoordTupps)); + async chunksLoaded(): Promise { + await this.db.whenSynced; } /** @@ -309,7 +51,7 @@ class PersistentChunkStore implements ChunkStore { while (sideLength <= MAX_CHUNK_SIZE) { const testChunkLoc = getChunkOfSideLengthContainingPoint(chunkLoc.bottomLeft, sideLength); - const chunk = this.getChunkById(getChunkKey(testChunkLoc)); + const chunk = this.chunkMap.get(getChunkKey(testChunkLoc)); if (chunk) { return chunk; } @@ -323,10 +65,6 @@ class PersistentChunkStore implements ChunkStore { return !!this.getChunkByFootprint(chunkLoc); } - private getChunkById(chunkId: ChunkId): Chunk | undefined { - return this.chunkMap.get(chunkId); - } - /** * When a chunk is mined, or a chunk is imported via map import, or a chunk is loaded from * persistent storage for the first time, we need to add this chunk to the game. This function @@ -334,175 +72,31 @@ class PersistentChunkStore implements ChunkStore { * might not want to persist the chunk is if you are sure that you got it from persistent storage. * i.e. it already exists in persistent storage. */ - public addChunk(chunk: Chunk, persistChunk = true): void { + public addChunk(chunk: Chunk): void { if (this.hasMinedChunk(chunk.chunkFootprint)) { return; } - const tx: DBAction[] = []; - - if (persistChunk) { - const minedSubChunks = this.getMinedSubChunks(chunk); - for (const subChunk of minedSubChunks) { - tx.push({ - type: DBActionType.DELETE, - dbKey: getChunkKey(subChunk.chunkFootprint), - }); - } - } - - addToChunkMap( - this.chunkMap, - chunk, - (chunk) => { - tx.push({ - type: DBActionType.UPDATE, - dbKey: getChunkKey(chunk.chunkFootprint), - dbValue: chunk, - }); - }, - (chunk) => { - tx.push({ - type: DBActionType.DELETE, - dbKey: getChunkKey(chunk.chunkFootprint), - }); - }, - MAX_CHUNK_SIZE - ); - - // modify in-memory store - for (const action of tx) { - if (action.type === DBActionType.UPDATE && action.dbValue) { - this.chunkMap.set(action.dbKey, action.dbValue); - } else if (action.type === DBActionType.DELETE) { - this.chunkMap.delete(action.dbKey); - } - } + this.doc.transact(() => { + processChunkInMap(this.chunkMap, chunk, this.onAdd, this.onRemove, MAX_CHUNK_SIZE); + }); this.diagnosticUpdater?.updateDiagnostics((d) => { d.totalChunks = this.chunkMap.size; }); - - // can stop here, if we're just loading into in-memory store from storage - if (!persistChunk) { - return; - } - - this.queuedChunkWrites.push(tx); - - this.diagnosticUpdater && - this.diagnosticUpdater.updateDiagnostics((d) => { - d.chunkUpdates = this.queuedChunkWrites.length; - }); - - // save chunks every 5s if we're just starting up, or 30s once we're moving - this.recomputeSaveThrottleAfterUpdate(); - this.throttledSaveChunkCacheToDisk(); } - /** - * Returns all the mined chunks with smaller sidelength strictly contained in the chunk. - * - * TODO: move this into ChunkUtils, and also make use of it, the way that it is currently used, in - * the function named `addToChunkMap`. - */ - private getMinedSubChunks(chunk: Chunk): Chunk[] { - const ret: Chunk[] = []; - for ( - let clearingSideLen = 16; - clearingSideLen < chunk.chunkFootprint.sideLength; - clearingSideLen *= 2 - ) { - for (let x = 0; x < chunk.chunkFootprint.sideLength; x += clearingSideLen) { - for (let y = 0; y < chunk.chunkFootprint.sideLength; y += clearingSideLen) { - const queryChunk: Rectangle = { - bottomLeft: { - x: chunk.chunkFootprint.bottomLeft.x + x, - y: chunk.chunkFootprint.bottomLeft.y + y, - }, - sideLength: clearingSideLen, - }; - const queryChunkKey = getChunkKey(queryChunk); - const exploredChunk = this.getChunkById(queryChunkKey); - if (exploredChunk) { - ret.push(exploredChunk); - } - } - } - } - return ret; - } + private onAdd = (chunk: Chunk) => { + this.chunkMap.set(getChunkKey(chunk.chunkFootprint), chunk); + }; - private recomputeSaveThrottleAfterUpdate() { - this.nUpdatesLastTwoMins += 1; - if (this.nUpdatesLastTwoMins === 50) { - this.throttledSaveChunkCacheToDisk.cancel(); - this.throttledSaveChunkCacheToDisk = _.throttle(this.persistQueuedChunks, 30000); - } - setTimeout(() => { - this.nUpdatesLastTwoMins -= 1; - if (this.nUpdatesLastTwoMins === 49) { - this.throttledSaveChunkCacheToDisk.cancel(); - this.throttledSaveChunkCacheToDisk = _.throttle(this.persistQueuedChunks, 5000); - } - }, 120000); - } + private onRemove = (chunk: Chunk) => { + this.chunkMap.delete(getChunkKey(chunk.chunkFootprint)); + }; public allChunks(): Iterable { return this.chunkMap.values(); } - - /** - * Whenever a transaction is submitted, it is persisted. When the transaction either fails or - * succeeds, it is un-persisted. The reason we persist submitted transactions is to be able to - * wait for them upon a fresh start of the game if you close the game before a transaction - * confirms. - */ - public async onEthTxSubmit(tx: Transaction): Promise { - // in case the tx was mined and saved already - if (!tx.hash || this.confirmedTxHashes.has(tx.hash)) return; - const ser: PersistedTransaction = { hash: tx.hash, intent: tx.intent }; - await this.db.put(ObjectStore.UNCONFIRMED_ETH_TXS, JSON.parse(JSON.stringify(ser)), tx.hash); - } - - /** - * Partner function to {@link PersistentChunkStore#onEthTxSubmit} - */ - public async onEthTxComplete(txHash: string): Promise { - this.confirmedTxHashes.add(txHash); - await this.db.delete(ObjectStore.UNCONFIRMED_ETH_TXS, txHash); - } - - public async getUnconfirmedSubmittedEthTxs(): Promise { - const ret: PersistedTransaction[] = await this.db.getAll(ObjectStore.UNCONFIRMED_ETH_TXS); - return ret; - } - - public async loadPlugins(): Promise { - const savedPlugins = await this.getKey('plugins', ObjectStore.PLUGINS); - - if (!savedPlugins) { - return []; - } - - return JSON.parse(savedPlugins) as SerializedPlugin[]; - } - - public async savePlugins(plugins: SerializedPlugin[]): Promise { - await this.setKey('plugins', JSON.stringify(plugins), ObjectStore.PLUGINS); - } - - public async saveModalPositions(modalPositions: Map): Promise { - if (!this.db.objectStoreNames.contains(ObjectStore.MODAL_POS)) return; - const serialized = JSON.stringify(Array.from(modalPositions.entries())); - await this.setKey(MODAL_POSITIONS_KEY, serialized, ObjectStore.MODAL_POS); - } - - public async loadModalPositions(): Promise> { - if (!this.db.objectStoreNames.contains(ObjectStore.MODAL_POS)) return new Map(); - const winPos = await this.getKey(MODAL_POSITIONS_KEY, ObjectStore.MODAL_POS); - return new Map(winPos ? JSON.parse(winPos) : null); - } } export default PersistentChunkStore; diff --git a/client/src/Backend/Storage/ReaderDataStore.ts b/client/src/Backend/Storage/ReaderDataStore.ts index e2e44e1c..8d1820d2 100644 --- a/client/src/Backend/Storage/ReaderDataStore.ts +++ b/client/src/Backend/Storage/ReaderDataStore.ts @@ -75,7 +75,7 @@ class ReaderDataStore { const addressTwitterMap = await getAllTwitters(); const contractConstants = await contractsAPI.getConstants(); const persistentChunkStore = - viewer && (await PersistentChunkStore.create({ account: viewer, contractAddress })); + viewer && new PersistentChunkStore({ account: viewer, contractAddress }); const singlePlanetStore = new ReaderDataStore({ contractAddress, @@ -99,7 +99,7 @@ class ReaderDataStore { } } - private setPlanetLocationIfKnown(planet: Planet): void { + private async setPlanetLocationIfKnown(planet: Planet): Promise { let planetLocation = undefined; if (planet && isLocatable(planet)) { @@ -111,6 +111,7 @@ class ReaderDataStore { } if (this.persistentChunkStore) { + await this.persistentChunkStore.chunksLoaded(); for (const chunk of this.persistentChunkStore.allChunks()) { for (const loc of chunk.planetLocations) { if (loc.hash === planet.locationId) { @@ -147,7 +148,7 @@ class ReaderDataStore { } updatePlanetToTime(planet, [], Date.now(), contractConstants); - this.setPlanetLocationIfKnown(planet); + await this.setPlanetLocationIfKnown(planet); return planet; } diff --git a/client/src/Frontend/Game/ModalManager.ts b/client/src/Frontend/Game/ModalManager.ts index 4e1c9317..6e46f4da 100644 --- a/client/src/Frontend/Game/ModalManager.ts +++ b/client/src/Frontend/Game/ModalManager.ts @@ -1,34 +1,31 @@ import { monomitter, Monomitter } from '@dfdao/events'; import { CursorState, ModalId, ModalManagerEvent, ModalPosition, WorldCoords } from '@dfdao/types'; import { EventEmitter } from 'events'; -import type PersistentChunkStore from '../../Backend/Storage/PersistentChunkStore'; +import OtherStore from '../../Backend/Storage/OtherStore'; class ModalManager extends EventEmitter { static instance: ModalManager; private lastIndex: number; private cursorState: CursorState; - private persistentChunkStore: PersistentChunkStore; + private store: OtherStore; private modalPositions: Map; public modalPositions$: Monomitter>; public readonly activeModalId$: Monomitter; public readonly modalPositionChanged$: Monomitter; - private constructor( - persistentChunkStore: PersistentChunkStore, - modalPositions: Map - ) { + private constructor(store: OtherStore, modalPositions: Map) { super(); this.lastIndex = 0; this.activeModalId$ = monomitter(true); this.modalPositionChanged$ = monomitter(); - this.persistentChunkStore = persistentChunkStore; + this.store = store; this.modalPositions = modalPositions; } - public static async create(persistentChunkStore: PersistentChunkStore): Promise { - const modalPositions = await persistentChunkStore.loadModalPositions(); - return new ModalManager(persistentChunkStore, modalPositions); + public static async create(store: OtherStore): Promise { + const modalPositions = await store.loadModalPositions(); + return new ModalManager(store, modalPositions); } public getIndex(): number { @@ -66,13 +63,13 @@ class ModalManager extends EventEmitter { public clearModalPosition(modalId: ModalId): void { this.modalPositions.delete(modalId); - this.persistentChunkStore.saveModalPositions(this.modalPositions); + this.store.saveModalPositions(this.modalPositions); this.modalPositionChanged$.publish(modalId); } public setModalPosition(modalId: ModalId, pos: ModalPosition): void { this.modalPositions.set(modalId, pos); - this.persistentChunkStore.saveModalPositions(this.modalPositions); + this.store.saveModalPositions(this.modalPositions); this.modalPositionChanged$.publish(modalId); } diff --git a/client/src/_types/darkforest/api/ChunkStoreTypes.ts b/client/src/_types/darkforest/api/ChunkStoreTypes.ts deleted file mode 100644 index b0e93761..00000000 --- a/client/src/_types/darkforest/api/ChunkStoreTypes.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { Abstract, LocationId, Rectangle } from '@dfdao/types'; - -/** - * one of "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" - */ -export type BucketId = Abstract; - -/** - * Don't worry about the values here. Never base code off the values here. PLEASE. - */ -export type ChunkId = Abstract; - -/** - * Chunks represent map data in some rectangle. This type represents a chunk when it is at rest in - * IndexedDB. The reason for this type's existence is that we want to reduce the amount of data we - * store on the user's computer. Shorter names hopefully means less data. - */ -export interface PersistedChunk { - x: number; // left - y: number; // bottom - s: number; // side length - l: PersistedLocation[]; - p: number; // approximate avg perlin value. used for rendering -} - -/** - * A location is a point sample of the universe. This type represents that point sample at rest when - * it is stored in IndexedDB. - */ -export interface PersistedLocation { - x: number; - y: number; - h: LocationId; - p: number; // perlin - b: number; // biomebase perlin -} - -/** - * Abstract interface shared between different types of chunk stores. Currently we have one that - * writes to IndexedDB, and one that simply throws away the data. - */ -export interface ChunkStore { - hasMinedChunk: (chunkFootprint: Rectangle) => boolean; -} diff --git a/package-lock.json b/package-lock.json index 3a964e28..ce5bd949 100644 --- a/package-lock.json +++ b/package-lock.json @@ -97,7 +97,9 @@ "sortablejs": "^1.10.2", "styled-components": "^5.3.3", "ts-dedent": "^2.0.0", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "y-indexeddb": "^9.0.9", + "yjs": "^13.5.41" }, "devDependencies": { "@projectsophon/workspace": "^2.0.0", @@ -20691,6 +20693,15 @@ "ws": "*" } }, + "node_modules/isomorphic.js": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -21621,6 +21632,21 @@ "node": ">= 0.8.0" } }, + "node_modules/lib0": { + "version": "0.2.52", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.52.tgz", + "integrity": "sha512-CjxlM7UgICfN6b2OPALBXchIBiNk6jE+1g7JP8ha+dh1xKRDSYpH0WQl1+rMqCju49xUnwPG34v4CR5/rPOZhg==", + "dependencies": { + "isomorphic.js": "^0.2.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/libp2p-crypto": { "version": "0.16.4", "resolved": "https://registry.npmjs.org/libp2p-crypto/-/libp2p-crypto-0.16.4.tgz", @@ -31995,6 +32021,21 @@ "node": ">=0.4" } }, + "node_modules/y-indexeddb": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/y-indexeddb/-/y-indexeddb-9.0.9.tgz", + "integrity": "sha512-GcJbiJa2eD5hankj46Hea9z4hbDnDjvh1fT62E5SpZRsv8GcEemw34l1hwI2eknGcv5Ih9JfusT37JLx9q3LFg==", + "dependencies": { + "lib0": "^0.2.35" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "peerDependencies": { + "yjs": "^13.0.0" + } + }, "node_modules/y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", @@ -32126,6 +32167,18 @@ "fd-slicer": "~1.1.0" } }, + "node_modules/yjs": { + "version": "13.5.41", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.5.41.tgz", + "integrity": "sha512-4eSTrrs8OeI0heXKKioRY4ag7V5Bk85Z4MeniUyown3o3y0G7G4JpAZWrZWfTp7pzw2b53GkAQWKqHsHi9j9JA==", + "dependencies": { + "lib0": "^0.2.49" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -40607,7 +40660,9 @@ "typescript": "4.7.x", "uuid": "^8.3.2", "vite": "^3.1.3", - "vitest": "^0.23.4" + "vitest": "^0.23.4", + "y-indexeddb": "^9.0.9", + "yjs": "^13.5.41" }, "dependencies": { "@rollup/plugin-commonjs": { @@ -47687,6 +47742,11 @@ "dev": true, "requires": {} }, + "isomorphic.js": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==" + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -48390,6 +48450,14 @@ "type-check": "~0.3.2" } }, + "lib0": { + "version": "0.2.52", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.52.tgz", + "integrity": "sha512-CjxlM7UgICfN6b2OPALBXchIBiNk6jE+1g7JP8ha+dh1xKRDSYpH0WQl1+rMqCju49xUnwPG34v4CR5/rPOZhg==", + "requires": { + "isomorphic.js": "^0.2.4" + } + }, "libp2p-crypto": { "version": "0.16.4", "resolved": "https://registry.npmjs.org/libp2p-crypto/-/libp2p-crypto-0.16.4.tgz", @@ -55991,6 +56059,14 @@ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true }, + "y-indexeddb": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/y-indexeddb/-/y-indexeddb-9.0.9.tgz", + "integrity": "sha512-GcJbiJa2eD5hankj46Hea9z4hbDnDjvh1fT62E5SpZRsv8GcEemw34l1hwI2eknGcv5Ih9JfusT37JLx9q3LFg==", + "requires": { + "lib0": "^0.2.35" + } + }, "y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", @@ -56080,6 +56156,14 @@ "fd-slicer": "~1.1.0" } }, + "yjs": { + "version": "13.5.41", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.5.41.tgz", + "integrity": "sha512-4eSTrrs8OeI0heXKKioRY4ag7V5Bk85Z4MeniUyown3o3y0G7G4JpAZWrZWfTp7pzw2b53GkAQWKqHsHi9j9JA==", + "requires": { + "lib0": "^0.2.49" + } + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", From 403e18c610095ebe1c3b75d6b701be83f3915057 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 3 Oct 2022 13:31:06 -0700 Subject: [PATCH 52/55] Store artifact information on contract planets (#28) * Store artifact information on each planet * Remove artifact task, as it does not make sense anymore * Comment out debug tasks until we have token APIs settled * Add ID logging to the prettyPrintToken util * fix: add extra timeout Co-authored-by: cha0sg0d <0xcha0sg0d@gmail.com> --- eth/contracts/DFTypes.sol | 6 + eth/contracts/facets/DFGetterFacet.sol | 24 +- eth/contracts/facets/DFMoveFacet.sol | 10 +- eth/contracts/libraries/LibArtifact.sol | 24 +- eth/contracts/libraries/LibArtifactUtils.sol | 19 +- eth/contracts/libraries/LibPlanet.sol | 6 + eth/contracts/libraries/LibSpaceship.sol | 14 +- eth/contracts/libraries/LibStorage.sol | 8 - eth/hardhat.config.ts | 1 - eth/tasks/artifact.ts | 18 -- eth/tasks/debug.ts | 218 +++++++++---------- eth/test/DFArtifacts.test.ts | 2 + eth/test/utils/TestUtils.ts | 2 +- 13 files changed, 170 insertions(+), 182 deletions(-) delete mode 100644 eth/tasks/artifact.ts diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 1dafbce3..198648a5 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -71,6 +71,12 @@ struct Planet { uint256 invadeStartBlock; address capturer; uint256 locationId; + // Token stuff + uint256[] artifacts; + uint256[] spaceships; + uint256 activeArtifact; + uint256 wormholeTo; + uint256 artifactActivationTime; } struct RevealedCoords { diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 9ac4419a..6c545777 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -91,12 +91,12 @@ contract DFGetterFacet is WithStorage { return gs().planetArrivals[key]; } - function planetArtifacts(uint256 key) public view returns (uint256[] memory) { - return gs().planetArtifacts[key]; + function planetArtifacts(uint256 locationId) public view returns (uint256[] memory) { + return gs().planets[locationId].artifacts; } - function planetSpaceships(uint256 key) public view returns (uint256[] memory) { - return gs().planetSpaceships[key]; + function planetSpaceships(uint256 locationId) public view returns (uint256[] memory) { + return gs().planets[locationId].spaceships; } // ADDITIONAL UTILITY GETTERS @@ -315,7 +315,7 @@ contract DFGetterFacet is WithStorage { } function getArtifactActivationTimeOnPlanet(uint256 locationId) public view returns (uint256) { - return gs().planetArtifactActivationTime[locationId]; + return gs().planets[locationId].artifactActivationTime; } // function getArtifactById(uint256 artifactId) @@ -345,8 +345,12 @@ contract DFGetterFacet is WithStorage { // }); // } + function getUpgradeForArtifact(uint256 artifactId) public pure returns (Upgrade memory) { + return LibArtifact.getUpgradeForArtifact(LibArtifact.decode(artifactId)); + } + function getArtifactsOnPlanet(uint256 locationId) public view returns (Artifact[] memory ret) { - uint256[] memory artifactIds = gs().planetArtifacts[locationId]; + uint256[] memory artifactIds = gs().planets[locationId].artifacts; ret = new Artifact[](artifactIds.length); for (uint256 i = 0; i < artifactIds.length; i++) { ret[i] = LibArtifact.decode(artifactIds[i]); @@ -359,7 +363,7 @@ contract DFGetterFacet is WithStorage { view returns (Spaceship[] memory ret) { - uint256[] memory tokenIds = gs().planetSpaceships[locationId]; + uint256[] memory tokenIds = gs().planets[locationId].spaceships; ret = new Spaceship[](tokenIds.length); for (uint256 i = 0; i < tokenIds.length; i++) { ret[i] = LibSpaceship.decode(tokenIds[i]); @@ -370,11 +374,11 @@ contract DFGetterFacet is WithStorage { // Combo on Ships and Artifacts function tokenExistsOnPlanet(uint256 locationId, uint256 tokenId) public view returns (bool) { bool hasToken = false; - uint256[] memory artifactIds = gs().planetArtifacts[locationId]; + uint256[] memory artifactIds = gs().planets[locationId].artifacts; for (uint256 i = 0; i < artifactIds.length; i++) { if (artifactIds[i] == tokenId) return true; } - uint256[] memory shipIds = gs().planetSpaceships[locationId]; + uint256[] memory shipIds = gs().planets[locationId].spaceships; for (uint256 i = 0; i < shipIds.length; i++) { if (shipIds[i] == tokenId) return true; } @@ -390,7 +394,7 @@ contract DFGetterFacet is WithStorage { view returns (Artifact memory ret) { - uint256 artifactId = gs().planetActiveArtifact[locationId]; + uint256 artifactId = gs().planets[locationId].activeArtifact; return LibArtifact.decode(artifactId); } diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 2168bbd1..443e3784 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -227,8 +227,8 @@ contract DFMoveFacet is WithStorage { if (args.movedArtifactId != 0) { require( - gs().planetArtifacts[args.newLoc].length + - gs().planetSpaceships[args.newLoc].length < + gs().planets[args.newLoc].artifacts.length + + gs().planets[args.newLoc].spaceships.length < 5, "too many tokens on this planet" ); @@ -296,7 +296,7 @@ contract DFMoveFacet is WithStorage { // If active artifact is a Wormhole and destination is newLoc if ( activeArtifactFrom.artifactType == ArtifactType.Wormhole && - gs().planetWormholes[args.oldLoc] == args.newLoc + gs().planets[args.oldLoc].wormholeTo == args.newLoc ) { wormholeRarity = activeArtifactFrom.rarity; wormholePresent = true; @@ -308,7 +308,7 @@ contract DFMoveFacet is WithStorage { // If active artifact is a Wormhole and destination is fromLoc if ( activeArtifactTo.artifactType == ArtifactType.Wormhole && - gs().planetWormholes[args.newLoc] == args.oldLoc + gs().planets[args.newLoc].wormholeTo == args.oldLoc ) { // Ensures higher rarity wormhole will be used. // TODO: Make sure client knows this. @@ -338,7 +338,7 @@ contract DFMoveFacet is WithStorage { Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); if ( activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && - block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= + block.timestamp - gs().planets[args.oldLoc].artifactActivationTime >= gameConstants().PHOTOID_ACTIVATION_DELAY ) { photoidPresent = true; diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index 60b392c8..c5c08f3c 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -218,7 +218,7 @@ library LibArtifact { // whether or not the artifact is activated. function isActivated(uint256 locationId, uint256 artifactId) internal view returns (bool) { - return (gs().planetActiveArtifact[locationId] == artifactId); + return (gs().planets[locationId].activeArtifact == artifactId); } function isArtifactOnPlanet(uint256 locationId, uint256 artifactId) @@ -226,8 +226,8 @@ library LibArtifact { view returns (bool) { - for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { + for (uint256 i; i < gs().planets[locationId].artifacts.length; i++) { + if (gs().planets[locationId].artifacts[i] == artifactId) { return true; } } @@ -290,25 +290,25 @@ library LibArtifact { } function putArtifactOnPlanet(uint256 locationId, uint256 artifactId) internal { - gs().planetArtifacts[locationId].push(artifactId); + gs().planets[locationId].artifacts.push(artifactId); } /** * Remove artifactId from planet with locationId if artifactId exists AND is not active. */ function takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) internal { - uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; + uint256 artifactsOnThisPlanet = gs().planets[locationId].artifacts.length; bool hadTheArtifact = false; for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { + if (gs().planets[locationId].artifacts[i] == artifactId) { require( !isActivated(locationId, artifactId), "you cannot take an activated artifact off a planet" ); - gs().planetArtifacts[locationId][i] = gs().planetArtifacts[locationId][ + gs().planets[locationId].artifacts[i] = gs().planets[locationId].artifacts[ artifactsOnThisPlanet - 1 ]; @@ -318,19 +318,19 @@ library LibArtifact { } require(hadTheArtifact, "this artifact was not present on this planet"); - gs().planetArtifacts[locationId].pop(); + gs().planets[locationId].artifacts.pop(); } // if the given planet has an activated artifact on it, then return the artifact // otherwise, return a 'null artifact' function getActiveArtifact(uint256 locationId) internal view returns (Artifact memory) { require(hasActiveArtifact(locationId), "planet does not have an active artifact"); - uint256 artifactId = gs().planetActiveArtifact[locationId]; + uint256 artifactId = gs().planets[locationId].activeArtifact; return LibArtifact.decode(artifactId); } function hasActiveArtifact(uint256 locationId) internal view returns (bool) { - uint256 artifactId = gs().planetActiveArtifact[locationId]; + uint256 artifactId = gs().planets[locationId].activeArtifact; return artifactId != 0; } @@ -342,8 +342,8 @@ library LibArtifact { returns (Artifact memory a) { bool found = false; - for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { + for (uint256 i; i < gs().planets[locationId].artifacts.length; i++) { + if (gs().planets[locationId].artifacts[i] == artifactId) { a = LibArtifact.decode(artifactId); found = true; return a; diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 70bafaab..22177b73 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -200,8 +200,8 @@ library LibArtifactUtils { bool shouldDeactivateAndBurn = false; - gs().planetArtifactActivationTime[locationId] = block.timestamp; - gs().planetActiveArtifact[locationId] = artifactId; + gs().planets[locationId].artifactActivationTime = block.timestamp; + gs().planets[locationId].activeArtifact = artifactId; emit ArtifactActivated(msg.sender, artifactId, locationId); if (artifact.artifactType == ArtifactType.Wormhole) { @@ -212,7 +212,7 @@ library LibArtifactUtils { "you can only create a wormhole to a planet you own" ); require(!gs().planets[wormholeTo].destroyed, "planet destroyed"); - gs().planetWormholes[locationId] = wormholeTo; + gs().planets[locationId].wormholeTo = wormholeTo; } else if (artifact.artifactType == ArtifactType.BloomFilter) { require( 2 * uint256(artifact.rarity) >= planet.planetLevel, @@ -231,7 +231,7 @@ library LibArtifactUtils { } if (shouldDeactivateAndBurn) { - gs().planetActiveArtifact[locationId] = 0; // immediately remove activate artifact + gs().planets[locationId].activeArtifact = 0; // immediately remove activate artifact emit ArtifactDeactivated(msg.sender, artifactId, locationId); // burn it after use. will be owned by contract but not on a planet anyone can control @@ -260,9 +260,9 @@ library LibArtifactUtils { Artifact memory artifact = LibArtifact.getActiveArtifact(locationId); // In case just pretend there is a wormhole. - gs().planetWormholes[locationId] = 0; - gs().planetActiveArtifact[locationId] = 0; - gs().planetArtifactActivationTime[locationId] = 0; + gs().planets[locationId].wormholeTo = 0; + gs().planets[locationId].activeArtifact = 0; + gs().planets[locationId].artifactActivationTime = 0; emit ArtifactDeactivated(msg.sender, artifact.id, locationId); @@ -298,7 +298,8 @@ library LibArtifactUtils { ); require( - gs().planetArtifacts[locationId].length + gs().planetSpaceships[locationId].length < 5, + gs().planets[locationId].artifacts.length + gs().planets[locationId].spaceships.length < + 5, "too many tokens on this planet" ); @@ -345,7 +346,7 @@ library LibArtifactUtils { } function containsGear(uint256 locationId) public view returns (bool) { - uint256[] memory tokenIds = gs().planetSpaceships[locationId]; + uint256[] memory tokenIds = gs().planets[locationId].spaceships; for (uint256 i = 0; i < tokenIds.length; i++) { Spaceship memory spaceship = LibSpaceship.decode(tokenIds[i]); if ( diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 679bf1ee..50fda7a7 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -162,6 +162,12 @@ library LibPlanet { _planet.planetLevel = defaultPlanet.planetLevel; _planet.planetType = defaultPlanet.planetType; + _planet.artifacts = defaultPlanet.artifacts; + _planet.spaceships = defaultPlanet.spaceships; + _planet.activeArtifact = defaultPlanet.activeArtifact; + _planet.wormholeTo = defaultPlanet.wormholeTo; + _planet.artifactActivationTime = defaultPlanet.artifactActivationTime; + _planet.isInitialized = true; _planet.perlin = args.perlin; _planet.spaceType = args.spaceType; diff --git a/eth/contracts/libraries/LibSpaceship.sol b/eth/contracts/libraries/LibSpaceship.sol index 3cf955c3..0cff4526 100644 --- a/eth/contracts/libraries/LibSpaceship.sol +++ b/eth/contracts/libraries/LibSpaceship.sol @@ -69,8 +69,8 @@ library LibSpaceship { } function isSpaceshipOnPlanet(uint256 locationId, uint256 shipId) internal view returns (bool) { - for (uint256 i; i < gs().planetSpaceships[locationId].length; i++) { - if (gs().planetSpaceships[locationId][i] == shipId) { + for (uint256 i; i < gs().planets[locationId].spaceships.length; i++) { + if (gs().planets[locationId].spaceships[i] == shipId) { return true; } } @@ -78,17 +78,17 @@ library LibSpaceship { } function putSpaceshipOnPlanet(uint256 locationId, uint256 spaceshipId) internal { - gs().planetSpaceships[locationId].push(spaceshipId); + gs().planets[locationId].spaceships.push(spaceshipId); } function takeSpaceshipOffPlanet(uint256 locationId, uint256 spaceshipId) internal { - uint256 shipsOnThisPlanet = gs().planetSpaceships[locationId].length; + uint256 shipsOnThisPlanet = gs().planets[locationId].spaceships.length; bool hadTheShip = false; for (uint256 i = 0; i < shipsOnThisPlanet; i++) { - if (gs().planetSpaceships[locationId][i] == spaceshipId) { - gs().planetSpaceships[locationId][i] = gs().planetSpaceships[locationId][ + if (gs().planets[locationId].spaceships[i] == spaceshipId) { + gs().planets[locationId].spaceships[i] = gs().planets[locationId].spaceships[ shipsOnThisPlanet - 1 ]; @@ -98,6 +98,6 @@ library LibSpaceship { } require(hadTheShip, "this ship was not present on this planet"); - gs().planetSpaceships[locationId].pop(); + gs().planets[locationId].spaceships.pop(); } } diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index 279f10bd..1c521033 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -43,14 +43,6 @@ struct GameStorage { mapping(uint256 => PlanetEventMetadata[]) planetEvents; // maps event id to arrival data mapping(uint256 => ArrivalData) planetArrivals; - // Token stuff - mapping(uint256 => uint256[]) planetArtifacts; - mapping(uint256 => uint256[]) planetSpaceships; - mapping(uint256 => uint256) planetActiveArtifact; - // wormhole from => to. planetWormHoles[from] = to; - mapping(uint256 => uint256) planetWormholes; - // planetId to timestamp. For all artifacts, but only used for photoids. - mapping(uint256 => uint256) planetArtifactActivationTime; // Capture Zones uint256 nextChangeBlock; } diff --git a/eth/hardhat.config.ts b/eth/hardhat.config.ts index fc7dee6e..fa307e48 100644 --- a/eth/hardhat.config.ts +++ b/eth/hardhat.config.ts @@ -27,7 +27,6 @@ import { Initializers, } from '@dfdao/settings'; import { workspace } from '@projectsophon/workspace'; -import './tasks/artifact'; import './tasks/circom'; import './tasks/debug'; import './tasks/deploy'; diff --git a/eth/tasks/artifact.ts b/eth/tasks/artifact.ts deleted file mode 100644 index fd74c6db..00000000 --- a/eth/tasks/artifact.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { task } from 'hardhat/config'; -import { HardhatRuntimeEnvironment } from 'hardhat/types'; - -task('artifact:read', 'Read Artifact data from Tokens contract').setAction(artifactsRead); - -async function artifactsRead({}, hre: HardhatRuntimeEnvironment) { - const contract = await hre.ethers.getContractAt( - 'DarkForest', - hre.settings.contracts.CONTRACT_ADDRESS - ); - - const id = await contract.tokenByIndex(0); - console.log(id.toString()); - const token = await contract.getArtifact(id); - console.log(token); - const URI = await contract.tokenURI(id); - console.log(URI); -} diff --git a/eth/tasks/debug.ts b/eth/tasks/debug.ts index 66ac78b1..1e1eed53 100644 --- a/eth/tasks/debug.ts +++ b/eth/tasks/debug.ts @@ -1,122 +1,118 @@ // these tasks are intended to simplify the debugging and development workflow by essentially giving -// you god-mode status, allowing you to change the state of the world however you want. - -import { task, types } from 'hardhat/config'; -import { HardhatRuntimeEnvironment } from 'hardhat/types'; // see DFTypes.sol - ArtifactType -const artifactOptions = ( - 'Monolith,Colossus,Spaceship,Pyramid,Wormhole,' + - 'PlanetaryShield,PhotoidCannon,BloomFilter,BlackDomain' -).split(','); +// const artifactOptions = ( +// 'Monolith,Colossus,Spaceship,Pyramid,Wormhole,' + +// 'PlanetaryShield,PhotoidCannon,BloomFilter,BlackDomain' +// ).split(','); -// npm run --workspace eth hardhat:dev -- debug:giveArtifact "0x27fd6eec1e1f3ce4a53b40d5813119d868f7b4e3" PhotoidCannon 5 -task('debug:giveArtifact', 'gives the player some amount of a particular type of artifact') - .addPositionalParam( - 'playerAddress', - 'the address of the player to give the artifacts', - undefined, - types.string - ) - .addPositionalParam( - 'artifactType', - 'one of: [Monolith, Colossus, Spaceship, Pyramid, Wormhole, ' + - 'PlanetaryShield, PhotoidCannon, BloomFilter, BlackDomain]', - undefined, - types.string - ) - .addPositionalParam('amount', 'the amount of this artifact to give', 1, types.int) - .addPositionalParam('rarity', 'the rarity of the artifact to give', 1, types.int) - .addPositionalParam('biome', 'the biome of the artifact to give', 1, types.int) - .addPositionalParam( - 'discoveredOn', - 'the planet ID (decimal string) this was discovered on', - '0', - types.string - ) - .setAction(giveArtifact); +// // npm run --workspace eth hardhat:dev -- debug:giveArtifact "0x27fd6eec1e1f3ce4a53b40d5813119d868f7b4e3" PhotoidCannon 5 +// task('debug:giveArtifact', 'gives the player some amount of a particular type of artifact') +// .addPositionalParam( +// 'playerAddress', +// 'the address of the player to give the artifacts', +// undefined, +// types.string +// ) +// .addPositionalParam( +// 'artifactType', +// 'one of: [Monolith, Colossus, Spaceship, Pyramid, Wormhole, ' + +// 'PlanetaryShield, PhotoidCannon, BloomFilter, BlackDomain]', +// undefined, +// types.string +// ) +// .addPositionalParam('amount', 'the amount of this artifact to give', 1, types.int) +// .addPositionalParam('rarity', 'the rarity of the artifact to give', 1, types.int) +// .addPositionalParam('biome', 'the biome of the artifact to give', 1, types.int) +// .addPositionalParam( +// 'discoveredOn', +// 'the planet ID (decimal string) this was discovered on', +// '0', +// types.string +// ) +// .setAction(giveArtifact); -async function giveArtifact( - { - playerAddress, - artifactType, - amount, - rarity, - biome, - discoveredOn, - }: { - playerAddress: string; - artifactType: string; - amount: number; - rarity: number; - biome: number; - discoveredOn: string; - }, - hre: HardhatRuntimeEnvironment -) { - const chosenArtifactType = artifactOptions.indexOf(artifactType) + 1; - const contract = await hre.ethers.getContractAt( - 'DarkForest', - hre.settings.contracts.CONTRACT_ADDRESS - ); +// async function giveArtifact( +// { +// playerAddress, +// artifactType, +// amount, +// rarity, +// biome, +// discoveredOn, +// }: { +// playerAddress: string; +// artifactType: string; +// amount: number; +// rarity: number; +// biome: number; +// discoveredOn: string; +// }, +// hre: HardhatRuntimeEnvironment +// ) { +// const chosenArtifactType = artifactOptions.indexOf(artifactType) + 1; +// const contract = await hre.ethers.getContractAt( +// 'DarkForest', +// hre.settings.contracts.CONTRACT_ADDRESS +// ); - for (let i = 0; i < amount; i++) { - // see contracts/types/ActionTypes.sol - CreateArtifactArgs - const createArtifactArgs = { - tokenId: random256Id(), - discoverer: playerAddress, - planetId: discoveredOn, - rarity: rarity, - biome: biome, - artifactType: chosenArtifactType, - owner: playerAddress, - controller: '0x0000000000000000000000000000000000000000', - }; +// for (let i = 0; i < amount; i++) { +// // see contracts/types/ActionTypes.sol - CreateArtifactArgs +// const createArtifactArgs = { +// tokenId: random256Id(), +// discoverer: playerAddress, +// planetId: discoveredOn, +// rarity: rarity, +// biome: biome, +// artifactType: chosenArtifactType, +// owner: playerAddress, +// controller: '0x0000000000000000000000000000000000000000', +// }; - await (await contract.createArtifact(createArtifactArgs)).wait(); - } -} +// await (await contract.createArtifact(createArtifactArgs)).wait(); +// } +// } // npm run --workspace eth hardhat:dev -- debug:giveOneOfEachArtifact "0x5bcf0ac4c057dcaf9b23e4dd7cb7b035a71dd0dc" 10 -task( - 'debug:giveOneOfEachArtifact', - 'gives the player one of each type of artifact, one of each rarity' -) - .addPositionalParam( - 'playerAddress', - 'the address of the player to give the artifacts', - undefined, - types.string - ) - .addPositionalParam('biome', 'the biome of the artifacts to give', 1, types.int) - .setAction(giveOneOfEachArtifact); +// task( +// 'debug:giveOneOfEachArtifact', +// 'gives the player one of each type of artifact, one of each rarity' +// ) +// .addPositionalParam( +// 'playerAddress', +// 'the address of the player to give the artifacts', +// undefined, +// types.string +// ) +// .addPositionalParam('biome', 'the biome of the artifacts to give', 1, types.int) +// .setAction(giveOneOfEachArtifact); -async function giveOneOfEachArtifact( - { playerAddress, biome }: { playerAddress: string; biome: number }, - hre: HardhatRuntimeEnvironment -) { - for (const artifact of artifactOptions) { - for (let i = 1; i < 6; i++) { - await giveArtifact( - { - playerAddress, - artifactType: artifact, - amount: 1, - rarity: i, - biome, - discoveredOn: '0', - }, - hre - ); - } - } -} +// async function giveOneOfEachArtifact( +// { playerAddress, biome }: { playerAddress: string; biome: number }, +// hre: HardhatRuntimeEnvironment +// ) { +// for (const artifact of artifactOptions) { +// for (let i = 1; i < 6; i++) { +// await giveArtifact( +// { +// playerAddress, +// artifactType: artifact, +// amount: 1, +// rarity: i, +// biome, +// discoveredOn: '0', +// }, +// hre +// ); +// } +// } +// } -function random256Id() { - const alphabet = '0123456789ABCDEF'.split(''); - let result = '0x'; - for (let i = 0; i < 256 / 4; i++) { - result += alphabet[Math.floor(Math.random() * alphabet.length)]; - } - return result; -} +// function random256Id() { +// const alphabet = '0123456789ABCDEF'.split(''); +// let result = '0x'; +// for (let i = 0; i < 256 / 4; i++) { +// result += alphabet[Math.floor(Math.random() * alphabet.length)]; +// } +// return result; +// } diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index 00664951..290f9745 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -211,6 +211,8 @@ describe('DarkForestArtifacts', function () { ); const moveReceipt = await moveTx.wait(); const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued + await increaseBlockchainTime(); + await world.contract.refreshPlanet(ARTIFACT_PLANET_1.id); const oldLocArtifacts = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); expect(oldLocArtifacts.length).to.equal(0); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index d37932a6..f9bd0c6b 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -47,7 +47,7 @@ export function hexToBigNumber(hex: string): BigNumber { export function prettyPrintToken(token: ArtifactStructOutput) { console.log( - `~Token~\nCollection: ${TokenTypeNames[token.tokenType]}\nRarity: ${ + `~Token~\nID: ${token.id}\nCollection: ${TokenTypeNames[token.tokenType]}\nRarity: ${ ArtifactRarityNames[token.rarity] }\nType: ${ArtifactTypeNames[token.artifactType]}\nBiome: ${BiomeNames[token.planetBiome]}` ); From f69383452513e1427b6b368e0cc158e56e8a7828 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 19:33:22 +0100 Subject: [PATCH 53/55] first draft at simple artifacts --- eth/contracts/facets/DFAdminFacet.sol | 32 +++ eth/contracts/facets/DFArtifactFacet.sol | 22 +- eth/contracts/libraries/LibArtifact.sol | 1 + eth/contracts/libraries/LibArtifactUtils.sol | 44 ++-- eth/test/DFArtifacts.test.ts | 246 ++++++------------- 5 files changed, 132 insertions(+), 213 deletions(-) diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 7a04df0c..f34a052b 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -17,6 +17,7 @@ import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Type imports import {Artifact, SpaceType, Spaceship, SpaceshipType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet, TokenType} from "../DFTypes.sol"; +import "hardhat/console.sol"; contract DFAdminFacet is WithStorage { event AdminOwnershipChanged(uint256 loc, address newOwner); @@ -24,6 +25,7 @@ contract DFAdminFacet is WithStorage { event AdminGiveSpaceship(uint256 loc, address owner, SpaceshipType shipType); event PauseStateChanged(bool paused); event AdminArtifactCreated(address player, uint256 artifactId, uint256 loc); + event AdminArtifactActivated(address player, uint256 artifactId, uint256 loc); ///////////////////////////// /// Administrative Engine /// @@ -180,4 +182,34 @@ contract DFAdminFacet is WithStorage { if (args.planetId != 0) LibArtifact.putArtifactOnPlanet(args.planetId, artifact.id); emit AdminArtifactCreated(args.owner, artifact.id, args.planetId); } + + // function adminGiveAndActivateArtifact(DFTCreateArtifactArgs memory args, uint256 wormholeTo) + // public + // onlyAdmin + // { + // // Note: calling this in tests should supply Diamond address as args.owner + // uint256 tokenId = LibArtifact.create(args.rarity, args.artifactType, args.biome); + + // Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact( + // tokenId, + // args.owner + // ); + + // console.log("owner", args.owner); + // // Don't put artifact on planet if no planetId given. + // console.log("wormholeto,", wormholeTo); + // console.log("planetId,", wormholeTo); + // console.log("planet is intialized?", gs().planets[args.planetId].isInitialized); + // if (args.planetId != 0) + // // address(this).delegatecall(abi.encodeWithSignature("transferOwnership(address)", address(0))); + // address(this).delegatecall( + // abi.encodeWithSignature( + // "activateArtifact(uint256, uint256, uint256)", + // args.planetId, + // artifact.id, + // wormholeTo + // ) + // ); + // emit AdminArtifactActivated(args.owner, artifact.id, args.planetId); + // } } diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 5f7b1bdd..4c5cf10e 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -20,6 +20,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtifactArgs, DFPFindArtifactArgs, Spaceship, SpaceshipType} from "../DFTypes.sol"; +import "hardhat/console.sol"; contract DFArtifactFacet is WithStorage { @@ -162,27 +163,6 @@ contract DFArtifactFacet is WithStorage { emit ArtifactFound(msg.sender, foundArtifactId, planetId); } - function depositArtifact(uint256 locationId, uint256 artifactId) public notPaused { - // should this be implemented as logic that is triggered when a player sends - // an artifact to the contract with locationId in the extra data? - // might be better use of the ERC721 standard - can use safeTransfer then - LibPlanet.refreshPlanet(locationId); - - LibArtifactUtils.depositArtifact(locationId, artifactId, address(this)); - - emit ArtifactDeposited(msg.sender, artifactId, locationId); - } - - // withdraws the given artifact from the given planet. you must own the planet, - // the artifact must be on the given planet - function withdrawArtifact(uint256 locationId, uint256 artifactId) public notPaused { - LibPlanet.refreshPlanet(locationId); - - LibArtifactUtils.withdrawArtifact(locationId, artifactId); - - emit ArtifactWithdrawn(msg.sender, artifactId, locationId); - } - // activates the given artifact on the given planet. the artifact must have // been previously deposited on this planet. the artifact cannot be activated // within a certain cooldown period, depending on the artifact type diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index c5c08f3c..d0f68736 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -13,6 +13,7 @@ import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStora // Type imports import {Artifact, ArtifactInfo, ArtifactRarity, ArtifactType, Biome, SpaceType, TokenType, Upgrade} from "../DFTypes.sol"; +import "hardhat/console.sol"; library LibArtifact { function gs() internal pure returns (GameStorage storage) { diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 22177b73..9d2b16b1 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -16,6 +16,7 @@ import {LibStorage, GameStorage, GameConstants} from "./LibStorage.sol"; // Type imports import {Biome, Planet, PlanetType, ArtifactType, ArtifactRarity, TokenType, DFPFindArtifactArgs, DFTCreateArtifactArgs, Artifact, ArtifactInfo, Spaceship, SpaceshipType} from "../DFTypes.sol"; +import "hardhat/console.sol"; library LibArtifactUtils { function gs() internal pure returns (GameStorage storage) { @@ -97,12 +98,14 @@ library LibArtifactUtils { ); uint256 tokenId = LibArtifact.create(rarity, artifactType, biome); + // Artifacts found in game are owned by player. Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( tokenId, - args.coreAddress + msg.sender ); - LibArtifact.putArtifactOnPlanet(args.planetId, foundArtifact.id); + // Artifacts are not put on planet. + // LibArtifact.putArtifactOnPlanet(args.planetId, foundArtifact.id); planet.hasTriedFindingArtifact = true; gs().players[msg.sender].score += gameConstants().ARTIFACT_POINT_VALUES[ @@ -118,20 +121,19 @@ library LibArtifactUtils { uint256 wormholeTo ) public { Planet storage planet = gs().planets[locationId]; - + require( + gs().planets[locationId].spaceships.length + gs().planets[locationId].artifacts.length < + 5, + "too many tokens on this planet" + ); + require( + DFArtifactFacet(address(this)).tokenExists(msg.sender, artifactId), + "you can only activate artifacts you own" + ); if (LibSpaceship.isShip(artifactId)) { - require( - LibSpaceship.isSpaceshipOnPlanet(locationId, artifactId), - "can't activate a ship on a planet it's not on" - ); activateSpaceshipArtifact(locationId, artifactId, planet); } else if (LibArtifact.isArtifact(artifactId)) { Artifact memory artifact = LibArtifact.decode(artifactId); - - require( - LibArtifact.isArtifactOnPlanet(locationId, artifactId), - "can't activate an artifact on a planet it's not on" - ); activateNonSpaceshipArtifact(locationId, artifactId, wormholeTo, planet, artifact); } else { require(false, "token cannot be activated"); @@ -193,11 +195,6 @@ library LibArtifactUtils { ); require(!planet.destroyed, "planet is destroyed"); - require( - LibArtifact.getPlanetArtifact(locationId, artifactId).tokenType != TokenType.Unknown, - "this artifact is not on this planet" - ); - bool shouldDeactivateAndBurn = false; gs().planets[locationId].artifactActivationTime = block.timestamp; @@ -234,12 +231,22 @@ library LibArtifactUtils { gs().planets[locationId].activeArtifact = 0; // immediately remove activate artifact emit ArtifactDeactivated(msg.sender, artifactId, locationId); + LibGameUtils._buffPlanet(locationId, LibArtifact.getUpgradeForArtifact(artifact)); + return; // burn it after use. will be owned by contract but not on a planet anyone can control - LibArtifact.takeArtifactOffPlanet(locationId, artifactId); + // No need to take off, Artifact will never get placed. + // LibArtifact.takeArtifactOffPlanet(locationId, artifactId); } // this is fine even tho some artifacts are immediately deactivated, because // those artifacts do not buff the planet. + LibArtifact.putArtifactOnPlanet(locationId, artifactId); LibGameUtils._buffPlanet(locationId, LibArtifact.getUpgradeForArtifact(artifact)); + // Also transfer to contract + DFArtifactFacet(address(this)).transferArtifact( + artifactId, + msg.sender, + gs().diamondAddress + ); } function deactivateArtifact(uint256 locationId) public { @@ -268,6 +275,7 @@ library LibArtifactUtils { bool shouldBurn = artifact.artifactType == ArtifactType.PlanetaryShield || artifact.artifactType == ArtifactType.PhotoidCannon; + if (shouldBurn) { // burn it after use. will be owned by contract but not on a planet anyone can control LibArtifact.takeArtifactOffPlanet(locationId, artifact.id); diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index 290f9745..dbba9f87 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -33,7 +33,7 @@ import { ZERO_PLANET, } from './utils/WorldConstants'; -describe('DarkForestArtifacts', function () { +describe.only('DarkForestArtifacts', function () { let world: World; async function worldFixture() { @@ -106,7 +106,7 @@ describe('DarkForestArtifacts', function () { const artifactId = await createArtifact( world.contract, world.user1.address, - ARTIFACT_PLANET_1, + ZERO_PLANET, ArtifactType.Colossus ); @@ -158,49 +158,50 @@ describe('DarkForestArtifacts', function () { }); it('should return a correct token uri for a minted artifact', async function () { - await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); - await increaseBlockchainTime(); - await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); - - const artifactsOnPlanet = await world.contract.planetArtifacts(ARTIFACT_PLANET_1.id); - const tokenUri = await world.contract.uri(artifactsOnPlanet[0]); + const artifactId = await user1MintArtifactPlanet(world.user1Core); + prettyPrintToken(await world.contract.getArtifactFromId(artifactId)); + const tokenUri = await world.contract.uri(artifactId); const networkId = hre.network.config.chainId; const contractAddress = world.contract.address; expect(tokenUri).to.eq( - `https://nft-test.zkga.me/token-uri/artifact/${networkId}-${contractAddress}/` + - artifactsOnPlanet[0] + `https://nft-test.zkga.me/token-uri/artifact/${networkId}-${contractAddress}/` + artifactId ); }); - it("should not be able to deposit an artifact you don't own", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // user1 moves artifact and withdraws - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) + it("should not be able to activate an artifact you don't own", async function () { + const newArtifactId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.Colossus ); - - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - // user2 should not be able to deposit artifact await expect( - world.user2Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) - ).to.be.revertedWith('you can only deposit artifacts you own'); + world.user2Core.activateArtifact(LVL3_SPACETIME_2.id, newArtifactId, 0) + ).to.be.revertedWith('you can only activate artifacts you own'); }); it('should be able to move an artifact from a planet you own', async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + console.log('init', (await world.contract.planets(ARTIFACT_PLANET_1.id)).isInitialized); + const newArtifactId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.Monolith + ); - let artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - let artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + expect(await world.contract.tokenExists(world.user1.address, newArtifactId)).to.equal(true); - // ruins should have 1 artifact (gear is filtered), spawn planet should not. - expect(artifactsOnRuins.length).to.eq(1); - // Might fail w spaceships - expect(artifactsOnSpawn.length).to.eq(0); + prettyPrintToken(await world.contract.getArtifactFromId(newArtifactId)); + + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, newArtifactId, 0); + expect(await world.contract.tokenExists(world.contract.address, newArtifactId)).to.equal( + true + ); + await world.user1Core.deactivateArtifact(ARTIFACT_PLANET_1.id); // after finding artifact, planet's popCap might get buffed // so let it fill up again await increaseBlockchainTime(); @@ -222,8 +223,8 @@ describe('DarkForestArtifacts', function () { expect(arrivalData.carriedArtifactId).to.equal(newArtifactId); // when moving, both the ruins and the spawn planet should not have artifacts - artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + let artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + let artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); expect(artifactsOnRuins.length).to.eq(0); expect(artifactsOnSpawn.length).to.eq(0); @@ -251,8 +252,8 @@ describe('DarkForestArtifacts', function () { ZERO_PLANET, ArtifactType.Monolith ); - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - + await world.user1Core.activateArtifact(LVL3_SPACETIME_1.id, newTokenId, 0); + await world.user1Core.deactivateArtifact(LVL3_SPACETIME_1.id); // wait for the planet to fill up and download its stats await increaseBlockchainTime(); await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); @@ -294,8 +295,13 @@ describe('DarkForestArtifacts', function () { }); it("should be able to conquer another player's planet and move their artifact", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - + const artifactId = await createArtifact( + world.contract, + world.user1.address, + ARTIFACT_PLANET_1, + ArtifactType.Monolith + ); + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); // after finding artifact, planet's popCap might get buffed // so let it fill up again await increaseBlockchainTime(); @@ -318,17 +324,18 @@ describe('DarkForestArtifacts', function () { await world.user2Core.move(...makeMoveArgs(SPAWN_PLANET_2, ARTIFACT_PLANET_1, 0, 50000, 0)); await increaseBlockchainTime(); + await world.user2Core.deactivateArtifact(ARTIFACT_PLANET_1.id); + // move artifact await world.user2Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_2, 0, 50000, 0, newArtifactId) + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_2, 0, 50000, 0, artifactId) ); await increaseBlockchainTime(); - + await world.user1Core.refreshPlanet(LVL3_SPACETIME_2.id); // verify that artifact was moved - await world.user2Core.withdrawArtifact(LVL3_SPACETIME_2.id, newArtifactId); - const artifacts = await world.user2Core.balanceOf(world.user2.address, newArtifactId); + const artifacts = await world.user2Core.getArtifactsOnPlanet(LVL3_SPACETIME_2.id); - expect(artifacts).to.be.equal(1); + expect(artifacts.length).to.be.equal(1); }); it('not be able to prospect for an artifact on planets that are not ruins', async function () { @@ -364,7 +371,14 @@ describe('DarkForestArtifacts', function () { }); it("should not be able to move an artifact from a planet it's not on", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + const newArtifactId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.Monolith + ); + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, newArtifactId, 0); + await world.user1Core.deactivateArtifact(ARTIFACT_PLANET_1.id); // after finding artifact, planet's popCap might get buffed // so let it fill up again await increaseBlockchainTime(); @@ -391,129 +405,22 @@ describe('DarkForestArtifacts', function () { }); describe('trading post', function () { - it('should be able to withdraw from / deposit onto trading posts you own', async function () { - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_3); - await increaseBlockchainTime(); - - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // move artifact to LVL3_SPACETIME_1 - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) - ); - await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); - - // artifact should be on LVL3_SPACETIME_1 - let artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); - let artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifactsOnTP1.find((a) => a.id === newArtifactId)); - await expect(artifactsOnTP1.length).to.eq(1); - await expect(artifactsOnTP2.length).to.eq(0); - - // withdraw from LVL3_SPACETIME_1 - await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - - // artifact should not be on any planet. - artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); - artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifactsOnTP1.length).to.eq(0); - await expect(artifactsOnTP2.length).to.eq(0); - - // deposit onto LVL3_SPACETIME_3 - await world.user1Core.depositArtifact(LVL3_SPACETIME_3.id, newArtifactId); - - // artifact should be on LVL3_SPACETIME_3 - artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); - artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifactsOnTP1.find((a) => a.id === newArtifactId)); - await expect(artifactsOnTP1.length).to.eq(0); - await expect(artifactsOnTP2.length).to.eq(1); - }); - - it("should not be able to withdraw from / deposit onto trading post you don't own", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // move artifact - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) - ); - - // user2 should not be able to withdraw from LVL3_SPACETIME_1 - await expect( - world.user2Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId) - ).to.be.revertedWith('you can only withdraw from a planet you own'); - - // user1 should not be able to deposit onto LVL3_SPACETIME_2 - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - await expect( - world.user1Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) - ).to.be.revertedWith('you can only deposit on a planet you own'); - }); - - it('should not be able to withdraw an artifact from a trading post that is not on the trading post', async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // should not be able to withdraw newArtifactId from LVL3_SPACETIME_1 - await expect( - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId) - ).to.be.revertedWith('artifact not found'); - }); - - it('should not be able to withdraw/deposit onto a planet that is not a trading post', async function () { - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL0_PLANET); - await increaseBlockchainTime(); - - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // should not be able to withdraw from ruins (which are not trading posts) - await expect( - world.user2Core.withdrawArtifact(ARTIFACT_PLANET_1.id, newArtifactId) - ).to.be.revertedWith('can only withdraw from trading posts'); - - // move artifact and withdraw - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) - ); - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - - // should not be able to deposit onto LVL0_PLANET (which is regular planet and not trading post) - await expect( - world.user1Core.depositArtifact(LVL0_PLANET.id, newArtifactId) - ).to.be.revertedWith('can only deposit on trading posts'); - }); - - it('should not be able to withdraw/deposit a high level artifact onto low level trading post', async function () { + it('should not be able to activate a high level artifact onto low level trading post', async function () { await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL6_SPACETIME); await increaseBlockchainTime(); // allow planets to fill up energy again const newTokenId = await createArtifact( world.contract, world.user1.address, - ZERO_PLANET, - ArtifactType.Monolith, - ArtifactRarity.Legendary, - Biome.OCEAN + ARTIFACT_PLANET_1, + ArtifactType.BlackDomain, + ArtifactRarity.Common ); - // deposit fails on low level trading post, succeeds on high level trading post + // activate fails on low level trading post, succeeds on high level trading post await expect( - world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId) - ).to.be.revertedWith('spacetime rip not high enough level to deposit this artifact'); - world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); - - // withdraw fails on low level trading post - await world.user1Core.move( - ...makeMoveArgs(LVL6_SPACETIME, LVL3_SPACETIME_1, 0, 250000000, 0, newTokenId) - ); - await expect( - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newTokenId) - ).to.be.revertedWith('spacetime rip not high enough level to withdraw this artifact'); - - // withdraw succeeds on high level post - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, LVL6_SPACETIME, 0, 500000, 0, newTokenId) - ); - await world.user1Core.withdrawArtifact(LVL6_SPACETIME.id, newTokenId); + world.user1Core.activateArtifact(LVL3_SPACETIME_1.id, newTokenId, 0) + ).to.be.revertedWith('artifact is not powerful enough to apply effect to this planet level'); }); }); @@ -539,14 +446,16 @@ describe('DarkForestArtifacts', function () { const artifactId = await createArtifact( world.contract, world.user1.address, - from, + ZERO_PLANET, ArtifactType.Wormhole, artifactRarities[i] as ArtifactRarity, Biome.OCEAN ); prettyPrintToken(await world.contract.getArtifactFromId(artifactId)); - activateAndConfirm(world.user1Core, from.id, artifactId, to.id); + await world.user1Core.activateArtifact(from.id, artifactId, to.id); + + // activateAndConfirm(world.user1Core, from.id, artifactId, to.id); // move from planet with artifact to its wormhole destination await increaseBlockchainTime(); @@ -578,7 +487,10 @@ describe('DarkForestArtifacts', function () { await world.user1Core.deactivateArtifact(from.id); // Move from planet with artifact to destination and expect speed is not boosted. - await world.user1Core.move(...makeMoveArgs(from, to, dist, shipsSent, silverSent)); + // Also move the artifact bc too many tokens; + await world.user1Core.move( + ...makeMoveArgs(from, to, dist, shipsSent, silverSent, artifactId) + ); const fromPlanetAfterDActivate = await world.contract.planets(from.id); const planetArrivalsAfterDActivate = await world.contract.getPlanetArrivals(to.id); const arrivalAfterDActivate = planetArrivalsAfterDActivate[0]; @@ -609,7 +521,7 @@ describe('DarkForestArtifacts', function () { const artifactId = await createArtifact( world.contract, world.user1.address, - from, + ZERO_PLANET, ArtifactType.Wormhole, ArtifactRarity.Common, Biome.OCEAN @@ -628,12 +540,6 @@ describe('DarkForestArtifacts', function () { expect(userWormholeBalance).to.eq(1); // activate the wormhole to the 2nd planet - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, artifactId); - - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, artifactId) - ); - await world.user1Core.activateArtifact(from.id, artifactId, to.id); const dist = 50; @@ -708,18 +614,14 @@ describe('DarkForestArtifacts', function () { prettyPrintToken(await world.contract.getArtifactFromId(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, newTokenId) - ); - await world.user1Core.activateArtifact(from.id, newTokenId, 0); + await world.user1Core.activateArtifact(SPAWN_PLANET_1.id, newTokenId, 0); const planetAfterBloomFilter = await world.user1Core.planets(from.id); expect(planetAfterBloomFilter.population).to.eq(planetAfterBloomFilter.populationCap); expect(planetAfterBloomFilter.silver).to.eq(planetAfterBloomFilter.silverCap); const artifactsOnRipAfterBurn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - + console.log(`artifacts on rip after burn`, artifactsOnRipAfterBurn); // bloom filter is immediately deactivated after activation expect(artifactsOnRipAfterBurn.length).to.equal(0); @@ -755,10 +657,6 @@ describe('DarkForestArtifacts', function () { prettyPrintToken(await world.contract.getArtifactFromId(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE, 0, 500000, 0, newTokenId) - ); await expect( world.user1Core.activateArtifact(LVL4_UNOWNED_DEEP_SPACE.id, newTokenId, 0) ).to.be.revertedWith('artifact is not powerful enough to apply effect to this planet level'); From 659f7df34f47a0bcef8e832ef1912b20efb70163 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Tue, 4 Oct 2022 12:32:17 +0100 Subject: [PATCH 54/55] feat: all tests pass --- eth/test/DFArtifacts.test.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index dbba9f87..8253c650 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -685,9 +685,6 @@ describe.only('DarkForestArtifacts', function () { ArtifactRarity.Common, Biome.OCEAN ); - - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move(...makeMoveArgs(LVL3_SPACETIME_1, to, 0, 500000, 0, newTokenId)); await world.user1Core.activateArtifact(to.id, newTokenId, 0); // black domain is no longer on a planet (is instead owned by contract), and so is effectively @@ -736,11 +733,6 @@ describe.only('DarkForestArtifacts', function () { Biome.OCEAN ); - await increaseBlockchainTime(); // so that trading post can fill up to max energy - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE, 0, 500000, 0, newTokenId) - ); await expect( world.user1Core.activateArtifact(LVL4_UNOWNED_DEEP_SPACE.id, newTokenId, 0) ).to.be.revertedWith('artifact is not powerful enough to apply effect to this planet level'); @@ -802,7 +794,7 @@ describe.only('DarkForestArtifacts', function () { ); prettyPrintToken(await world.contract.getArtifactFromId(newTokenId)); - await world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); + // await world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); // Confirm photoid cannon is activated. await activateAndConfirm(world.user1Core, LVL6_SPACETIME.id, newTokenId); From 7e54c3fe1481c21eba20ee849778a929159bda6c Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Tue, 4 Oct 2022 14:17:01 +0100 Subject: [PATCH 55/55] simple artifact functionality works --- eth/contracts/libraries/LibArtifactUtils.sol | 38 ++++++++++++-------- eth/test/DFArtifacts.test.ts | 30 ++++++++++++++-- eth/test/DFMove.test.ts | 19 ++++++---- eth/test/DFSilver.ts | 2 +- eth/test/DFSpaceShips.test.ts | 2 +- 5 files changed, 66 insertions(+), 25 deletions(-) diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 9d2b16b1..1e327b6a 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; // External contract imports import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; +import {DFTokenFacet} from "../facets/DFTokenFacet.sol"; import {DFGetterFacet} from "../facets/DFGetterFacet.sol"; // Library imports @@ -126,9 +127,11 @@ library LibArtifactUtils { 5, "too many tokens on this planet" ); + // Either artifact is owned by player OR is on planet. require( - DFArtifactFacet(address(this)).tokenExists(msg.sender, artifactId), - "you can only activate artifacts you own" + DFTokenFacet(address(this)).tokenExists(msg.sender, artifactId) || + LibArtifact.isArtifactOnPlanet(locationId, artifactId), + "you can only activate artifacts you own or on planet" ); if (LibSpaceship.isShip(artifactId)) { activateSpaceshipArtifact(locationId, artifactId, planet); @@ -174,6 +177,8 @@ library LibArtifactUtils { // TODO: Why not actually burn? // burn it after use. will be owned by contract but not on a planet anyone can control LibSpaceship.takeSpaceshipOffPlanet(locationId, shipId); + // BURN + DFTokenFacet(address(this)).burn(msg.sender, shipId, 1); emit ArtifactDeactivated(msg.sender, shipId, locationId); } } @@ -230,23 +235,28 @@ library LibArtifactUtils { if (shouldDeactivateAndBurn) { gs().planets[locationId].activeArtifact = 0; // immediately remove activate artifact - emit ArtifactDeactivated(msg.sender, artifactId, locationId); LibGameUtils._buffPlanet(locationId, LibArtifact.getUpgradeForArtifact(artifact)); - return; + emit ArtifactDeactivated(msg.sender, artifactId, locationId); + DFTokenFacet(address(this)).burn(msg.sender, artifactId, 1); // burn it after use. will be owned by contract but not on a planet anyone can control // No need to take off, Artifact will never get placed. // LibArtifact.takeArtifactOffPlanet(locationId, artifactId); + } else { + // this is fine even tho some artifacts are immediately deactivated, because + // those artifacts do not buff the planet. + LibArtifact.putArtifactOnPlanet(locationId, artifactId); + LibGameUtils._buffPlanet(locationId, LibArtifact.getUpgradeForArtifact(artifact)); + // Also transfer to contract + + // Only transfer if player is activating from inventory + if (DFTokenFacet(address(this)).tokenExists(msg.sender, artifactId)) { + DFArtifactFacet(address(this)).transferArtifact( + artifactId, + msg.sender, + gs().diamondAddress + ); + } } - // this is fine even tho some artifacts are immediately deactivated, because - // those artifacts do not buff the planet. - LibArtifact.putArtifactOnPlanet(locationId, artifactId); - LibGameUtils._buffPlanet(locationId, LibArtifact.getUpgradeForArtifact(artifact)); - // Also transfer to contract - DFArtifactFacet(address(this)).transferArtifact( - artifactId, - msg.sender, - gs().diamondAddress - ); } function deactivateArtifact(uint256 locationId) public { diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index 8253c650..4759baf2 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -22,6 +22,7 @@ import { ARTIFACT_PLANET_1, LVL0_PLANET, LVL0_PLANET_DEAD_SPACE, + LVL1_ASTEROID_1, LVL3_SPACETIME_1, LVL3_SPACETIME_2, LVL3_SPACETIME_3, @@ -33,7 +34,7 @@ import { ZERO_PLANET, } from './utils/WorldConstants'; -describe.only('DarkForestArtifacts', function () { +describe('DarkForestArtifacts', function () { let world: World; async function worldFixture() { @@ -180,7 +181,7 @@ describe.only('DarkForestArtifacts', function () { // user2 should not be able to deposit artifact await expect( world.user2Core.activateArtifact(LVL3_SPACETIME_2.id, newArtifactId, 0) - ).to.be.revertedWith('you can only activate artifacts you own'); + ).to.be.revertedWith('you can only activate artifacts you own or on planet'); }); it('should be able to move an artifact from a planet you own', async function () { @@ -358,7 +359,7 @@ describe.only('DarkForestArtifacts', function () { const artifactId = await createArtifact( world.contract, world.user1.address, - ARTIFACT_PLANET_1, + ZERO_PLANET, ArtifactType.Monolith ); await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); @@ -402,6 +403,29 @@ describe.only('DarkForestArtifacts', function () { ) ).to.be.revertedWith('cannot move token of this type'); }); + it('should be able to activate, deactivate, move, activate', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL1_ASTEROID_1); + const artifactId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.Monolith + ); + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); + + await world.user1Core.deactivateArtifact(ARTIFACT_PLANET_1.id); + + await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL1_ASTEROID_1, 10, 50000, 0, artifactId) + ); + + await increaseBlockchainTime(); + + await world.user1Core.activateArtifact(LVL1_ASTEROID_1.id, artifactId, 0); + expect((await world.user1Core.getActiveArtifactOnPlanet(LVL1_ASTEROID_1.id)).id).to.equal( + artifactId + ); + }); }); describe('trading post', function () { diff --git a/eth/test/DFMove.test.ts b/eth/test/DFMove.test.ts index a3a4850b..e368ddd7 100644 --- a/eth/test/DFMove.test.ts +++ b/eth/test/DFMove.test.ts @@ -102,6 +102,7 @@ describe('DarkForestMove', function () { }); it('should not consume a photoid if moving a ship off a planet with one activated', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL2_PLANET_SPACE); const artifactId = await createArtifact( world.contract, world.user1.address, @@ -109,25 +110,31 @@ describe('DarkForestMove', function () { ArtifactType.PhotoidCannon ); - await world.user1Core.activateArtifact(SPAWN_PLANET_1.id, artifactId, 0); - expect((await world.contract.getActiveArtifactOnPlanet(SPAWN_PLANET_1.id)).id).to.equal( + await world.user1Core.activateArtifact(LVL2_PLANET_SPACE.id, artifactId, 0); + expect((await world.contract.getActiveArtifactOnPlanet(LVL2_PLANET_SPACE.id)).id).to.equal( artifactId ); await increaseBlockchainTime(); + // Move const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).filter( (s) => s.spaceshipType === SpaceshipType.ShipGear )[0]; await world.user1Core.move( - ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_1, 100, 0, 0, ship?.id) + ...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 100, 0, 0, ship?.id) + ); + + await increaseBlockchainTime(); + await world.user1Core.move( + ...makeMoveArgs(LVL2_PLANET_SPACE, LVL1_ASTEROID_1, 100, 0, 0, ship?.id) ); - await world.contract.refreshPlanet(SPAWN_PLANET_1.id); - const activePhotoid = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + await world.contract.refreshPlanet(LVL2_PLANET_SPACE.id); + const activePhotoid = await getArtifactsOnPlanet(world, LVL2_PLANET_SPACE.id); // If the photoid is not there, it was used during ship move - expect(activePhotoid).to.not.eq(undefined); + expect(activePhotoid[0].id).to.equal(artifactId); }); }); diff --git a/eth/test/DFSilver.ts b/eth/test/DFSilver.ts index 8eea3e22..17a08413 100644 --- a/eth/test/DFSilver.ts +++ b/eth/test/DFSilver.ts @@ -4,7 +4,7 @@ import { conquerUnownedPlanet, feedSilverToCap, makeInitArgs } from './utils/Tes import { defaultWorldFixture, World } from './utils/TestWorld'; import { LVL1_ASTEROID_1, LVL3_SPACETIME_1, SPAWN_PLANET_1 } from './utils/WorldConstants'; -describe.only('DFSilver', async function () { +describe('DFSilver', async function () { // Bump the time out so that the test doesn't timeout during // initial fixture creation this.timeout(1000 * 60); diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index 874f6d38..ea5eb516 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -189,7 +189,7 @@ describe('DarkForestSpaceShips', function () { // Cannot activate again. await expect( world.user1Core.activateArtifact(LVL1_PLANET_DEEP_SPACE.id, crescent.id, 0) - ).to.be.revertedWith("can't activate a ship on a planet it's not on"); + ).to.be.revertedWith('you can only activate artifacts you own or on planet'); }); });