MUD for Solidity Programmers
To properly use MUD you need to do some things differently than the way you do them normally in Solidity.
State variables
A System
contract should not store any internal state, but only interact with World
state via Tables
.
- By concentrating all the state information in a single contract (the
World
) we make it easier to apply access control, execute hooks when data changes, etc. - It is a lot easier to upgrade functionality when you do not need to deal with storage because the contract does not have a state.
- The same
System
can be used by multipleWorld
s.
Instead of using state variables (opens in a new tab) you can achieve the same functionality with tables.
Value-type (string
, uint
, etc.)
Value-type (opens in a new tab) state variables such as bool
, uint256
, and string
are represented by a singleton table, a table with no key that only contains a single record.
Code example
contract Example {
string name;
}
import { defineWorld } from "@latticexyz/world";
export default defineWorld({
namespace: "app",
tables: {
Name: {
schema: {
name: "string",
},
key: [],
},
},
});
Structures
A struct
(opens in a new tab) state variable wraps multiple variables together. You can create a singleton table with multiple value fields.
Code example
contract Example {
struct Person {
string name;
address personalWallet;
uint balance;
}
Person deployer;
}
import { defineWorld } from "@latticexyz/world";
export default defineWorld({
namespace: "app",
tables: {
Deployer: {
schema: {
personalWallet: "address",
balance: "uint256",
name: "string",
},
key: [],
},
},
});
Mappings
A mapping type (opens in a new tab) maps between a key type and a value type, which is pretty much the same as what MUD tables do.
Single key mapping
contract Example {
mapping(address => uint) public balances;
}
import { defineWorld } from "@latticexyz/world";
export default defineWorld({
namespace: "app",
tables: {
Balances: {
schema: {
owner: "address",
balance: "uint256",
},
key: ["owner"],
},
},
});
Multiple key mapping
contract Example {
mapping(address => mapping(address => uint256)) public allowances;
}
import { defineWorld } from "@latticexyz/world";
export default defineWorld({
namespace: "app",
tables: {
Allowances: {
schema: {
owner: "address",
spender: "address",
balance: "uint256",
},
key: ["owner", "spender"],
},
},
});
MUD key fields have to be fixed-length.
This means that some mappings, for example mapping(string => uint) balances;
, cannot be directly translated to a MUD table.
An easy workaround is to use the hash of the string as the key instead of the string itself (which is exactly what happens under the hood of vanilla Solidity with the keys of a mapping).
Arrays
There are two ways in which MUD supports arrays.
-
Arrays of fixed-length value types, such as
uint24[]
oraddress[]
, are supported as table fields (opens in a new tab).Example
Soliditycontract Example { bool[] listOfBooleans; }
MUDimport { defineWorld } from "@latticexyz/world"; export default defineWorld({ namespace: "app", tables: { ListOfBooleans: { schema: { values: "bool[]", }, key: [], }, }, });
-
An array is a mapping between an unsigned integer and the value type of the array. We can use a table with
index
as the key. That way, we can get around the limitation that arrays of variable-length types such asstring
orbytes
are not supported.Example
Soliditycontract Example { string[] listOfNames; }
MUDimport { defineWorld } from "@latticexyz/world"; export default defineWorld({ namespace: "app", tables: { ListOfNames: { schema: { index: "uint256", name: "string", }, key: ["index"], }, }, });