Skip to content

Commit

Permalink
chore: SOF V2 Conversion (#369)
Browse files Browse the repository at this point in the history
# **Current PR Status**:

- Foundational setup is done. `WorldWithContext` is being used and I've
started our setup script in `EveTest.sol`
- When it comes time to deploy this for real, we can break the logic in
`EveTest` to a separate deploy script. I kept it in `EveTest` for now
for testing simplicity (no need to run and deploy a separate World and
run tests against it).
- I think I've added `scope` and `access` to all the appropriate places,
but this needs to be confirmed. Errors here will be apparent when
running tests.
- I've done a lot of work on implementing the permission matrix found
[here](https://miro.com/app/board/uXjVK_LSVxY=/). Still need to do one
last pass as @ccp-tezza updated it recently. I've been adding tests that
hit the access functions as part of the separate deployable tests.

## **Current Passing Tests**:
- `SmartStorageUnit`
- `InventoryInteract`
- `Inventory`
- `EphemeralInventory`
- `Deployable`
- `Fuel`
- `SmartTurret`
- `SmartGate`
- `Location`

## **My Process for Converting Tests**:
1. Inherit from `EveTest`. This will do all of the SOFv2 setup
2. Remove all old setup from the test. i.e. user addresses are handled,
`World` is now `WorldWithContext`
3. Make sure to create a new `classId` if needed and to instantiate the
smart object as that class. i.e. In DeployableTest we create a mock
deployable so we need to create a class id associated with this. Usually
this is handled in the top level create functions like
`CreateAndAchorSmartStorageUnit`
4. If there are `UnscopedSystemCall` errors, tag the class id with the
necessary system
5. If there are `Access` errors, prank as the right user in the test
when making the system calls

### Test Tips
- If you are hitting `EntityAlreadyExists` when instantiating, it is
most likely because test fuzzing is using a generated id that conflicts
with a hardcoded ID. This was happening a lot with `characterId`
conflicting with `smartObjectId`. You can add `vm.assume(smartObjectId
!= characterId)` to make sure this doesn't happen.
- When hitting `UnscopedSystemCall` you can search for the given
`systemId` in the codebase and the generated libs will pop up for easy
translation.

## **Final Confirmation**:
When all tests are passing, we should do a final pass and confirm:
- `Access` functions exactly match the permission matrix
- `context`, `scope`, and `access` are added to every system

---------

Co-authored-by: CCP Legolas <[email protected]>
  • Loading branch information
Kooshaba and 0xxlegolas authored Feb 13, 2025
1 parent a040a0e commit bdebf06
Show file tree
Hide file tree
Showing 94 changed files with 9,739 additions and 4,355 deletions.
5 changes: 3 additions & 2 deletions end-to-end-tests-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"solhint": "solhint --config ./.solhint.json 'src/**/*.sol'"
},
"dependencies": {
"@eveworld/world-v2": "link:../mud-contracts/world-v2",
"@eveworld/world-v3": "link:../mud-contracts/world-v2",
"@eveworld/world-v2": "git+ssh://[email protected]/projectawakening/world-chain-contracts.git#4ed48276b125b5665ba6f569209b2ea54f6e0ba0",
"@latticexyz/cli": "2.2.15-main-ba5191c3d6f74b3c4982afd465cf449d23d70bb7",
"@latticexyz/gas-report": "2.2.15-main-ba5191c3d6f74b3c4982afd465cf449d23d70bb7",
"@latticexyz/schema-type": "2.2.15-main-ba5191c3d6f74b3c4982afd465cf449d23d70bb7",
Expand All @@ -68,4 +69,4 @@
"solhint-config-mud": "2.2.15-main-ba5191c3d6f74b3c4982afd465cf449d23d70bb7",
"solhint-plugin-mud": "2.2.15-main-ba5191c3d6f74b3c4982afd465cf449d23d70bb7"
}
}
}
3 changes: 1 addition & 2 deletions end-to-end-tests-v2/remappings.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
ds-test/=node_modules/ds-test/src/
forge-std/=node_modules/forge-std/src/
@latticexyz/=node_modules/@latticexyz/
@eveworld/=node_modules/@eveworld/

