Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(world): deployment salt by msg.sender #2210

Merged
merged 11 commits into from
Jan 30, 2024
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
@@ -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",
},
@@ -70,8 +70,8 @@ describe("createStorageAdapter", async () => {
).toMatchInlineSnapshot(`
[
{
"address": "0x1A33F58dE9acC2645630B4EdC3d3116c755016e0",
"blockNumber": 21n,
"address": "0x2964aF56c8aACdE425978a28b018956D21cF50f0",
"blockNumber": 20n,
"dynamicData": "0x000001a400000045",
"encodedLengths": "0x0000000000000000000000000000000000000000000000000800000000000008",
"isDeleted": false,
@@ -89,7 +89,7 @@ describe("createStorageAdapter", async () => {
expect(tables).toMatchInlineSnapshot(`
[
{
"address": "0x1A33F58dE9acC2645630B4EdC3d3116c755016e0",
"address": "0x2964aF56c8aACdE425978a28b018956D21cF50f0",
"keySchema": {},
"name": "NumberList",
"namespace": "",
@@ -106,7 +106,7 @@ describe("createStorageAdapter", async () => {
[
{
"__keyBytes": "0x",
"__lastUpdatedBlockNumber": 21n,
"__lastUpdatedBlockNumber": 20n,
"value": [
420,
69,
Original file line number Diff line number Diff line change
@@ -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",
},
@@ -68,8 +68,8 @@ describe("createStorageAdapter", async () => {
).toMatchInlineSnapshot(`
[
{
"address": "0x1A33F58dE9acC2645630B4EdC3d3116c755016e0",
"blockNumber": 21n,
"address": "0x2964aF56c8aACdE425978a28b018956D21cF50f0",
"blockNumber": 20n,
"dynamicData": "0x000001a400000045",
"encodedLengths": "0x0000000000000000000000000000000000000000000000000800000000000008",
"isDeleted": false,
16 changes: 8 additions & 8 deletions packages/store-sync/src/sqlite/sqliteStorage.test.ts
Original file line number Diff line number Diff line change
@@ -64,7 +64,7 @@ describe("sqliteStorage", async () => {
{
"chainId": 31337,
"lastError": null,
"lastUpdatedBlockNumber": 21n,
"lastUpdatedBlockNumber": 20n,
"schemaVersion": 1,
},
]
@@ -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,
@@ -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,
@@ -117,7 +117,7 @@ describe("sqliteStorage", async () => {
"__encodedLengths": "0x0000000000000000000000000000000000000000000000000800000000000008",
"__isDeleted": false,
"__key": "0x",
"__lastUpdatedBlockNumber": 21n,
"__lastUpdatedBlockNumber": 20n,
"__staticData": null,
"value": [
420,
6 changes: 3 additions & 3 deletions packages/world/gas-report.json
Original file line number Diff line number Diff line change
@@ -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",
7 changes: 4 additions & 3 deletions packages/world/src/IWorldFactory.sol
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 4 additions & 3 deletions packages/world/src/WorldFactory.sol
Original file line number Diff line number Diff line change
@@ -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) {
@@ -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
52 changes: 46 additions & 6 deletions packages/world/test/Factories.t.sol
Original file line number Diff line number Diff line change
@@ -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);
@@ -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
Loading