Skip to content
Open
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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ docs/
/src/mockContract/

node_modules/

.history
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
url = https://github.com/foundry-rs/forge-std
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/sdk"]
path = lib/sdk
url = https://github.com/gotchipus/sdk
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ gas_limit = "300000000"
[rpc_endpoints]
devnet = "https://devnet.dplabs-internal.com"
testnet = "https://testnet.dplabs-internal.com"
atlantic = "https://atlantic.dplabs-internal.com"

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
93 changes: 93 additions & 0 deletions test/facets/AttributesFacet.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.29;

import "forge-std/Test.sol";
import { DiamondFixture } from "../utils/DiamondFixture.sol";
import { MintFacet } from "../../src/facets/MintFacet.sol";
import { AttributesFacet } from "../../src/facets/AttributesFacet.sol";
import { LibGotchiConstants } from "../../src/libraries/LibGotchiConstants.sol";

contract AttributesFacetTest is DiamondFixture {
MintFacet internal mintFacetBound;
AttributesFacet internal attributesFacetBound;
address internal user = address(0xABC);
uint256 internal tokenId = 0;

function setUp() public override {
super.setUp();
mintFacetBound = MintFacet(address(diamond));
attributesFacetBound = AttributesFacet(address(diamond));

// Deposit enough ETH into the test users
vm.deal(user, 10 ether);

// Prerequisites: Mint and summon a Gotchi
_setupGotchi(user);
}

function _setupGotchi(address _user) internal {
vm.startPrank(_user);

// Public Mint
uint256 price = LibGotchiConstants.PHAROS_PRICE;
mintFacetBound.mint{value: price}(1);

// summon
MintFacet.SummonArgs memory args = MintFacet.SummonArgs({
gotchipusTokenId: tokenId,
gotchiName: "PetMe",
collateralToken: address(0),
stakeAmount: 0,
utc: 0,
story: "",
preIndex: 0
});
mintFacetBound.summonGotchipus(args);

vm.stopPrank();
}

function test_PetIncreaseStats() public {
vm.startPrank(user);

// Summon might initialize lastPetTime to the current time, so we need to skip 1 day first
vm.warp(block.timestamp + 1 days + 1 hours);

// 1. First touch (Pet)
attributesFacetBound.pet(tokenId);

// Verify timestamp update
uint32 lastPetTime = attributesFacetBound.getLastPetTime(tokenId);
assertEq(lastPetTime, block.timestamp);

// Verify attribute changes (based on AttributesFacet logic, empirical value +20)
// Here we can further verify the specific attribute values, if AttributesFacet has a getter
// For example:
// (uint32 exp,,,,,) = attributesFacetBound.getAttributes(tokenId);
// assertEq(exp, 20); // Initially 0, Pet increases by 20
// 2. Touching again during the cooldown period should fail
vm.expectRevert("gotchipus already pet");
attributesFacetBound.pet(tokenId);

// 3. Time travel: Fast forward 1 day + 1 second
vm.warp(block.timestamp + 1 days + 1);

// 4. The second touch should be successful
attributesFacetBound.pet(tokenId);
assertEq(attributesFacetBound.getLastPetTime(tokenId), block.timestamp);

vm.stopPrank();
}

function test_SetName() public {
vm.startPrank(user);

string memory newName = "NewGotchiName";
attributesFacetBound.setName(newName, tokenId);

string memory storedName = attributesFacetBound.getTokenName(tokenId);
assertEq(storedName, newName);

vm.stopPrank();
}
}
40 changes: 40 additions & 0 deletions test/facets/DNAFacet.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.29;

import "forge-std/Test.sol";
import { DiamondFixture } from "../utils/DiamondFixture.sol";
import { DNAFacet } from "../../src/facets/DNAFacet.sol";
import { MintFacet } from "../../src/facets/MintFacet.sol";
import { LibGotchiConstants } from "../../src/libraries/LibGotchiConstants.sol";

