You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Below, a proposal for a steamlined way to call non-root Systems in a way that abstracts away the namespace it's deployed onto:
One of the main concern I have with registering some System methods as World-function selectors are the following:
First, registering those methods as World-function selectors has limits since the available space is only bytes4 (~4,294,967,295 possible function selectors), and so by the time we reach 65536 registered method in the World, the collision chance will be about 50% (birthday paradox). Even though this number seems high, it's really not that high once a given World's "builder" scene takes off.
So, if we had a way that makes not registering function selectors at the World level a viable alternative to build and interact with it, e.g., libraries to inline world.call(SystemId, abi.encodeCall(...)) for them, it would be a preferable approach.
Secondly, calling methods through World-function selectors implies that, in the event a Module is registered more than once, it will break that interface. Right now, MUD's CLI auto-generates name-spaced method ( myNamespace__foo() ), so if someone was building on top of those methods, and the module ends up being redeployed somewhere else, it will break the interface.
Transforming a System interface into it's world.call(..) equivalent is pretty straightforward, right now it needs to be done by hand, which could lead to mismatching errors if said interfaces have to be updated; so following that logic we need something like mud worldgen or mud tablegen, so that libraries can be auto-generated in one command line.
mud modulegen perhaps ?
Let's study a dummy system DummySystem to see how it plays out :
// DummyLib.solimport { Utils } from"./Utils.sol";
interfaceIDummySystem {
function foo(uint256a, uint256b) external;
function bar(uint256a) externalreturns (uint256b);
}
libraryDummyLib {
using Utilsforbytes14;
struct World {
IBaseWorld iface;
bytes14 namespace;
}
function foo(World memoryworld, uint256a, uint256b) internal {
world.iface.call(world.namespace.dummySystemId(),
abi.encodeCall(IDummySystem.foo,
(a, b)
)
);
}
function bar(World memoryworld, uint256a) internalreturns (uint256b) {
bytesmemory result = world.iface.call(world.namespace.dummySystemId(),
abi.encodeCall(IDummySystem.bar,
(a)
)
);
returnabi.decode(result, (uint256));
}
}
From there, is is possible to import DummyLib anywhere, and call our DummySystem methods like this:
// DummyTest.t.solimport { DummyLib } from"../src/DummyLib.sol";
contractDummyTestisTest {
using Utilsforbytes14;
using DummyLibfor DummyLib.World;
using WorldResourceIdInstancefor ResourceId;
IBaseWorld baseWorld;
DummyLib.World dummy;
DummyModule dummyModule;
function setup() public {
// Setting up a Base World and install Dummy System on it through a Module contract
baseWorld =IBaseWorld(address(newWorld()));
baseWorld.initialize(createCoreModule()); // doing some installation stuff, not getting into that it was meant to be sudo code
DummyModule module =newDummyModule();
baseWorld.installModule(module, abi.encode(DUMMY_NAMESPACE));
StoreSwitch.setStoreAddress(address(baseWorld));
// This is the interesting part
dummy = DummyLib.World(baseWorld, DUMMY_NAMESPACE);
}
function testFoo() public {
dummy.foo(123, 456); //just works !assertEq(MyTable.get(DUMMY_NAMESPACE.myTableId(), 123), 456); // true
}
function testBar() public {
testFoo();
uint2356 result = dummy.bar(123);
assertEq(result, 456); // true
}
}
Granted, it needs a little bit of setting things up, but it's fairly straightforward once that's done.
The issue boils down to this: come up with a code-generation script to make a library out of a given System contract ( or its interface), in the same way as shown above
The biggest advantage of doing this is that the interface for Dummy (or rather, the syntax to call Dummy's methods) becomes decoupled from the namespace it's deployed onto, which makes that system fully reusable. It's just a matter of deploying the module on your namespace, and initializing your DummyLib.World structure with the right IBaseWorld and namespace parameters.
Instead of having to use the worldgen interfaces, which are constructed with an appended namespace to each methods (which breaks it if you decide to re-deploy it on another namespace), this bypasses completely World-level function selectors, since we make a direct call to the related system instead
The text was updated successfully, but these errors were encountered:
MerkleBoy
changed the title
Library autogeneration
Library autogeneration for non-root modules
Feb 27, 2024
Below, a proposal for a steamlined way to call non-root Systems in a way that abstracts away the namespace it's deployed onto:
One of the main concern I have with registering some System methods as World-function selectors are the following:
First, registering those methods as World-function selectors has limits since the available space is only bytes4 (~4,294,967,295 possible function selectors), and so by the time we reach 65536 registered method in the World, the collision chance will be about 50% (birthday paradox). Even though this number seems high, it's really not that high once a given World's "builder" scene takes off.
So, if we had a way that makes not registering function selectors at the World level a viable alternative to build and interact with it, e.g., libraries to inline
world.call(SystemId, abi.encodeCall(...))
for them, it would be a preferable approach.Secondly, calling methods through World-function selectors implies that, in the event a Module is registered more than once, it will break that interface. Right now, MUD's CLI auto-generates name-spaced method (
myNamespace__foo()
), so if someone was building on top of those methods, and the module ends up being redeployed somewhere else, it will break the interface.Transforming a System interface into it's
world.call(..)
equivalent is pretty straightforward, right now it needs to be done by hand, which could lead to mismatching errors if said interfaces have to be updated; so following that logic we need something likemud worldgen
ormud tablegen
, so that libraries can be auto-generated in one command line.mud modulegen
perhaps ?Let's study a dummy system
DummySystem
to see how it plays out :From there, is is possible to import
DummyLib
anywhere, and call our DummySystem methods like this:Granted, it needs a little bit of setting things up, but it's fairly straightforward once that's done.
The issue boils down to this: come up with a code-generation script to make a library out of a given System contract ( or its interface), in the same way as shown above
The biggest advantage of doing this is that the interface for
Dummy
(or rather, the syntax to callDummy
's methods) becomes decoupled from the namespace it's deployed onto, which makes that system fully reusable. It's just a matter of deploying the module on your namespace, and initializing yourDummyLib.World
structure with the rightIBaseWorld
andnamespace
parameters.Instead of having to use the
worldgen
interfaces, which are constructed with an appendednamespace
to each methods (which breaks it if you decide to re-deploy it on anothernamespace
), this bypasses completely World-level function selectors, since we make a direct call to the related system insteadThe text was updated successfully, but these errors were encountered: