Skip to content

Commit

Permalink
feat(world): deployment salt by msg.sender (#2210)
Browse files Browse the repository at this point in the history
Co-authored-by: Kevin Ingersoll <kingersoll@gmail.com>
  • Loading branch information
yonadaaa and holic committed Jan 30, 2024
1 parent aabd307 commit 6470fe1
Show file tree
Hide file tree
Showing 9 changed files with 540 additions and 493 deletions.
5 changes: 5 additions & 0 deletions .changeset/selfish-pears-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/world": minor
---

`WorldFactory` now derives a salt based on number of worlds deployed by `msg.sender`, which should help with predictable world deployments across chains.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe("createStorageAdapter", async () => {
expect(await db.select().from(storageAdapter.tables.configTable)).toMatchInlineSnapshot(`
[
{
"blockNumber": 21n,
"blockNumber": 20n,
"chainId": 31337,
"version": "0.0.4",
},
Expand All @@ -70,8 +70,8 @@ describe("createStorageAdapter", async () => {
).toMatchInlineSnapshot(`
[
{
"address": "0x1A33F58dE9acC2645630B4EdC3d3116c755016e0",
"blockNumber": 21n,
"address": "0x2964aF56c8aACdE425978a28b018956D21cF50f0",
"blockNumber": 20n,
"dynamicData": "0x000001a400000045",
"encodedLengths": "0x0000000000000000000000000000000000000000000000000800000000000008",
"isDeleted": false,
Expand All @@ -89,7 +89,7 @@ describe("createStorageAdapter", async () => {
expect(tables).toMatchInlineSnapshot(`
[
{
"address": "0x1A33F58dE9acC2645630B4EdC3d3116c755016e0",
"address": "0x2964aF56c8aACdE425978a28b018956D21cF50f0",
"keySchema": {},
"name": "NumberList",
"namespace": "",
Expand All @@ -106,7 +106,7 @@ describe("createStorageAdapter", async () => {
[
{
"__keyBytes": "0x",
"__lastUpdatedBlockNumber": 21n,
"__lastUpdatedBlockNumber": 20n,
"value": [
420,
69,
Expand Down
6 changes: 3 additions & 3 deletions packages/store-sync/src/postgres/createStorageAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe("createStorageAdapter", async () => {
expect(await db.select().from(storageAdapter.tables.configTable)).toMatchInlineSnapshot(`
[
{
"blockNumber": 21n,
"blockNumber": 20n,
"chainId": 31337,
"version": "0.0.4",
},
Expand All @@ -68,8 +68,8 @@ describe("createStorageAdapter", async () => {
).toMatchInlineSnapshot(`
[
{
"address": "0x1A33F58dE9acC2645630B4EdC3d3116c755016e0",
"blockNumber": 21n,
"address": "0x2964aF56c8aACdE425978a28b018956D21cF50f0",
"blockNumber": 20n,
"dynamicData": "0x000001a400000045",
"encodedLengths": "0x0000000000000000000000000000000000000000000000000800000000000008",
"isDeleted": false,
Expand Down
16 changes: 8 additions & 8 deletions packages/store-sync/src/sqlite/sqliteStorage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe("sqliteStorage", async () => {
{
"chainId": 31337,
"lastError": null,
"lastUpdatedBlockNumber": 21n,
"lastUpdatedBlockNumber": 20n,
"schemaVersion": 1,
},
]
Expand All @@ -73,11 +73,11 @@ describe("sqliteStorage", async () => {
expect(db.select().from(mudStoreTables).where(eq(mudStoreTables.name, "NumberList")).all()).toMatchInlineSnapshot(`
[
{
"address": "0x1A33F58dE9acC2645630B4EdC3d3116c755016e0",
"id": "0x1A33F58dE9acC2645630B4EdC3d3116c755016e0____NumberList",
"address": "0x2964aF56c8aACdE425978a28b018956D21cF50f0",
"id": "0x2964aF56c8aACdE425978a28b018956D21cF50f0____NumberList",
"keySchema": {},
"lastError": null,
"lastUpdatedBlockNumber": 21n,
"lastUpdatedBlockNumber": 20n,
"name": "NumberList",
"namespace": "",
"schemaVersion": 1,
Expand All @@ -93,11 +93,11 @@ describe("sqliteStorage", async () => {
expect(tables).toMatchInlineSnapshot(`
[
{
"address": "0x1A33F58dE9acC2645630B4EdC3d3116c755016e0",
"id": "0x1A33F58dE9acC2645630B4EdC3d3116c755016e0____NumberList",
"address": "0x2964aF56c8aACdE425978a28b018956D21cF50f0",
"id": "0x2964aF56c8aACdE425978a28b018956D21cF50f0____NumberList",
"keySchema": {},
"lastError": null,
"lastUpdatedBlockNumber": 21n,
"lastUpdatedBlockNumber": 20n,
"name": "NumberList",
"namespace": "",
"schemaVersion": 1,
Expand All @@ -117,7 +117,7 @@ describe("sqliteStorage", async () => {
"__encodedLengths": "0x0000000000000000000000000000000000000000000000000800000000000008",
"__isDeleted": false,
"__key": "0x",
"__lastUpdatedBlockNumber": 21n,
"__lastUpdatedBlockNumber": 20n,
"__staticData": null,
"value": [
420,
Expand Down
6 changes: 3 additions & 3 deletions packages/world/gas-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@
"file": "test/Factories.t.sol",
"test": "testCreate2Factory",
"name": "deploy contract via Create2",
"gasUsed": 4734151
"gasUsed": 4768291
},
{
"file": "test/Factories.t.sol",
"test": "testWorldFactory",
"test": "testWorldFactoryGas",
"name": "deploy world via WorldFactory",
"gasUsed": 12961592
"gasUsed": 12961878
},
{
"file": "test/World.t.sol",
Expand Down
7 changes: 4 additions & 3 deletions packages/world/src/IWorldFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ interface IWorldFactory {
event WorldDeployed(address indexed newContract);

/**
* @notice Returns the total count of deployed World contracts.
* @return The total number of World contracts deployed by this factory.
* @notice Returns the total count of deployed World contracts per account.
* @param account The account.
* @return The total number of World contracts deployed by this factory per account.
*/
function worldCount() external view returns (uint256);
function worldCounts(address account) external view returns (uint256);

/**
* @notice Deploys a new World contract.
Expand Down
7 changes: 4 additions & 3 deletions packages/world/src/WorldFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ contract WorldFactory is IWorldFactory {
/// @notice Address of the core module to be set in the World instances.
IModule public immutable coreModule;

/// @notice Counter to keep track of the number of World instances deployed.
uint256 public worldCount;
/// @notice Counters to keep track of the number of World instances deployed per address.
mapping(address creator => uint256 worldCount) public worldCounts;

/// @param _coreModule The address of the core module.
constructor(IModule _coreModule) {
Expand All @@ -33,7 +33,8 @@ contract WorldFactory is IWorldFactory {
function deployWorld() public returns (address worldAddress) {
// Deploy a new World and increase the WorldCount
bytes memory bytecode = type(World).creationCode;
worldAddress = Create2.deploy(bytecode, worldCount++);
uint256 salt = uint256(keccak256(abi.encode(msg.sender, worldCounts[msg.sender]++)));
worldAddress = Create2.deploy(bytecode, salt);
IBaseWorld world = IBaseWorld(worldAddress);

// Initialize the World and transfer ownership to the caller
Expand Down
52 changes: 46 additions & 6 deletions packages/world/test/Factories.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,23 @@ contract FactoriesTest is Test, GasReporter {

// Confirm worldFactory was deployed correctly
IWorldFactory worldFactory = IWorldFactory(calculatedAddress);
assertEq(uint256(worldFactory.worldCount()), uint256(0));
assertEq(uint256(worldFactory.worldCounts(address(0))), uint256(0));
}

function testWorldFactory() public {
function testWorldFactory(address account) public {
vm.startPrank(account);

// Deploy WorldFactory with current CoreModule
CoreModule coreModule = createCoreModule();
address worldFactoryAddress = address(new WorldFactory(coreModule));
IWorldFactory worldFactory = IWorldFactory(worldFactoryAddress);

// Address we expect for World
address calculatedAddress = calculateAddress(worldFactoryAddress, bytes32(0), type(World).creationCode);
address calculatedAddress = calculateAddress(
worldFactoryAddress,
keccak256(abi.encode(account, 0)),
type(World).creationCode
);

// Check for HelloWorld event from World
vm.expectEmit(true, true, true, true);
Expand All @@ -77,13 +83,47 @@ contract FactoriesTest is Test, GasReporter {
// Set the store address manually
StoreSwitch.setStoreAddress(calculatedAddress);

// Confirm accountCount (which is salt) has incremented
assertEq(uint256(worldFactory.worldCounts(account)), uint256(1));

// Confirm correct Core is installed
assertTrue(InstalledModules.get(address(coreModule), keccak256(new bytes(0))));

// Confirm worldCount (which is salt) has incremented
assertEq(uint256(worldFactory.worldCount()), uint256(1));
// Confirm the msg.sender is owner of the root namespace of the new world
assertEq(NamespaceOwner.get(ROOT_NAMESPACE_ID), account);

// Deploy another world

// Address we expect for World
calculatedAddress = calculateAddress(
worldFactoryAddress,
keccak256(abi.encode(account, 1)),
type(World).creationCode
);

// Check for HelloWorld event from World
vm.expectEmit(true, true, true, true);
emit HelloWorld(WORLD_VERSION);

// Check for WorldDeployed event from Factory
vm.expectEmit(true, false, false, false);
emit WorldDeployed(calculatedAddress);
worldFactory.deployWorld();

// Confirm accountCount (which is salt) has incremented
assertEq(uint256(worldFactory.worldCounts(account)), uint256(2));

// Set the store address manually
StoreSwitch.setStoreAddress(calculatedAddress);

// Confirm correct Core is installed
assertTrue(InstalledModules.get(address(coreModule), keccak256(new bytes(0))));

// Confirm the msg.sender is owner of the root namespace of the new world
assertEq(NamespaceOwner.get(ROOT_NAMESPACE_ID), address(this));
assertEq(NamespaceOwner.get(ROOT_NAMESPACE_ID), account);
}

function testWorldFactoryGas() public {
testWorldFactory(address(this));
}
}
Loading

0 comments on commit 6470fe1

Please sign in to comment.