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
---

Added `msg.sender` to the `WorldFactory` deployment salt, so the World address is derived from the creator of the world.
yonadaa marked this conversation as resolved.
Show resolved Hide resolved
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": 4763472
"gasUsed": 4797613
},
{
"file": "test/Factories.t.sol",
"test": "testWorldFactory",
"test": "testWorldFactoryGas",
"name": "deploy world via WorldFactory",
"gasUsed": 13045360
"gasUsed": 13045646
},
{
"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 accountCount(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 => uint256) public accountCount;
yonadaa marked this conversation as resolved.
Show resolved Hide resolved

/// @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, accountCount[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.accountCount(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.accountCount(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.accountCount(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