contract DNAFacetTest is DiamondFixture {
// Remove duplicate declarations: DNAFacet internal dnaFacet;
// Remove duplicate declarations: MintFacet internal mintFacet;

function setUp() public override {
super.setUp();
// Directly assign to inherited variables
dnaFacet = DNAFacet(address(diamond));
mintFacet = MintFacet(address(diamond));
}

function test_RuleVersion() public {
vm.prank(owner);
dnaFacet.setRuleVersion(2);
assertEq(dnaFacet.ruleVersion(), 2);
}

function test_TokenGeneSeed() public {
address user = address(0x1);
vm.deal(user, 1 ether);
vm.startPrank(user);

uint256 price = LibGotchiConstants.PHAROS_PRICE;
mintFacet.mint{value: price}(1);

// Verify that the call does not report any errors
dnaFacet.tokenGeneSeed(0);

vm.stopPrank();
}
}
123 changes: 123 additions & 0 deletions test/facets/GotchiWearableFacet.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.29;

import "forge-std/Test.sol";
import { DiamondFixture } from "../utils/DiamondFixture.sol";
import { GotchiWearableFacet } from "../../src/facets/GotchiWearableFacet.sol";
import { MintFacet } from "../../src/facets/MintFacet.sol";
import { WearableInfo } from "../../src/libraries/LibAppStorage.sol";
import { LibSvg } from "../../src/libraries/LibSvg.sol";
import { LibGotchiConstants } from "../../src/libraries/LibGotchiConstants.sol";

contract GotchiWearableFacetTest is DiamondFixture {
GotchiWearableFacet internal wearableFacetBound;
address internal user = address(0xABC);

function setUp() public override {
super.setUp();
wearableFacetBound = GotchiWearableFacet(address(diamond));
mintFacet = MintFacet(address(diamond));

// Register yourself as a Wearable contract (for mintWearable permissions)
vm.prank(owner);
wearableFacetBound.setWearableDiamond(address(this));
}

function test_CreateAndMintWearable() public {
// 1. Get the next available ID
// DiamondFixture has already initialized equipment numbers 0-84, so this should be number 85
uint256 newId = wearableFacetBound.getNextTokenId();

WearableInfo memory info = WearableInfo({
name: "God Sword",
description: "A powerful sword",
author: "Dev",
wearableType: LibSvg.SVG_TYPE_HAND,
wearableId: uint8(newId) // Although `create` rewrites the ID internally, it's good practice to maintain consistency
});

vm.prank(owner);
wearableFacetBound.createWearable("ipfs://sword", info);

// 2. Verification information (using dynamically obtained newId, instead of hardcoding 0)
WearableInfo memory fetched = wearableFacetBound.getWearableInfo(newId);
assertEq(fetched.name, "God Sword");

// 3. Mint equipment for users
// Only WearableDiamond (parallel to address(this)) can call mint
vm.deal(address(this), 1 ether);

uint256 amount = 10;
uint256 price = 0.001 ether;
uint256 cost = amount * price;

// Payment amount must equal amount * 0.001
wearableFacetBound.mintWearable{value: cost}(user, newId, amount);

assertEq(wearableFacetBound.wearableBalanceOf(user, newId), amount);
}

function test_EquipAndUnequip() public {
// 1. Preparation environment: The user has Gotchi and equipment
// The `_setupGotchiAndWearable` function internally creates an item with ID 85.
uint256 swordId = _setupGotchiAndWearable();

vm.startPrank(user);

// 2. equipment
// Equip with the correct swordId (85)
wearableFacetBound.equipWearable(0, swordId, LibSvg.SVG_TYPE_HAND);

// Verify the balance change (initially 5, becomes 4 after wearing)
assertEq(wearableFacetBound.wearableBalanceOf(user, swordId), 4);

// 3. take off
wearableFacetBound.unequipWearable(0, swordId, LibSvg.SVG_TYPE_HAND);
assertEq(wearableFacetBound.wearableBalanceOf(user, swordId), 5);

vm.stopPrank();
}

// Return the created equipment ID
function _setupGotchiAndWearable() internal returns (uint256) {
uint256 newId = wearableFacetBound.getNextTokenId();

// Create equipment (ID = newId, for example, 85)
WearableInfo memory info = WearableInfo({
name: "Sword",
description: "Weapon",
author: "Admin",
wearableType: LibSvg.SVG_TYPE_HAND,
wearableId: uint8(newId)
});
vm.prank(owner);
wearableFacetBound.createWearable("uri", info);

vm.deal(address(this), 1 ether);

// Payment amounts must match (5 * 0.001 = 0.005 ether)
uint256 amount = 5;
uint256 cost = amount * 0.001 ether;
wearableFacetBound.mintWearable{value: cost}(user, newId, amount);

// Mint Gotchi for User
vm.deal(user, 10 ether);
vm.prank(user);
mintFacet.mint{value: LibGotchiConstants.PHAROS_PRICE}(1);

// Summon
vm.prank(user);
MintFacet.SummonArgs memory args = MintFacet.SummonArgs({
gotchipusTokenId: 0,
gotchiName: "Warrior",
collateralToken: address(0),
stakeAmount: 0,
utc: 0,
story: "",
preIndex: 0
});
mintFacet.summonGotchipus(args);

return newId;
}
}
13 changes: 9 additions & 4 deletions test/facets/GotchipusFacet.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import { IGotchipusFacet } from "../../src/interfaces/IGotchipusFacet.sol";
import { IGotchiWearableFacet } from "../../src/interfaces/IGotchiWearableFacet.sol";
import { EquipWearableType, MAX_TRAITS_NUM } from "../../src/libraries/LibAppStorage.sol";
import { AttributesFacet } from "../../src/facets/AttributesFacet.sol";
import { LibGotchiConstants } from "../../src/libraries/LibGotchiConstants.sol";

