-
Notifications
You must be signed in to change notification settings - Fork 188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: World framework v2 #393
Comments
Everything looks great!
you could also require no Might be useful for |
I am worried that the filesystem-like routing will be a constant pain point for us/our users because of the trailing or no-trailing slash. I'm a seasoned engineer and still run into this issue constantly with bash, etc. An alternative that comes to mind is an e.g. colon-separated namespace and wildcards:
Another thing that comes to mind with strings is escaping, if you e.g. want to use the separator in your path name (whether it's In terms of naming, I don't think of "routes" as access control. I wonder if a term like "namespace" is more intuitive (certainly is to me). |
Independent from the separation character we should enforce that a route always starts with the separator and never ends with it to remove the uncertainty. I see how existing associations with Wildcards are hard because of the way we check that a
An array of strings would certainly be explicit here, but I'm a little wary of that option because of the gas overhead (32 bytes When registering a new route, we should definitely check that the new route doesn't include any
I like "namespace" for referring to it in the context of access control! While for accessing a specific resource at some path I personally find "route" more intuitive. In my head its like "a route defines a namespace", you refer to a resource (table, system) with a route and give access based on namespaces, but it all uses the same underlying concept of a route. |
Should routes include special characters at all? (except for the separators) I'd limit them to alphanumeric (and maybe _) |
This is really good stuff! A couple of questions/remarks:
|
Thanks for your comments, really good thoughts!
To avoid any confusion with trailing slashes we decided to never have any trailing slashes anywhere, so neither systems, nor tables, nor
Yeah I agree, the naming is not super clear right now. We're thinking of calling the resources at routes "files" (see #456), and making
So all tables are just storage addresses within the
Part of what you describe is already possible: Addresses with access to a parent route automatically have access to all sub-routes, but the reverse is not true. This is the case because the
Related to the point above, this might have been unclear from the proposal - with nested routes permission inheritance is possible, anyone with access to
Read access is not limited, anyone can ready any data from any table. For the second part I think you're describing something like "Subsystems" - systems that encapsulate some logic for other systems to share, but are not intended to be called directly. For this use case we're currently thinking it might make sense to make the sub systems private (= I might be misunderstanding your point though, let me know if that's the case.
Totally agree, good point!
❤️ |
What prevents a system from using the libraries for a table that it does not have access to?
Yeah, seems I was confused here. I wonder if what I wanted to suggest wasn't the opposite: give the sub-routes access to the parent routes (hence why only the creator could create sub-routes). Feels like often you add new features that need to call into the "lower-level" features, which would maybe more naturally be parent routes (as they were created first).
What I meant is that it would be good to group all logic related to a given table into a given system. This includes data derivation logic (which should be public, ~ view solidity functions) and write logic (which should only be available to people who are allowed to call the system). Access to the table is not enough, as you want to reuse the data derivation logic. Sure, subsystems will solve it (pretty similar to what I said of separate read (data derivation) / write logic), but then you have to write two contracts dealing with the same table, which might not be tons of code each. Adding a read/write distinction on subsystems might be too complex / too much overhead to bother though. It was just an idea. |
Closing this since we started implementing it in v2 and discussion on individual parts is happening in dedicated issues. |
Table of contents
Abstract
This proposal outlines a new version of the World framework, which leverages the Store library to move away from individual component contracts to manage state and instead towards managing state in access-controlled tables (backwards compatible to components) in a central World contract. In addition it proposes a new way of interacting with systems via a central entry point on the World contract. This new architecture promises to be more gas-efficient and powerful, enabling a long list of extensions described in the last part.
Motivation
The goal of the World framework is to facilitate complex and extendable on-chain applications. If used exclusively by a first party developer it can be compared to the Diamond pattern. But the goal of the World framework is allow both first and third party developers to extend existing applications, after they’ve been deployed, to facilitate the creation of Autonomous Worlds.
Issues with the previous approach
In the first version of MUD, the World contract served as a central registry for components and systems and as a central source of update events for indexers and clients to reconstruct the component state. State and logic was separated into components and systems, with components being separate contracts (each managing its own access control) and systems being bound to a World (with the World address stored in their storage). This approach led to a couple issues / inconveniences:
Each system contract can only be part of a single World (unlike in the Diamond pattern where Facets can be part of different diamonds, making it possible to create a new instance of an app by redeploying a single DiamondProxy contract and linking existing Facets to it)
Unnecessary gas overhead for reading from / writing to components from a system
sload
)call
, 1xsload
)call
, 1xsload
/sstore
)→ 2x
call
, 2xsload
overhead in addition to the requiredsload
/sstore
Logic for account delegation or to allow for systems to call other systems (eg Proposal: General approval pattern (for modular systems and session wallets) #327) would have to be implemented in each system separately (since systems are called directly)
Access control managed in each component contract separately means developers have to either manually keep track of which systems need access to which component, or just give all systems access to all components, with the number of permissions to be set scaling quadratically (
number of systems
*number of components
)Relation to Store
In #347 / #352 we added a new core storage paradigm to allow any contract to become compatible with the MUD networking stack (while also saving on storage gas due to more efficient packing).
In this proposal we use the new Store library to move all state to the central World contract instead of individual component contracts, and extend the low level Store functionality with access control for individual storage areas to allow a World app to be extended by independent third party developers while preventing unauthorised storage access.
Design goals
With this proposal we aim to make the World framework:
Basic Concepts
This section goes over the general concepts that are important for this proposal.
Separation of data and logic into tables and systems
msgSender
value forwarded by the World contract.delegatecall
ed from the World, it reads/writes directly to the delegated World storage (using the Store library)call
ed from the World, it reads/writes via access controlled methods on the World contract (by calling the methods onmsg.sender
, which always is the World contract)Routes
/
(like/mudswap/
or/skystrife/round1/
)/mudswap/BalanceTable
or/skystrife/round1/Movementsystem
)/skystrife/
. Only Alice can register tables or systems at/skystrife/
. But Bob can register/skystrife/bobmod/
and then register tables and systems at this new route./
) are called viadelegatecall
, all other systems are called viacall
Access control
/skystrife/
, then Charlie can also access/skystrife/bobmod/PositionTable
/skystrife/Movementsystem
has access to/skystrife/
and therefore has access to all tables and systems registered under/skystrife/
Code exploration
This section includes a “pseudo-code” implementation of the framework described above. The code is close to real Solidity, but has no ambitions to compile or to be complete.
Advanced concepts / extensions
With the basic World framework described above in place, we can add a couple of interesting extensions, described below.
Hooks for system calls
Akin to hooks for state changes in Store, we can add hooks for system calls. These hooks allow the owner of a route to register a “callback function” to be called when the system is called. This can be used for all kinds of use-cases, an example for an ERC20 implementation using this method is described below in “Interface proxy”.
Pseudocode for system call hooks:
Interface proxy
Assume we want to create an ERC20 implementation that lives inside our World, so we can share access to changing balances between different systems.
To conform with the ERC20 spec, we need to implement the ERC20 interface - but systems in our world are called via their route instead of the required function selector.
High level approach to solving this using an “Interface proxy” and system call hooks (see above):
InterfaceProxy
contract implementsERC20
interface andISystemHook
interfaceInterfaceProxy
contract registers its tables on the world, as well as aInterfaceImplementation
system (which has access to the tables)InterfaceProxy
forwards calls to its methods to the respective methods on theInterfaceImplementation
via routes on theWorld
InterfaceProxy
registers itself as hook for calls of theInterfaceImplementation
systemInterfaceImplementation
functions are called (via theWorld
),InterfaceProxy
is notified via itsonCallsystem
function and can emit the requiredERC20
eventsInterfaceImplementation
can either be limited toInterfaceProxy
, or shared with individual other trusted systems, or be public (and implement its own access control checks)Register function selectors for root systems
In the proposal above we have a single entry point on the World contract to call systems (
call
). By allowing the registration of function selectors for root level systems, we can dynamically add more entry points with alternative access control mechanisms! (This is restricted to the owner of the root route (/
) of course.)Pseudocode for function selectors for root systems:
Subsystems
In #268 we explored the concept of “subsystems” as a way to share “stateful” low level logic between different systems. With the new World framework, we can revisit this concept. Now subsystems can just be private systems (that only grant access to other systems at the same base route). To call a system B as subsystem from system A, system A can just delegatecall system B directly. If system A was delegatecalled (as a root system), then all storage and context is forwarded to system B. If system A was called (as a regular system), then the
msg.sender
stays the same (= the World address), so system B can write to itsmsg.sender
(= the World) as usual.Generic account delegation / approval pattern
In addition to the single
call
entry point to call systems described above, we can add an alternativecallFrom
entry point to implement an account delegation / approval pattern similar to the ERC20 pattern of approvals andtransferFrom
. This was already discussed in #327, but unlike in the linked discussion, we only have to add the required logic once, in the World contract, not in every system contract separately. Systems don’t have to care about this, in fact even systems developed before thecallFrom
entry point was added would be compatible by default.Modules
Modules can be a way to abstract the installation of a set of systems and corresponding tables into a World. Modules themselves are verifiable contracts, and could pave the way to an “on-chain library of World extensions”.
For simplicity the “registration methods” in the World pseudocode above were placed into the World contract directly. If instead they would live in a
Registrationsystem
that is registered when the World is created, we could dogfood thecallFrom
entry point described above to let Modules register new tables and systems on behalf of other addresses.Pseudocode of two Module variants:
Multicall
In addition to the World’s
call
entrypoint described above, we could add amulticall
entrypoint to allow multiple systems to be executed in a single atomic transaction. (This could be extended to pipe the output of a system to the next system.)Pseudocode of simple
multicall
entrypoint:Acknowledgements
fallback
method, as well as using delegated storage is based onEIP-2535
(diamond pattern, https://eips.ethereum.org/EIPS/eip-2535)The text was updated successfully, but these errors were encountered: