Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/contracts/constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ bytes14 constant WORLD_NAMESPACE = "UD";

string constant ERC721_NAME = "UDCharacters";
string constant ERC721_SYMBOL = "UDC";
string constant TOKEN_URI = "Test-token-uri";
string constant TOKEN_URI = "Test-token-uri/";
5 changes: 5 additions & 0 deletions packages/contracts/out/IWorld.sol/IWorld.abi.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@
"name": "name",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "tokenUri",
"type": "string",
"internalType": "string"
}
],
"outputs": [
Expand Down
5 changes: 5 additions & 0 deletions packages/contracts/out/IWorld.sol/IWorld.abi.json.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ declare const abi: [
"name": "name",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "tokenUri",
"type": "string",
"internalType": "string"
}
],
"outputs": [
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/out/IWorld.sol/IWorld.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
"@latticexyz/schema-type": "2.0.11",
"@latticexyz/store": "2.0.11",
"@latticexyz/world": "2.0.11",
"@latticexyz/world-modules": "2.0.11",
"@pythnetwork/entropy-sdk-solidity": "1.3.0"
"@latticexyz/world-modules": "2.0.11"
},
"devDependencies": {
"@pythnetwork/entropy-sdk-solidity": "1.3.0",
"@types/node": "^18.15.11",
"ds-test": "https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0",
"forge-std": "https://github.com/foundry-rs/forge-std.git#74cfb77e308dd188d2f58864aaf44963ae6b88b1",
Expand Down
3 changes: 2 additions & 1 deletion packages/contracts/remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ forge-std/=node_modules/forge-std/src/
@world/=src/codegen/world/
@codegen/=src/codegen/
@openzeppelin=node_modules/@openzeppelin/contracts/
@openzeppelin-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/
@openzeppelin-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/
@erc1155/=lib/ERC1155-puppet/
6 changes: 5 additions & 1 deletion packages/contracts/src/codegen/world/ICharacterSystem.sol

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

220 changes: 118 additions & 102 deletions packages/contracts/src/systems/CharacterSystem.sol
Original file line number Diff line number Diff line change
@@ -1,109 +1,125 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;

import { System } from "@latticexyz/world/src/System.sol";
import { RandomNumbers } from "@codegen/index.sol";
import { RngRequestType } from "@codegen/common.sol";

import { UltimateDominionConfig } from "@codegen/index.sol";
import { IERC721Mintable } from "@latticexyz/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol";
import { SystemSwitch } from "@latticexyz/world-modules/src/utils/SystemSwitch.sol";
import { IERC20System } from "@latticexyz/world-modules/src/interfaces/IERC20System.sol";
import { Classes } from "@codegen/common.sol";
import { Characters, CharactersData } from "@tables/Characters.sol";
import { CharacterStats, CharacterStatsData } from "@tables/CharacterStats.sol";
import { NameExists } from "@tables/NameExists.sol";
import { Counters } from "@tables/Counters.sol";
import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
import { IWorld } from "@world/IWorld.sol";
import { IRngSystem } from "../interfaces/IRngSystem.sol";
import { LibChunks } from "../libraries/LibChunks.sol";
import {System} from "@latticexyz/world/src/System.sol";
import {RandomNumbers} from "@codegen/index.sol";
import {RngRequestType} from "@codegen/common.sol";

import {UltimateDominionConfig} from "@codegen/index.sol";
import {IERC721Mintable} from "@latticexyz/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol";
import {SystemSwitch} from "@latticexyz/world-modules/src/utils/SystemSwitch.sol";
import {TokenURI} from "@latticexyz/world-modules/src/modules/erc721-puppet/tables/TokenURI.sol";
import {_tokenUriTableId} from "@latticexyz/world-modules/src/modules/erc721-puppet/utils.sol";
import {IERC20System} from "@latticexyz/world-modules/src/interfaces/IERC20System.sol";
// import {IItemsSystem} from "@codegen/world/IItemsSystem.sol";
import {Classes} from "@codegen/common.sol";
import {Characters, CharactersData} from "@tables/Characters.sol";
import {CharacterStats, CharacterStatsData} from "@tables/CharacterStats.sol";
import {NameExists} from "@tables/NameExists.sol";
import {Counters} from "@tables/Counters.sol";
// import {IERC1155System} from "@erc1155/IERC1155System.sol";
import {ResourceId, WorldResourceIdLib, WorldResourceIdInstance} from "@latticexyz/world/src/WorldResourceId.sol";
import {RESOURCE_SYSTEM} from "@latticexyz/world/src/worldResourceTypes.sol";
import {IWorld} from "@world/IWorld.sol";
import {IRngSystem} from "../interfaces/IRngSystem.sol";
import {LibChunks} from "../libraries/LibChunks.sol";
import "forge-std/console2.sol";
import { IEntropyConsumer } from "@pythnetwork/IEntropyConsumer.sol";
import { IEntropy } from "@pythnetwork/IEntropy.sol";
import { _erc721SystemId } from "../utils.sol";
import { GOLD_NAMESPACE, CHARACTERS_NAMESPACE, WORLD_NAMESPACE } from "../../constants.sol";
import {IEntropyConsumer} from "@pythnetwork/IEntropyConsumer.sol";
import {IEntropy} from "@pythnetwork/IEntropy.sol";
import {_erc721SystemId} from "../utils.sol"; //, _erc1155SystemId, _itemsSystemId
import {GOLD_NAMESPACE, CHARACTERS_NAMESPACE, WORLD_NAMESPACE} from "../../constants.sol"; //, ITEMS_NAMESPACE

contract CharacterSystem is System {
function getName(uint256 characterId) public view returns (bytes32 _name) {
_name = Characters.getName(characterId);
}

function getClass(uint256 characterId) public view returns (Classes _class) {
_class = Characters.getClass(characterId);
}

function _goldToken() internal view returns (IERC20System goldToken) {
goldToken = IERC20System(UltimateDominionConfig.getGoldToken());
}

function _characterToken() internal view returns (IERC721Mintable characterToken) {
characterToken = IERC721Mintable(UltimateDominionConfig.getCharacterToken());
}

function mintCharacter(address account, bytes32 name) public returns (uint256 characterId) {
characterId = _incrementCharacterCounter();
IWorld(_world()).call(
_erc721SystemId(CHARACTERS_NAMESPACE),
abi.encodeCall(IERC721Mintable.mint, (account, characterId))
);

Characters.setOwner(characterId, account);

require(!NameExists.getValue(name), "Name already exists");
NameExists.setValue(name, true);
Characters.setName(characterId, name);
}

function rollStats(bytes32 userRandomNumber, uint256 characterId, Classes class) public payable {
require(!Characters.getLocked(characterId), "you have already accepted this character");
require(_isOwner(characterId), "Not your Character.");

Characters.setClass(characterId, class);

RngRequestType requestType = RngRequestType.CharacterStats;
// use systemSwitch to call rng system
SystemSwitch.call(abi.encodeCall(IRngSystem.getRng, (userRandomNumber, requestType, abi.encode(characterId))));
}

function enterGame(uint256 characterId) public {
require(_isOwner(characterId), "not your character");
require(!Characters.getLocked(characterId), "you have entered the game");

issueGold(characterId, 5 ether);

Characters.setLocked(characterId, true);
}

function _incrementCharacterCounter() internal returns (uint256) {
address characterContract = UltimateDominionConfig.getCharacterToken();
uint256 characterCounter = Counters.getCounter(address(characterContract));
Counters.setCounter(characterContract, (characterCounter + 1));
return characterCounter;
}

function _gold() internal view returns (IERC20System gold) {
return IERC20System(UltimateDominionConfig.getGoldToken());
}

function _isOwner(uint256 characterId) internal view returns (bool) {
return _msgSender() == Characters.getOwner(characterId);
}

function getOwner(uint256 characterId) public view returns (address) {
return Characters.getOwner(characterId);
}

function issueGold(uint256 characterId, uint256 amount) internal {
_gold().mint(getOwner(characterId), amount);
}

function getExperience(uint256 characterId) public view returns (uint256) {
return CharacterStats.getExperience(characterId);
}

function getCharacterStats(uint256 characterId) public view returns (CharacterStatsData memory) {
return CharacterStats.get(characterId);
}
function getName(uint256 characterId) public view returns (bytes32 _name) {
_name = Characters.getName(characterId);
}

function getClass(uint256 characterId) public view returns (Classes _class) {
_class = Characters.getClass(characterId);
}

function _goldToken() internal view returns (IERC20System goldToken) {
goldToken = IERC20System(UltimateDominionConfig.getGoldToken());
}

function _characterToken() internal view returns (IERC721Mintable characterToken) {
characterToken = IERC721Mintable(UltimateDominionConfig.getCharacterToken());
}

function mintCharacter(address account, bytes32 name, string memory tokenUri)
public
returns (uint256 characterId)
{
characterId = _incrementCharacterCounter();
// _characterToken().mint(account, characterId);
IWorld(_world()).call(
_erc721SystemId(CHARACTERS_NAMESPACE), abi.encodeCall(IERC721Mintable.mint, (account, characterId))
);

Characters.setOwner(characterId, account);

require(!NameExists.getValue(name), "Name already exists");
NameExists.setValue(name, true);
Characters.setName(characterId, name);
_setTokenURI(characterId, tokenUri);
}

function rollStats(bytes32 userRandomNumber, uint256 characterId, Classes class) public payable {
require(!Characters.getLocked(characterId), "you have already accepted this character");
require(_isOwner(characterId), "Not your Character.");
RngRequestType requestType = RngRequestType.CharacterStats;
Characters.setClass(characterId, class);
// use systemSwitch to call rng system
SystemSwitch.call(abi.encodeCall(IRngSystem.getRng, (userRandomNumber, requestType, abi.encode(characterId))));
}

function enterGame(uint256 characterId) public {
require(_isOwner(characterId), "not your character");
require(!Characters.getLocked(characterId), "you have entered the game");

issueGold(characterId, 5 ether);
// issue starterWeapon
// IWorld(_world()).UD__issueStarterItems(characterId, 0, 1);

Characters.setLocked(characterId, true);
}

function _setTokenURI(uint256 tokenId, string memory tokenUri) internal {
TokenURI.setTokenURI(_tokenUriTableId(CHARACTERS_NAMESPACE), tokenId, tokenUri);
}

function _incrementCharacterCounter() internal returns (uint256) {
address characterContract = UltimateDominionConfig.getCharacterToken();
uint256 characterCounter = Counters.getCounter(address(characterContract));
Counters.setCounter(characterContract, (characterCounter + 1));
return characterCounter;
}

function _gold() internal view returns (IERC20System gold) {
return IERC20System(UltimateDominionConfig.getGoldToken());
}

function _isOwner(uint256 characterId) internal view returns (bool) {
return _msgSender() == Characters.getOwner(characterId);
}

function getOwner(uint256 characterId) public view returns (address) {
return Characters.getOwner(characterId);
}

function issueGold(uint256 characterId, uint256 amount) internal {
_gold().mint(getOwner(characterId), amount);
}

function _charactersNamespace() internal returns (ResourceId) {
return WorldResourceIdLib.encodeNamespace(CHARACTERS_NAMESPACE);
}

function getExperience(uint256 characterId) public view returns (uint256) {
return CharacterStats.getExperience(characterId);
}

function getCharacterStats(uint256 characterId) public view returns (CharacterStatsData memory) {
return CharacterStats.get(characterId);
}
}
99 changes: 57 additions & 42 deletions packages/contracts/test/CharacterSystem.t.sol
Original file line number Diff line number Diff line change
@@ -1,48 +1,63 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;

import { SetUp } from "./SetUp.sol";
import { Classes } from "../src/codegen/common.sol";
import { CharacterStatsData } from "../src/codegen/tables/CharacterStats.sol";
import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol";
import {SetUp} from "./SetUp.sol";
import {Classes} from "../src/codegen/common.sol";
import {CharacterStatsData} from "../src/codegen/tables/CharacterStats.sol";
import {GasReporter} from "@latticexyz/gas-report/src/GasReporter.sol";
import {IERC721Metadata} from "@latticexyz/world-modules/src/modules/erc721-puppet/IERC721Metadata.sol";
import "forge-std/console2.sol";

contract Test_CharacterSystem is SetUp, GasReporter {
function test_Mint() public {
startGasReport("mints a character");

vm.startPrank(alice);
alicesCharacterId = world.UD__mintCharacter(alice, bytes32("Alan"));
assertEq(alicesCharacterId, 1);
assertEq(characterToken.ownerOf(1), alice);
assertEq(characterToken.balanceOf(alice), 2);

endGasReport();
}

function test_RollStats() public {
startGasReport("rolls stats for a character");

uint256 fees = entropy.getFee(address(1));
vm.prank(alice);
world.UD__rollStats{ value: fees }(alicesRandomness, alicesCharacterId, Classes.Warrior);
vm.warp(block.number + 1);
CharacterStatsData memory alicesCharacter = world.UD__getCharacterStats(alicesCharacterId);
assertEq(uint8(world.UD__getClass(alicesCharacterId)), uint8(Classes.Rogue));
assertEq(alicesCharacter.strength, 2);
assertEq(alicesCharacter.agility, 7);
assertEq(alicesCharacter.hitPoints, 3);
assertEq(alicesCharacter.intelligence, 4);

endGasReport();
}

function test_RollStats_Revert_GameStarted() public {
uint256 fees = entropy.getFee(address(1));
vm.startPrank(alice);
world.UD__rollStats{ value: fees }(alicesRandomness, alicesCharacterId, Classes.Warrior);
world.UD__enterGame(alicesCharacterId);
vm.expectRevert();
world.UD__rollStats{ value: fees }(alicesRandomness, alicesCharacterId, Classes.Warrior);
}
function test_Mint() public {
startGasReport("mints a character");

vm.startPrank(alice);
alicesCharacterId = world.UD__mintCharacter(alice, bytes32("Alan"), "test_Character_URI");
assertEq(alicesCharacterId, 1);
assertEq(characterToken.ownerOf(1), alice);
assertEq(characterToken.balanceOf(alice), 2);
assertEq(
IERC721Metadata(address(characterToken)).tokenURI(alicesCharacterId), "Test-token-uri/test_Character_URI"
);

endGasReport();
}

function test_RollStats() public {
startGasReport("rolls stats for a character");

uint256 fees = entropy.getFee(address(1));
vm.prank(alice);
world.UD__rollStats{value: fees}(alicesRandomness, alicesCharacterId, Classes.Rogue);
vm.warp(block.number + 1);
CharacterStatsData memory alicesCharacter = world.UD__getCharacterStats(alicesCharacterId);
assertEq(uint8(world.UD__getClass(alicesCharacterId)), uint8(Classes.Rogue));
assertEq(alicesCharacter.strength, 2);
assertEq(alicesCharacter.agility, 7);
assertEq(alicesCharacter.hitPoints, 3);
assertEq(alicesCharacter.intelligence, 4);

endGasReport();
}

function test_RollStats_Revert_GameStarted() public {
uint256 fees = entropy.getFee(address(1));
vm.startPrank(alice);
world.UD__rollStats{value: fees}(alicesRandomness, alicesCharacterId, Classes.Rogue);
world.UD__enterGame(alicesCharacterId);
vm.expectRevert();
world.UD__rollStats{value: fees}(alicesRandomness, alicesCharacterId, Classes.Rogue);
}

function test_EnterGame() public {
startGasReport("enters a character into the game");

uint256 fees = entropy.getFee(address(1));
vm.startPrank(alice);
world.UD__rollStats{value: fees}(alicesRandomness, alicesCharacterId, Classes.Rogue);
world.UD__enterGame(alicesCharacterId);
// assertEq(erc1155System.balanceOf(alice, 0), 1);

endGasReport();
}
}
Loading