Cycle

The Scriptorium

Smart Assembly code templates and tools for on-chain development in Eve Frontier.

SA Fundamentals
BeginnerChapter 3 of 520 min read

Writing Your First System

Now that you understand MUD's architecture, let's write a real system. We'll build a simple visitor counter that tracks how many times a Smart Assembly has been interacted with.

Step 1: Define the Table

First, define a table to store visitor counts per assembly:

typescript
// mud.config.ts
import { mudConfig } from "@latticexyz/world/src/mudConfig";

export default mudConfig({
  tables: {
    VisitorCount: {
      keySchema: {
        smartObjectId: "uint256",
      },
      valueSchema: {
        count: "uint32",
        lastVisitor: "address",
        lastVisitTime: "uint256",
      },
    },
  },
});

After updating the config, run code generation:

bash
pnpm mud tablegen

Step 2: Write the System

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { System } from "@latticexyz/world/src/System.sol";
import { VisitorCount } from "../codegen/tables/VisitorCount.sol";

contract VisitorSystem is System {
  error AlreadyVisitedRecently(uint256 smartObjectId, address visitor);

  function recordVisit(uint256 smartObjectId) public {
    address visitor = _msgSender();

    // Read current state
    uint32 currentCount = VisitorCount.getCount(smartObjectId);
    address lastVisitor = VisitorCount.getLastVisitor(smartObjectId);
    uint256 lastTime = VisitorCount.getLastVisitTime(smartObjectId);

    // Prevent spam: same visitor must wait 60 seconds
    if (lastVisitor == visitor && block.timestamp - lastTime < 60) {
      revert AlreadyVisitedRecently(smartObjectId, visitor);
    }

    // Update the table
    VisitorCount.set(
      smartObjectId,
      currentCount + 1,
      visitor,
      block.timestamp
    );
  }

  function getVisitorCount(uint256 smartObjectId) public view returns (uint32) {
    return VisitorCount.getCount(smartObjectId);
  }
}

Step 3: Understanding the Code

Let's break down the key patterns:

Reading State

solidity
uint32 currentCount = VisitorCount.getCount(smartObjectId);

MUD generates getter functions for each column. The key (smartObjectId) identifies which row to read.

Writing State

solidity
VisitorCount.set(smartObjectId, currentCount + 1, visitor, block.timestamp);

The set function writes all columns at once. Order matches valueSchema in the config.

Access Control

solidity
address visitor = _msgSender();

The _msgSender() function (from MUD's System base class) returns the actual caller, even through delegatecall chains.

Step 4: Local Testing

Write a test using Foundry:

solidity
// test/VisitorSystem.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/Test.sol";
import { VisitorSystem } from "../src/systems/VisitorSystem.sol";

contract VisitorSystemTest is Test {
  function test_recordVisit_incrementsCount() public {
    // Setup and test will be covered in a later chapter
  }
}

In the next chapter, we'll explore reading on-chain tables from a frontend.

Sign in to track your progress.