@eveworld/=node_modules/@eveworld/world-v2/mud-contracts/
3 changes: 2 additions & 1 deletion mud-contracts/smart-object-framework-v2/.solhintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/src/WorldWithContext.sol
/src/WorldWithContext.sol
/src/namespaces/**/codegen/**
3 changes: 3 additions & 0 deletions mud-contracts/smart-object-framework-v2/mud.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export default defineWorld({
name: "WorldWithContext",
},
},
codegen: {
generateSystemLibraries: true,
},
userTypes: {
TagId: { type: "bytes32", filePath: "./src/libs/TagId.sol" },
ResourceId: { type: "bytes32", filePath: "@latticexyz/store/src/ResourceId.sol" },
Expand Down
14 changes: 7 additions & 7 deletions mud-contracts/smart-object-framework-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
"test": "tsc --noEmit && mud test --forgeOptions=-vvv"
},
"dependencies": {
"@latticexyz/cli": "2.2.11",
"@latticexyz/schema-type": "2.2.11",
"@latticexyz/store": "2.2.11",
"@latticexyz/world": "2.2.11",
"@latticexyz/world-modules": "2.2.11"
"@latticexyz/cli": "2.2.15-main-ba5191c3d6f74b3c4982afd465cf449d23d70bb7",
"@latticexyz/schema-type": "2.2.15-main-ba5191c3d6f74b3c4982afd465cf449d23d70bb7",
"@latticexyz/store": "2.2.15-main-ba5191c3d6f74b3c4982afd465cf449d23d70bb7",
"@latticexyz/world": "2.2.15-main-ba5191c3d6f74b3c4982afd465cf449d23d70bb7",
"@latticexyz/world-modules": "2.2.15-main-ba5191c3d6f74b3c4982afd465cf449d23d70bb7"
},
"devDependencies": {
"@types/debug": "4.1.7",
Expand All @@ -33,8 +33,8 @@
"prettier": "3.2.5",
"prettier-plugin-solidity": "1.3.1",
"solhint": "^3.3.7",
"solhint-config-mud": "2.2.11",
"solhint-plugin-mud": "2.2.11",
"solhint-config-mud": "2.2.15-main-ba5191c3d6f74b3c4982afd465cf449d23d70bb7",
"solhint-plugin-mud": "2.2.15-main-ba5191c3d6f74b3c4982afd465cf449d23d70bb7",
"typescript": "^5.4.5"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.21;
pragma solidity ^0.8.24;

import { IWorld } from "./codegen/world/IWorld.sol";
import { ResourceId } from "@latticexyz/world/src/WorldResourceId.sol";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;
pragma solidity ^0.8.24;

import { ResourceId, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
import { AccessControl } from "@latticexyz/world/src/AccessControl.sol";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;
pragma solidity ^0.8.24;

import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;
pragma solidity ^0.8.24;

import { StoreCore } from "@latticexyz/store/src/StoreCore.sol";
import { Bytes } from "@latticexyz/store/src/Bytes.sol";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;
pragma solidity ^0.8.24;

import { System } from "@latticexyz/world/src/System.sol";
import { ResourceId } from "@latticexyz/world/src/WorldResourceId.sol";
Expand Down Expand Up @@ -74,13 +74,15 @@ contract SmartObjectFramework is System {
*/
modifier scope(uint256 entityId) {
// check that the current system is in scope for the given entity
ResourceId systemId = SystemRegistry.get(address(this));
_scope(entityId, systemId);
// if this is a subsequent system-to-system call (callCount > 1), then check that the previous (calling) system is in scope for the given entity
uint256 callCount = IWorldWithContext(_world()).getWorldCallCount();
if (callCount > 1) {
(ResourceId prevSystemId, , , ) = IWorldWithContext(_world()).getWorldCallContext(callCount - 1);
_scope(entityId, prevSystemId);
{
ResourceId systemId = SystemRegistry.get(address(this));
_scope(entityId, systemId);
// if this is a subsequent system-to-system call (callCount > 1), then check that the previous (calling) system is in scope for the given entity
uint256 callCount = IWorldWithContext(_world()).getWorldCallCount();
if (callCount > 1) {
(ResourceId prevSystemId, , , ) = IWorldWithContext(_world()).getWorldCallContext(callCount - 1);
_scope(entityId, prevSystemId);
}
}
_;
}
Expand Down
2 changes: 1 addition & 1 deletion mud-contracts/smart-object-framework-v2/src/libs/TagId.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;
pragma solidity ^0.8.24;

/**
* @title TagId type definition and related utilities
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;

/* Autogenerated file. Do not edit manually. */

import { AccessConfigSystem } from "../../systems/access-config-system/AccessConfigSystem.sol";
import { ResourceId } from "@latticexyz/world/src/WorldResourceId.sol";
import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol";
import { IWorldCall } from "@latticexyz/world/src/IWorldKernel.sol";
import { SystemCall } from "@latticexyz/world/src/SystemCall.sol";
import { Systems } from "@latticexyz/world/src/codegen/tables/Systems.sol";
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";

type AccessConfigSystemType is bytes32;

// equivalent to WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: "evefrontier", name: "AccessConfigSyst" }))
AccessConfigSystemType constant accessConfigSystem = AccessConfigSystemType.wrap(
0x737965766566726f6e74696572000000416363657373436f6e66696753797374
);

struct CallWrapper {
ResourceId systemId;
address from;
}

struct RootCallWrapper {
ResourceId systemId;
address from;
}

/**
* @title AccessConfigSystemLib
* @author MUD (https://mud.dev) by Lattice (https://lattice.xyz)
* @dev This library is automatically generated from the corresponding system contract. Do not edit manually.
*/
library AccessConfigSystemLib {
error AccessConfigSystemLib_CallingFromRootSystem();

function configureAccess(
AccessConfigSystemType self,
ResourceId targetSystemId,
bytes4 targetFunctionId,
ResourceId accessSystemId,
bytes4 accessFunctionId
) internal {
return
CallWrapper(self.toResourceId(), address(0)).configureAccess(
targetSystemId,
targetFunctionId,
accessSystemId,
accessFunctionId
);
}

function setAccessEnforcement(
AccessConfigSystemType self,
ResourceId targetSystemId,
bytes4 targetFunctionId,
bool enforced
) internal {
return
CallWrapper(self.toResourceId(), address(0)).setAccessEnforcement(targetSystemId, targetFunctionId, enforced);
}

function configureAccess(
CallWrapper memory self,
ResourceId targetSystemId,
bytes4 targetFunctionId,
ResourceId accessSystemId,
bytes4 accessFunctionId
) internal {
// if the contract calling this function is a root system, it should use `callAsRoot`
if (address(_world()) == address(this)) revert AccessConfigSystemLib_CallingFromRootSystem();

bytes memory systemCall = abi.encodeCall(
_configureAccess_ResourceId_bytes4_ResourceId_bytes4.configureAccess,
(targetSystemId, targetFunctionId, accessSystemId, accessFunctionId)
);
self.from == address(0)
? _world().call(self.systemId, systemCall)
: _world().callFrom(self.from, self.systemId, systemCall);
}

function setAccessEnforcement(
CallWrapper memory self,
ResourceId targetSystemId,
bytes4 targetFunctionId,
bool enforced
) internal {
// if the contract calling this function is a root system, it should use `callAsRoot`
if (address(_world()) == address(this)) revert AccessConfigSystemLib_CallingFromRootSystem();

bytes memory systemCall = abi.encodeCall(
_setAccessEnforcement_ResourceId_bytes4_bool.setAccessEnforcement,
(targetSystemId, targetFunctionId, enforced)
);
self.from == address(0)
? _world().call(self.systemId, systemCall)
: _world().callFrom(self.from, self.systemId, systemCall);
}

function configureAccess(
RootCallWrapper memory self,
ResourceId targetSystemId,
bytes4 targetFunctionId,
ResourceId accessSystemId,
bytes4 accessFunctionId
) internal {
bytes memory systemCall = abi.encodeCall(
_configureAccess_ResourceId_bytes4_ResourceId_bytes4.configureAccess,
(targetSystemId, targetFunctionId, accessSystemId, accessFunctionId)
);
SystemCall.callWithHooksOrRevert(self.from, self.systemId, systemCall, msg.value);
}

function setAccessEnforcement(
RootCallWrapper memory self,
ResourceId targetSystemId,
bytes4 targetFunctionId,
bool enforced
) internal {
bytes memory systemCall = abi.encodeCall(
_setAccessEnforcement_ResourceId_bytes4_bool.setAccessEnforcement,
(targetSystemId, targetFunctionId, enforced)
);
SystemCall.callWithHooksOrRevert(self.from, self.systemId, systemCall, msg.value);
}

function callFrom(AccessConfigSystemType self, address from) internal pure returns (CallWrapper memory) {
return CallWrapper(self.toResourceId(), from);
}

function callAsRoot(AccessConfigSystemType self) internal view returns (RootCallWrapper memory) {
return RootCallWrapper(self.toResourceId(), msg.sender);
}

function callAsRootFrom(AccessConfigSystemType self, address from) internal pure returns (RootCallWrapper memory) {
return RootCallWrapper(self.toResourceId(), from);
}

function toResourceId(AccessConfigSystemType self) internal pure returns (ResourceId) {
return ResourceId.wrap(AccessConfigSystemType.unwrap(self));
}

function fromResourceId(ResourceId resourceId) internal pure returns (AccessConfigSystemType) {
return AccessConfigSystemType.wrap(resourceId.unwrap());
}

function getAddress(AccessConfigSystemType self) internal view returns (address) {
return Systems.getSystem(self.toResourceId());
}

function _world() private view returns (IWorldCall) {
return IWorldCall(StoreSwitch.getStoreAddress());
}
}

/**
* System Function Interfaces
*
* We generate an interface for each system function, which is then used for encoding system calls.
* This is necessary to handle function overloading correctly (which abi.encodeCall cannot).
*
* Each interface is uniquely named based on the function name and parameters to prevent collisions.
*/

interface _configureAccess_ResourceId_bytes4_ResourceId_bytes4 {
function configureAccess(
ResourceId targetSystemId,
bytes4 targetFunctionId,
ResourceId accessSystemId,
bytes4 accessFunctionId
) external;
}

interface _setAccessEnforcement_ResourceId_bytes4_bool {
function setAccessEnforcement(ResourceId targetSystemId, bytes4 targetFunctionId, bool enforced) external;
}

using AccessConfigSystemLib for AccessConfigSystemType global;
using AccessConfigSystemLib for CallWrapper global;
using AccessConfigSystemLib for RootCallWrapper global;
Loading

0 comments on commit bdebf06

Please sign in to comment.