A factory for deploying deterministic UUPS upgradeable proxies with permissioned deployment and consistent addresses across EVM chains.
This factory allows you to deploy UUPS upgradeable proxies deterministically by specifying a consistent initial implementation across chains. Proxies can then be upgraded to any implementation while keeping the same address. Since initialization calls are often sensitive, salts must encode the deployer's address, making deployment permissioned but still consistent across chains.
For convenience, a bare-bones minimum-viable UUPSUpgradeable implementation is provided which includes no authorization mechanisms before being upgraded.
- Deterministic deployment of UUPS upgradeable proxies
- Support for immutable args in proxy deployments
- Permission control via deployer address encoded in salt
- Consistent addresses across all EVM chains
- Minimal UUPS implementation for immediate upgrade to target implementation
Permissionless, non-upgradeable protocols like Seaport or the Delegate Registry are often deployed to consistent, deterministic addresses accross chains using Nick Johnson's Keyless Create Factory or 0age's ImmutableCreate2Factory.
Both factories use the CREATE2 opcode which hash the initialization code of the contract with a "salt" value to determine the resulting address. This is fine for immutable contracts, but since upgradeable contracts can change over time, it is not always desirable to use the same initialization code over time. This is especially true because upgradeable contracts require initialization data that is separate from a contract's bytecode. This data often sets the contract owner or admin authority, and must be provided in a separate call.
The ProxyFactory uses the ImmutableCreate2Factory strategy of encoding the deployer's address into the salt, which allows for permissioned deploys while still being deterministic. The ProxyFactory also accepts optional calldata for an initialization call, which is passed to the proxy after deployment to its deterministic address.
Support for ClonesWithImmutableArgs-style "immutable arguments" is also included, but they are not forwarded to the implementation contract by default. Instead, they are appended to the bytecode of the proxy, but not as calldata to every call to the proxy implementation.
The factory and minimal UUPS proxy implementation are deployed at the following addresses:
// Proxy Factory
address constant PROXY_FACTORY_ADDRESS = 0x000000000011Bc780f30CB1A21030b332c981e84;
// Minimal UUPS Proxy Implementation
address constant MINIMAL_UUPS_UPGRADEABLE_ADDRESS = 0x00000000003cc991111C0f2e88135b04D5F945FE;import { DeterministicProxyFactory } from "deterministic-proxy-factory/DeterministicProxyFactory.sol";
import { MinimalUUPSUpgradeable } from "deterministic-proxy-factory/MinimalUUPSUpgradeable.sol";
import { PermissionedSalt } from "deterministic-proxy-factory/PermissionedSalt.sol";
// Deploy a UUPS proxy with initialization
DeterministicProxyFactory factory = DeterministicProxyFactory(0x000000000011Bc780f30CB1A21030b332c981e84);
address uupsImplementation = 0x00000000003cc991111C0f2e88135b04D5F945FE;
address targetImplementation = address(new MyContract());
bytes32 salt = PermissionedSalt.createPermissionedSalt(msg.sender, 1);
bytes memory upgradeCallData = abi.encodeCall(MyContract.initialize, (msg.sender));
address proxy = factory.deploy({
implementation: uupsImplementation,
salt: salt,
callData: abi.encodeCall(MinimalUUPSUpgradeable.upgradeToAndCall, (targetImplementation, upgradeCallData)),
immutableArgs: ""
});Install with forge soldeer install or forge install
forge soldeer install deterministic-proxy-factory~0.3.0
# or
forge install emo-eth/deterministic-proxy-factory@v0.3.0
UUPS proxies are deployed with the minimal UUPS implementation and immediately upgraded to your target implementation during deployment.
// reference the deployed proxy factory
DeterministicProxyFactory factory = DeterministicProxyFactory(0x000000000011Bc780f30CB1A21030b332c981e84);
// reference the minimal UUPS proxy implementation
address uupsImplementation = 0x00000000003cc991111C0f2e88135b04D5F945FE;
// To derive a salt, encode the deployer's address into the top 160 bits of the salt, followed by 96 bits of "actual" salt.
address deployer = msg.sender;
uint96 actualSalt = 1; // can be any uint96 value
// can be created manually
bytes32 salt = bytes32((uint256(uint160(deployer)) << 96) | uint96(actualSalt));
// or using the PermissionedSalt library
bytes32 sameSalt = PermissionedSalt.createPermissionedSalt(deployer, actualSalt);
// Deploy your target implementation
address targetImplementation = address(new MyContract());
// The callData must be nested: upgradeToAndCall(implementation, initializationData)
bytes memory upgradeCallData = abi.encodeCall(MyContract.initialize, (owner, param1, param2));
address uupsProxy = factory.deploy({
implementation: uupsImplementation,
salt: salt,
callData: abi.encodeCall(MinimalUUPSUpgradeable.upgradeToAndCall, (targetImplementation, upgradeCallData)),
immutableArgs: ""
});
// Get the initcode hash for UUPS proxies
bytes32 uupsProxyInitcodeHash = factory.getInitcodeHashForProxy(uupsImplementation, immutableArgs);Key points for UUPS proxies:
- The proxy is deployed with the minimal UUPS implementation
- The deployment immediately calls
upgradeToAndCall(targetImplementation, initializationData) - This atomically upgrades to your implementation and initializes it
- No separate initialization step is needed
The library includes a fixture for setting up the factory in your test environment.
/// SPDX-License-Identifier: MIT
import { MyContract } from "./MyContract.sol";
import { PermissionedSalt } from "deterministic-proxy-factory/PermissionedSalt.sol";
import {
DeterministicProxyFactory,
DeterministicProxyFactoryFixture
} from "deterministic-proxy-factory/fixtures/DeterministicProxyFactoryFixture.sol";
import {
MinimalUUPSUpgradeable,
MinimalUUPSUpgradeableFixture
} from "deterministic-proxy-factory/fixtures/MinimalUUPSUpgradeableFixture.sol";
import { Test } from "forge-std/Test.sol";
contract MyContractTest is Test {
DeterministicProxyFactory factory;
MyContract myContract;
function setUp() public {
// Deploy factory and minimal UUPS proxy implementation to their deterministic addresses
factory = DeterministicProxyFactory(
DeterministicProxyFactoryFixture.setUpDeterministicProxyFactory()
);
address minimalUUPSUpgradeable = MinimalUUPSUpgradeableFixture.setUpMinimalUUPSUpgradeable();
// Deploy your implementation contract
MyContract myContractImplementation = new MyContract();
// Deploy using UUPS implementation (note the nested encoding pattern)
myContract = MyContract(
DeterministicProxyFactoryFixture.deterministicProxyUUPS({
initialProxySalt: PermissionedSalt.createPermissionedSalt(
address(this), uint96(vm.envOr("SALT", uint256(0)))
),
implementation: address(myContractImplementation),
upgradeCallData: abi.encodeCall(MyContract.initialize, (address(this)))
})
);
}
// write your tests here
}Yes, by Zenith. See the report here.
Immutable args are best used for values that:
- Never change throughout the contract's lifetime
- Are frequently accessed (since reading from bytecode is cheaper than storage)
- Are the same across all chains where the proxy is deployed
Initialization data is better for:
- Values that might need to change (via upgrade)
- Chain-specific configuration
- Complex setup logic that needs to be executed
It does not support permissioned deploys.
It does not support initialization calls.
Why not wrap a call to the ImmutableCreate2Factory in another contract that makes an initialization call?
Solady's LibClone library has painstakingly optimized proxy/clone/beacon proxy bytecode, which gets loaded into memory as part of the deployment process. Rather than try to tweak the logic so it works with external calls, it's easier, safer, and more gas efficient to just have the factory deploy the proxies in addition to call their initialization methods. This approach saves gas by avoiding an extra external call and maintains the optimized bytecode patterns.