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

refactor: split most of Factory out to FactoryLib #443

Merged
merged 3 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions script/deploy-atlas.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "forge-std/Test.sol";

import { DeployBaseScript } from "./base/deploy-base.s.sol";

import { FactoryLib } from "../src/contracts/atlas/FactoryLib.sol";
import { Atlas } from "../src/contracts/atlas/Atlas.sol";
import { AtlasVerification } from "../src/contracts/atlas/AtlasVerification.sol";
import { TxBuilder } from "../src/contracts/helpers/TxBuilder.sol";
Expand All @@ -27,9 +28,9 @@ contract DeployAtlasScript is DeployBaseScript {
address deployer = vm.addr(deployerPrivateKey);

// Computes the addresses at which AtlasVerification will be deployed
address expectedAtlasAddr = vm.computeCreateAddress(deployer, vm.getNonce(deployer) + 1);
address expectedAtlasVerificationAddr = vm.computeCreateAddress(deployer, vm.getNonce(deployer) + 2);
address expectedSimulatorAddr = vm.computeCreateAddress(deployer, vm.getNonce(deployer) + 3);
address expectedAtlasAddr = vm.computeCreateAddress(deployer, vm.getNonce(deployer) + 2);
address expectedAtlasVerificationAddr = vm.computeCreateAddress(deployer, vm.getNonce(deployer) + 3);
address expectedSimulatorAddr = vm.computeCreateAddress(deployer, vm.getNonce(deployer) + 4);

address prevSimAddr = _getAddressFromDeploymentsJson("SIMULATOR");
uint256 prevSimBalance = (prevSimAddr == address(0)) ? 0 : prevSimAddr.balance;
Expand All @@ -40,13 +41,14 @@ contract DeployAtlasScript is DeployBaseScript {
vm.startBroadcast(deployerPrivateKey);

ExecutionEnvironment execEnvTemplate = new ExecutionEnvironment(expectedAtlasAddr);
FactoryLib factoryLib = new FactoryLib(address(execEnvTemplate));
atlas = new Atlas({
escrowDuration: ESCROW_DURATION,
atlasSurchargeRate: ATLAS_SURCHARGE_RATE,
bundlerSurchargeRate: BUNDLER_SURCHARGE_RATE,
verification: expectedAtlasVerificationAddr,
simulator: expectedSimulatorAddr,
executionTemplate: address(execEnvTemplate),
factoryLib: address(factoryLib),
initialSurchargeRecipient: deployer,
l2GasCalculator: address(0)
});
Expand Down Expand Up @@ -110,6 +112,16 @@ contract DeployAtlasScript is DeployBaseScript {
console.log("ERROR: Sorter deployment address is 0x0");
error = true;
}
// Check FactoryLib address set correctly in Atlas
if (address(factoryLib) != atlas.FACTORY_LIB()) {
console.log("ERROR: FactoryLib address not set correctly in Atlas");
error = true;
}
// Check ExecutionEnvironment address set correctly in FactoryLib
if (address(execEnvTemplate) != factoryLib.EXECUTION_ENV_TEMPLATE()) {
console.log("ERROR: ExecutionEnvironment address not set correctly in FactoryLib");
error = true;
}
// Check ESCROW_DURATION was not set to 0
if (atlas.ESCROW_DURATION() == 0) {
console.log("ERROR: ESCROW_DURATION was set to 0");
Expand Down
5 changes: 2 additions & 3 deletions src/contracts/atlas/Atlas.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ contract Atlas is Escrow, Factory {
address simulator,
address initialSurchargeRecipient,
address l2GasCalculator,
address executionTemplate
address factoryLib
)
Escrow(
escrowDuration,
Expand All @@ -45,7 +45,7 @@ contract Atlas is Escrow, Factory {
initialSurchargeRecipient,
l2GasCalculator
)
Factory(executionTemplate)
Factory(factoryLib)
{ }

/// @notice metacall is the entrypoint function for the Atlas transactions.
Expand Down Expand Up @@ -371,7 +371,6 @@ contract Atlas is Escrow, Factory {
uint32 callConfig
)
internal
view
override
returns (bool)
{
Expand Down
112 changes: 19 additions & 93 deletions src/contracts/atlas/Factory.sol
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
//SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import { FactoryLib } from "./FactoryLib.sol";

import { IDAppControl } from "../interfaces/IDAppControl.sol";
import { Mimic } from "../common/Mimic.sol";
import { DAppConfig } from "../types/ConfigTypes.sol";
import { UserOperation } from "../types/UserOperation.sol";
import { AtlasEvents } from "../types/AtlasEvents.sol";
import { AtlasErrors } from "../types/AtlasErrors.sol";

/// @title Factory
/// @author FastLane Labs
/// @notice Provides functionality for creating and managing execution environments for DApps within the Atlas Protocol.
/// @dev This contract uses deterministic deployment to generate and manage Execution Environment instances based on
/// predefined templates.
abstract contract Factory {
address public immutable EXECUTION_ENV_TEMPLATE;
address public immutable FACTORY_LIB;
bytes32 internal immutable _FACTORY_BASE_SALT;

/// @notice Initializes a new Factory contract instance by setting the immutable salt for deterministic deployment
/// of Execution Environments and storing the execution template address.
/// @dev The Execution Environment Template must be separately deployed using the same calculated salt.
/// @param executionTemplate Address of the pre-deployed execution template contract for creating Execution
/// Environment instances.
constructor(address executionTemplate) {
EXECUTION_ENV_TEMPLATE = executionTemplate;
constructor(address factoryLib) {
FACTORY_LIB = factoryLib;
_FACTORY_BASE_SALT = keccak256(abi.encodePacked(block.chainid, address(this)));
}

Expand Down Expand Up @@ -57,7 +47,6 @@ abstract contract Factory {
address control
)
external
view
returns (address executionEnvironment, uint32 callConfig, bool exists)
{
callConfig = IDAppControl(control).CALL_CONFIG();
Expand Down Expand Up @@ -100,25 +89,13 @@ abstract contract Factory {
internal
returns (address executionEnvironment)
{
bytes memory _creationCode = _getMimicCreationCode({ user: user, control: control, callConfig: callConfig });
bytes32 _salt = _computeSalt(user, control, callConfig);

executionEnvironment = address(
uint160(
uint256(
keccak256(
abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(abi.encodePacked(_creationCode)))
)
)
)
bytes memory returnData = _delegatecallFactoryLib(
abi.encodeCall(FactoryLib.getOrCreateExecutionEnvironment, (user, control, callConfig, _salt))
);

if (executionEnvironment.code.length == 0) {
assembly {
executionEnvironment := create2(0, add(_creationCode, 32), mload(_creationCode), _salt)
}
emit AtlasEvents.ExecutionEnvironmentCreated(user, executionEnvironment);
}
return abi.decode(returnData, (address));
}

/// @notice Generates the address of a user's execution environment affected by deprecated callConfig changes in the
Expand All @@ -135,79 +112,28 @@ abstract contract Factory {
uint32 callConfig
)
internal
view
returns (address executionEnvironment)
{
bytes memory _creationCode = _getMimicCreationCode({ user: user, control: control, callConfig: callConfig });
bytes32 _salt = _computeSalt(user, control, callConfig);

executionEnvironment = address(
uint160(
uint256(
keccak256(
abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(abi.encodePacked(_creationCode)))
)
)
)
bytes memory returnData = _delegatecallFactoryLib(
abi.encodeCall(FactoryLib.getExecutionEnvironmentCustom, (user, control, callConfig, _salt))
);

return abi.decode(returnData, (address));
}

function _computeSalt(address user, address control, uint32 callConfig) internal view returns (bytes32) {
return keccak256(abi.encodePacked(_FACTORY_BASE_SALT, user, control, callConfig));
}

/// @notice Generates the creation code for the execution environment contract.
/// @param control The address of the DAppControl contract associated with the execution environment.
/// @param callConfig The configuration flags defining the behavior of the execution environment.
/// @param user The address of the user for whom the execution environment is being created, contributing to the
/// uniqueness of the creation code.
/// @return creationCode The bytecode representing the creation code of the execution environment contract.
function _getMimicCreationCode(
address user,
address control,
uint32 callConfig
)
internal
view
returns (bytes memory creationCode)
{
address _executionLib = EXECUTION_ENV_TEMPLATE;
// NOTE: Changing compiler settings or solidity versions can break this.
creationCode = type(Mimic).creationCode;

assembly {
// Insert the ExecutionEnvironment "Lib" address, into the AAAA placeholder in the creation code.
mstore(
add(creationCode, 79),
or(
and(mload(add(creationCode, 79)), not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))),
shl(96, _executionLib)
)
)

// Insert the user address into the BBBB placeholder in the creation code.
mstore(
add(creationCode, 111),
or(
and(mload(add(creationCode, 111)), not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))),
shl(96, user)
)
)

// Insert the control address into the CCCC placeholder in the creation code.
mstore(
add(creationCode, 132),
or(
and(mload(add(creationCode, 132)), not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))),
shl(96, control)
)
)

// Insert the callConfig into the 2222 placeholder in the creation code.
mstore(
add(creationCode, 153),
or(and(mload(add(creationCode, 153)), not(shl(224, 0xFFFFFFFF))), shl(224, callConfig))
)
function _delegatecallFactoryLib(bytes memory data) internal returns (bytes memory) {
(bool _success, bytes memory _result) = FACTORY_LIB.delegatecall(data);
if (!_success) {
assembly {
revert(add(_result, 32), mload(_result))
}
}
return _result;
}
}
146 changes: 146 additions & 0 deletions src/contracts/atlas/FactoryLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import { Mimic } from "../common/Mimic.sol";
import { AtlasEvents } from "../types/AtlasEvents.sol";

// NOTE: Do not call these functions directly. This contract should only ever be delegatecalled by the Atlas contract.

contract FactoryLib {
address public immutable EXECUTION_ENV_TEMPLATE;

/// @notice Initializes a new Factory contract instance by setting the immutable salt for deterministic deployment
/// of Execution Environments and storing the execution template address.
/// @dev The Execution Environment Template must be separately deployed using the same calculated salt.
/// @param executionTemplate Address of the pre-deployed execution template contract for creating Execution
/// Environment instances.
constructor(address executionTemplate) {
EXECUTION_ENV_TEMPLATE = executionTemplate;
}

/// @notice Deploys a new execution environment or retrieves the address of an existing one based on the DApp
/// control, user, and configuration.
/// @dev Uses the `create2` opcode for deterministic deployment, allowing the calculation of the execution
/// environment's address before deployment. The deployment uses a combination of the DAppControl address, user
/// address, call configuration, and a unique salt to ensure the uniqueness and predictability of the environment's
/// address.
/// @param user The address of the user for whom the execution environment is being set.
/// @param control The address of the DAppControl contract providing the operational context.
/// @param callConfig CallConfig settings of the DAppControl contract.
/// @return executionEnvironment The address of the newly created or already existing execution environment.
function getOrCreateExecutionEnvironment(
address user,
address control,
uint32 callConfig,
bytes32 salt
)
public
payable
returns (address executionEnvironment)
{
bytes memory _creationCode = _getMimicCreationCode({ user: user, control: control, callConfig: callConfig });

executionEnvironment = address(
uint160(
uint256(
keccak256(
abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(abi.encodePacked(_creationCode)))
)
)
)
);

if (executionEnvironment.code.length == 0) {
assembly {
executionEnvironment := create2(0, add(_creationCode, 32), mload(_creationCode), salt)
}
emit AtlasEvents.ExecutionEnvironmentCreated(user, executionEnvironment);
}
}

/// @notice Generates the address of a user's execution environment affected by deprecated callConfig changes in the
/// DAppControl.
/// @dev Calculates the deterministic address of the execution environment based on the user, control,
/// callConfig, and controlCodeHash, ensuring consistency across changes in callConfig.
/// @param user The address of the user for whom the execution environment's address is being generated.
/// @param control The address of the DAppControl contract associated with the execution environment.
/// @param callConfig The configuration flags defining the behavior of the execution environment.
/// @return executionEnvironment The address of the user's execution environment.
function getExecutionEnvironmentCustom(
address user,
address control,
uint32 callConfig,
bytes32 salt
)
public
view
returns (address executionEnvironment)
{
bytes memory _creationCode = _getMimicCreationCode({ user: user, control: control, callConfig: callConfig });

executionEnvironment = address(
uint160(
uint256(
keccak256(
abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(abi.encodePacked(_creationCode)))
)
)
)
);
}

/// @notice Generates the creation code for the execution environment contract.
/// @param control The address of the DAppControl contract associated with the execution environment.
/// @param callConfig The configuration flags defining the behavior of the execution environment.
/// @param user The address of the user for whom the execution environment is being created, contributing to the
/// uniqueness of the creation code.
/// @return creationCode The bytecode representing the creation code of the execution environment contract.
function _getMimicCreationCode(
address user,
address control,
uint32 callConfig
)
internal
view
returns (bytes memory creationCode)
{
address _executionLib = EXECUTION_ENV_TEMPLATE;
// NOTE: Changing compiler settings or solidity versions can break this.
creationCode = type(Mimic).creationCode;

assembly {
// Insert the ExecutionEnvironment "Lib" address, into the AAAA placeholder in the creation code.
mstore(
add(creationCode, 79),
or(
and(mload(add(creationCode, 79)), not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))),
shl(96, _executionLib)
)
)

// Insert the user address into the BBBB placeholder in the creation code.
mstore(
add(creationCode, 111),
or(
and(mload(add(creationCode, 111)), not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))),
shl(96, user)
)
)

// Insert the control address into the CCCC placeholder in the creation code.
mstore(
add(creationCode, 132),
or(
and(mload(add(creationCode, 132)), not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))),
shl(96, control)
)
)

// Insert the callConfig into the 2222 placeholder in the creation code.
mstore(
add(creationCode, 153),
or(and(mload(add(creationCode, 153)), not(shl(224, 0xFFFFFFFF))), shl(224, callConfig))
)
}
}
}
Loading
Loading