contract GotchipusFacetTest is DiamondFixture {
uint256 price = 0.04 ether;
uint256 price = LibGotchiConstants.PHAROS_PRICE;

function _doMint(uint256 amount) internal {
IMintFacet gotchi = IMintFacet(address(diamond));
Expand Down Expand Up @@ -96,10 +97,14 @@ contract GotchipusFacetTest is DiamondFixture {

AttributesFacet attr = AttributesFacet(address(diamond));

for (uint256 i = 0; i < 100; i++) {
attr.pet(0);
}
vm.warp(block.timestamp + 25 hours);

// One tough and it's done
attr.pet(0);

// Verify the cooling logic: Touching it again immediately should fail
vm.expectRevert("gotchipus already pet");
attr.pet(0);

vm.stopPrank();
}
Expand Down
62 changes: 62 additions & 0 deletions test/facets/HooksFacet.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.29;

import "forge-std/Test.sol";
import { DiamondFixture } from "../utils/DiamondFixture.sol";
import { HooksFacet } from "../../src/facets/HooksFacet.sol";
import { IHook } from "../../src/interfaces/IHook.sol";
import { BeforeExecuteHook } from "../../src/utils/BaseHook.sol";

contract MockHook is BeforeExecuteHook {
constructor(address _g) BeforeExecuteHook(_g) {}
function _beforeExecute(IHook.HookParams calldata) internal view override {}
}

contract HooksFacetTest is DiamondFixture {
// Remove duplicate declarations: HooksFacet internal hooksFacet
MockHook internal hook;
uint256 internal tokenId = 0;

function setUp() public override {
super.setUp();
hooksFacet = HooksFacet(address(diamond));
hook = new MockHook(address(diamond));
}

function test_AddAndRemoveHook() public {
// First mint a token for the owner (mock).
// Here we need to ensure that tokenId 0 exists and belongs to the owner
// Simple simulation:
vm.store(address(diamond), keccak256(abi.encode(tokenId, 2)), bytes32(uint256(uint160(owner)))); // mock ownerOf logic if needed or invoke mint

// A better way is to directly mint
// mintFacet.mint(1) if available, let's assume setup is ok or mock ownership
// prank owner is used via modifier.

// Assuming the owner in DiamondFixture is also the deployer, we'll give him a mint
_mintForTest(owner);

vm.startPrank(owner);

hooksFacet.addHook(tokenId, IHook.GotchiEvent.BeforeExecute, hook);
assertTrue(hooksFacet.isHookValid(tokenId, address(hook)));

hooksFacet.removeHook(tokenId, IHook.GotchiEvent.BeforeExecute, hook);
assertFalse(hooksFacet.isHookValid(tokenId, address(hook)));

vm.stopPrank();
}

function _mintForTest(address /* to */) internal {
// A simple, brute-force approach to write storage to simulate the owner, preventing Mint logic from becoming too complex.
// mapping(uint256 => address) tokenOwners; // slot 2 in LibAppStorage usually
// Or directly call diamond's mint.
// Let's try calling mintFacet directly (is it defined in the parent class? It should be).
// If the parent class has a MintFacet definition but it's not public, we removed the duplicate definition earlier.
// Let's assume diamond has already deployed MintFacet.
(bool success,) = address(diamond).call{value: 0.04 ether}(abi.encodeWithSignature("mint(uint256)", 1));
if(success) {
// transfer to 'to' if needed, default is msg.sender
}
}
}
Loading