Skip to content

Commit

Permalink
feat(world): add CallBatchSystem to core module (#1500)
Browse files Browse the repository at this point in the history
Co-authored-by: alvarius <alvarius@lattice.xyz>
  • Loading branch information
yonadaaa and alvrs committed Sep 21, 2023
1 parent 3d49b84 commit 95c59b2
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 22 deletions.
13 changes: 13 additions & 0 deletions .changeset/wild-nails-wonder.md
@@ -0,0 +1,13 @@
---
"@latticexyz/world": minor
---

The `World` now has a `callBatch` method which allows multiple system calls to be batched into a single transaction.

```solidity
import { SystemCallData } from "@latticexyz/world/modules/core/types.sol";
interface IBaseWorld {
function callBatch(SystemCallData[] calldata systemCalls) external returns (bytes[] memory returnDatas);
}
```
48 changes: 27 additions & 21 deletions packages/world/gas-report.json
Expand Up @@ -35,17 +35,23 @@
"name": "AccessControl: requireAccess (this address)",
"gasUsed": 153
},
{
"file": "test/CallBatch.t.sol",
"test": "testCallBatchReturnData",
"name": "call systems with callBatch",
"gasUsed": 45546
},
{
"file": "test/KeysInTableModule.t.sol",
"test": "testInstallComposite",
"name": "install keys in table module",
"gasUsed": 1414653
"gasUsed": 1415069
},
{
"file": "test/KeysInTableModule.t.sol",
"test": "testInstallGas",
"name": "install keys in table module",
"gasUsed": 1414653
"gasUsed": 1415069
},
{
"file": "test/KeysInTableModule.t.sol",
Expand All @@ -57,13 +63,13 @@
"file": "test/KeysInTableModule.t.sol",
"test": "testInstallSingleton",
"name": "install keys in table module",
"gasUsed": 1414653
"gasUsed": 1415069
},
{
"file": "test/KeysInTableModule.t.sol",
"test": "testSetAndDeleteRecordHookCompositeGas",
"name": "install keys in table module",
"gasUsed": 1414653
"gasUsed": 1415069
},
{
"file": "test/KeysInTableModule.t.sol",
Expand All @@ -81,7 +87,7 @@
"file": "test/KeysInTableModule.t.sol",
"test": "testSetAndDeleteRecordHookGas",
"name": "install keys in table module",
"gasUsed": 1414653
"gasUsed": 1415069
},
{
"file": "test/KeysInTableModule.t.sol",
Expand All @@ -99,7 +105,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testGetKeysWithValueGas",
"name": "install keys with value module",
"gasUsed": 665378
"gasUsed": 665448
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -117,7 +123,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testInstall",
"name": "install keys with value module",
"gasUsed": 665378
"gasUsed": 665448
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -129,7 +135,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testSetAndDeleteRecordHook",
"name": "install keys with value module",
"gasUsed": 665378
"gasUsed": 665448
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -147,7 +153,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testSetField",
"name": "install keys with value module",
"gasUsed": 665378
"gasUsed": 665448
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand Down Expand Up @@ -231,7 +237,7 @@
"file": "test/StandardDelegationsModule.t.sol",
"test": "testCallFromCallboundDelegation",
"name": "register a callbound delegation",
"gasUsed": 114589
"gasUsed": 114729
},
{
"file": "test/StandardDelegationsModule.t.sol",
Expand All @@ -243,7 +249,7 @@
"file": "test/StandardDelegationsModule.t.sol",
"test": "testCallFromTimeboundDelegation",
"name": "register a timebound delegation",
"gasUsed": 109084
"gasUsed": 109224
},
{
"file": "test/StandardDelegationsModule.t.sol",
Expand All @@ -255,7 +261,7 @@
"file": "test/UniqueEntityModule.t.sol",
"test": "testInstall",
"name": "install unique entity module",
"gasUsed": 690431
"gasUsed": 690539
},
{
"file": "test/UniqueEntityModule.t.sol",
Expand All @@ -267,7 +273,7 @@
"file": "test/UniqueEntityModule.t.sol",
"test": "testInstallRoot",
"name": "installRoot unique entity module",
"gasUsed": 680681
"gasUsed": 680735
},
{
"file": "test/UniqueEntityModule.t.sol",
Expand All @@ -285,7 +291,7 @@
"file": "test/World.t.sol",
"test": "testCallFromUnlimitedDelegation",
"name": "register an unlimited delegation",
"gasUsed": 50629
"gasUsed": 50617
},
{
"file": "test/World.t.sol",
Expand All @@ -309,43 +315,43 @@
"file": "test/World.t.sol",
"test": "testRegisterFallbackSystem",
"name": "Register a fallback system",
"gasUsed": 59306
"gasUsed": 59347
},
{
"file": "test/World.t.sol",
"test": "testRegisterFallbackSystem",
"name": "Register a root fallback system",
"gasUsed": 52950
"gasUsed": 52972
},
{
"file": "test/World.t.sol",
"test": "testRegisterFunctionSelector",
"name": "Register a function selector",
"gasUsed": 79872
"gasUsed": 79913
},
{
"file": "test/World.t.sol",
"test": "testRegisterNamespace",
"name": "Register a new namespace",
"gasUsed": 123269
"gasUsed": 123225
},
{
"file": "test/World.t.sol",
"test": "testRegisterRootFunctionSelector",
"name": "Register a root function selector",
"gasUsed": 74863
"gasUsed": 74885
},
{
"file": "test/World.t.sol",
"test": "testRegisterSystem",
"name": "register a system",
"gasUsed": 165750
"gasUsed": 165772
},
{
"file": "test/World.t.sol",
"test": "testRegisterTable",
"name": "Register a new table in the namespace",
"gasUsed": 651802
"gasUsed": 651898
},
{
"file": "test/World.t.sol",
Expand Down
2 changes: 2 additions & 0 deletions packages/world/src/interfaces/IBaseWorld.sol
Expand Up @@ -9,6 +9,7 @@ import { IWorldKernel } from "../interfaces/IWorldKernel.sol";
import { ICoreSystem } from "./ICoreSystem.sol";
import { IAccessManagementSystem } from "./IAccessManagementSystem.sol";
import { IBalanceTransferSystem } from "./IBalanceTransferSystem.sol";
import { ICallBatchSystem } from "./ICallBatchSystem.sol";
import { IModuleInstallationSystem } from "./IModuleInstallationSystem.sol";
import { IWorldRegistrationSystem } from "./IWorldRegistrationSystem.sol";

Expand All @@ -22,6 +23,7 @@ interface IBaseWorld is
ICoreSystem,
IAccessManagementSystem,
IBalanceTransferSystem,
ICallBatchSystem,
IModuleInstallationSystem,
IWorldRegistrationSystem
{
Expand Down
10 changes: 10 additions & 0 deletions packages/world/src/interfaces/ICallBatchSystem.sol
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.21;

/* Autogenerated file. Do not edit manually. */

import { SystemCallData } from "./../modules/core/types.sol";

interface ICallBatchSystem {
function callBatch(SystemCallData[] calldata systemCalls) external returns (bytes[] memory returnDatas);
}
5 changes: 4 additions & 1 deletion packages/world/src/modules/core/CoreModule.sol
Expand Up @@ -28,6 +28,7 @@ import { Balances } from "./tables/Balances.sol";

import { AccessManagementSystem } from "./implementations/AccessManagementSystem.sol";
import { BalanceTransferSystem } from "./implementations/BalanceTransferSystem.sol";
import { CallBatchSystem } from "./implementations/CallBatchSystem.sol";
import { ModuleInstallationSystem } from "./implementations/ModuleInstallationSystem.sol";
import { StoreRegistrationSystem } from "./implementations/StoreRegistrationSystem.sol";
import { WorldRegistrationSystem } from "./implementations/WorldRegistrationSystem.sol";
Expand Down Expand Up @@ -102,14 +103,16 @@ contract CoreModule is Module {
* Register function selectors for all CoreSystem functions in the World
*/
function _registerFunctionSelectors() internal {
bytes4[16] memory functionSelectors = [
bytes4[17] memory functionSelectors = [
// --- AccessManagementSystem ---
AccessManagementSystem.grantAccess.selector,
AccessManagementSystem.revokeAccess.selector,
AccessManagementSystem.transferOwnership.selector,
// --- BalanceTransferSystem ---
BalanceTransferSystem.transferBalanceToNamespace.selector,
BalanceTransferSystem.transferBalanceToAddress.selector,
// --- CallBatchSystem ---
CallBatchSystem.callBatch.selector,
// --- ModuleInstallationSystem ---
ModuleInstallationSystem.installModule.selector,
// --- StoreRegistrationSystem ---
Expand Down
2 changes: 2 additions & 0 deletions packages/world/src/modules/core/CoreSystem.sol
Expand Up @@ -5,6 +5,7 @@ import { IWorldErrors } from "../../interfaces/IWorldErrors.sol";

import { AccessManagementSystem } from "./implementations/AccessManagementSystem.sol";
import { BalanceTransferSystem } from "./implementations/BalanceTransferSystem.sol";
import { CallBatchSystem } from "./implementations/CallBatchSystem.sol";
import { ModuleInstallationSystem } from "./implementations/ModuleInstallationSystem.sol";
import { StoreRegistrationSystem } from "./implementations/StoreRegistrationSystem.sol";
import { WorldRegistrationSystem } from "./implementations/WorldRegistrationSystem.sol";
Expand All @@ -17,6 +18,7 @@ contract CoreSystem is
IWorldErrors,
AccessManagementSystem,
BalanceTransferSystem,
CallBatchSystem,
ModuleInstallationSystem,
StoreRegistrationSystem,
WorldRegistrationSystem
Expand Down
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import { System } from "../../../System.sol";
import { IBaseWorld } from "../../../interfaces/IBaseWorld.sol";
import { revertWithBytes } from "../../../revertWithBytes.sol";

import { SystemCallData } from "../types.sol";

contract CallBatchSystem is System {

This comment has been minimized.

Copy link
@holic

holic Sep 22, 2023

Member

I still liked BatchCallSystem and batchCall 🙈

This comment has been minimized.

Copy link
@alvrs

alvrs Sep 22, 2023

Author Member

Even though we have callFrom? 😱 call, callFrom, callBatch?

This comment has been minimized.

Copy link
@holic

holic Sep 23, 2023

Member

good point!

This comment has been minimized.

Copy link
@holic

holic Sep 23, 2023

Member

I guess that brings up another point: should we have batchCallFrom?

This comment has been minimized.

Copy link
@alvrs

alvrs Sep 23, 2023

Author Member

you mean callFromBatch 🙈? jokes aside, i agree batchCallFrom would be a useful addition to this system, an in that case batchCall and batchCallFrom would make more sense

/**
* Batch calls to multiple systems into a single transaction, return the array of return data.
*/
function callBatch(SystemCallData[] calldata systemCalls) public returns (bytes[] memory returnDatas) {
IBaseWorld world = IBaseWorld(_world());
returnDatas = new bytes[](systemCalls.length);

for (uint256 i; i < systemCalls.length; i++) {
(bool success, bytes memory returnData) = address(world).delegatecall(
abi.encodeCall(world.call, (systemCalls[i].systemId, systemCalls[i].callData))
);
if (!success) revertWithBytes(returnData);

returnDatas[i] = abi.decode(returnData, (bytes));

This comment has been minimized.

Copy link
@holic

holic Sep 22, 2023

Member

why do we abi decode a thing that is already bytes?

This comment has been minimized.

Copy link
@alvrs

alvrs Sep 22, 2023

Author Member

it's nested - the World's call function returns the value of the system call encoded as bytes, and the low level delegatecall returns the bytes value returned by world.call encoded as bytes, so we decode once to get back the actual value returned by the system

}
}
}
9 changes: 9 additions & 0 deletions packages/world/src/modules/core/types.sol
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.21;

import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";

struct SystemCallData {
ResourceId systemId;
bytes callData;
}

0 comments on commit 95c59b2

Please sign in to comment.