System Best Practices
Security considerations
Avoid the root namespace if possible
A System in the root namespace costs slightly less gas because it uses the World context, including storage, and does not need to call the World back to read or write information.
However, because it runs in the World context, there are security risks in writing such a System.
- A root namespace
Systemcan overwrite information in tables. This means, for example, it can create any delegation that it wants by modifyingworld__UserDelegationControl. - Any ETH owned by a namespace in the
Worldis actually stored in theWorldand therefore can be sent anywhere by such aSystem. - A root namespace
Systemcan call any otherSystemwith arbitrary values for_msgSender()and_msgValue(). This means it can perform any action while pretending to be any user.
This violates the security principle of least privilege (opens in a new tab). Ideally, you should avoid running in the root namespace.
If you have to use a System in the root namespace, avoid using
delegatecall (opens in a new tab),
except for libraries whose security you verified. Any contract you call with delegatecall is going to inherit the
World context, and have unlimited permissions on your application.
Design consideraions
One call, one action
If you want a transaction to do multiple actions (for example: register, start a game, and then perform the first move in the game), you don't need a DoThis_DoThat_AndThen_DoTheOtherThing function.
Just use batch calls, which preserve the message sender (in `_msgSender()).
Use delegation for user agents
If a System is going to act on the users' behalf, it will need to call other Systems, probably in other namespaces.
The way to do this is to request users to delegate their permissions.
This is necessary because:
- If you call another
Systemthrough theWorld, the message sender (_msgSender()) is the callingSystem. The calledSystemhas no way of knowing if it can trust the callerSystemto represent the wishes of the ultimate user. See the Solidity documentation (opens in a new tab) for an in-depth explanation. - If you
delegatecallanotherSystemdirectly you keep_msgSender()'s value, but the calledSystemis only allowed to perform actions allowed to the callingSystem.
Don't send ETH with your calls
The way ETH balances work in MUD, all ETH is stored in the World contract and internally accounted as belonging to namespaces.
If your System is in the root namespace it has access to this ETH, but if you transfer out except through BalanceTransferSystem (opens in a new tab), you need to update the accounting table.
If your System is not in the root namespace it does not have direct access to the ETH anyway.
You have to use BalanceTransferSystem.
Implementation considerations
Use libraries to bypass the contract size limit
If you have a large System and you come up against the contract size limit (opens in a new tab), don't split it in two.
Instead, use public libraries (opens in a new tab).
Libraries are called using delegatecall (opens in a new tab), so they have the same permissions as the calling contract.
This includes the ability to write to MUD tables when the calling System is permitted to do so.