Skip to content

Commit 26eefc4

Browse files
committed
🔨 contracts: guard factory deploys against redundant upgrades
1 parent e82ffe3 commit 26eefc4

3 files changed

Lines changed: 55 additions & 16 deletions

File tree

‎contracts/.gas-snapshot‎

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -212,18 +212,20 @@ IssuerCheckerTest:test_setPrevIssuerWindow_emits_PrevIssuerWindowSet() (gas: 526
212212
IssuerCheckerTest:test_setPrevIssuerWindow_reverts_whenNotAdmin() (gas: 45548)
213213
MockSwapperTest:test_swapExactAmountIn_swaps() (gas: 269807)
214214
MockSwapperTest:test_swapExactAmountOut_swaps() (gas: 269803)
215-
RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 53905971)
216-
RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 14922555)
217-
RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 259268794)
218-
RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 354975875)
219-
RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 33396161)
220-
RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 14923070)
221-
RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 19417493)
222-
RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 14949296)
223-
RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 29708771)
224-
RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 33567331)
225-
RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 36600171)
226-
RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 41785116)
215+
RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 54219802)
216+
RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 15237329)
217+
RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 30533445)
218+
RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 259575404)
219+
RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 355281354)
220+
RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 33725071)
221+
RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 15260529)
222+
RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 31079985)
223+
RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 18736058)
224+
RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 15264030)
225+
RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 30023365)
226+
RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 33896263)
227+
RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 36914862)
228+
RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 42414208)
227229
RefunderTest:test_refund_refunds() (gas: 263363)
228230
RefunderTest:test_refund_reverts_whenExpired() (gas: 88359)
229231
RefunderTest:test_refund_reverts_whenNotKeeper() (gas: 68861)

‎contracts/script/Redeployer.s.sol‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ pragma solidity ^0.8.0;
44
import {
55
TransparentUpgradeableProxy
66
} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol";
7+
import { ERC1967Utils } from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol";
78
import { ProxyAdmin } from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol";
89
import {
910
ITransparentUpgradeableProxy
1011
} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
1112

12-
import { IPlugin } from "modular-account-libs/interfaces/IPlugin.sol";
13+
import { IPlugin, PluginMetadata } from "modular-account-libs/interfaces/IPlugin.sol";
1314

1415
import { ACCOUNT_IMPL, ENTRYPOINT } from "webauthn-owner-plugin/../script/Factory.s.sol";
1516

@@ -52,7 +53,7 @@ contract Redeployer is BaseScript {
5253
ownerPlugin =
5354
IPlugin(_broadcastOrCreate3("node_modules/webauthn-owner-plugin/broadcast/Plugin", "WebauthnOwnerPlugin"));
5455
exaPlugin = IPlugin(_broadcastOrCreate3("broadcast/ExaPlugin", "ExaPlugin"));
55-
factory = ExaAccountFactory(payable(CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("ExaAccountFactory")))));
56+
factory = _factory();
5657
auditor = IAuditor(_protocolOrStub("Auditor", "StubAuditor"));
5758
marketUSDC = IMarket(_protocolOrStub("MarketUSDC", "StubMarketUSDC"));
5859
marketWETH = IMarket(_protocolOrStub("MarketWETH", "StubMarketWETH"));
@@ -196,18 +197,23 @@ contract Redeployer is BaseScript {
196197
/// @notice Upgrades a proxy to the cached ExaAccountFactory implementation.
197198
function deployExaFactory(address proxy) external {
198199
if (address(factory).code.length == 0) revert NotPrepared();
200+
if (address(uint160(uint256(vm.load(proxy, ERC1967Utils.IMPLEMENTATION_SLOT)))) == address(factory)) return;
199201
vm.broadcast(acct("admin"));
200202
proxyAdmin.upgradeAndCall(ITransparentUpgradeableProxy(proxy), address(factory), "");
201203
}
202204

203205
/// @notice Deploys ExaAccountFactory at a version-specific CREATE3 address.
204206
function deployExaFactory(string calldata version) external returns (ExaAccountFactory f) {
205-
if (address(factory).code.length == 0) revert NotPrepared();
207+
bytes32 salt = keccak256(abi.encode("Exa Plugin", version));
208+
f = ExaAccountFactory(payable(CREATE3_FACTORY.getDeployed(acct("admin"), salt)));
209+
if (address(f).code.length != 0) return f;
210+
if (address(ownerPlugin).code.length == 0 || address(exaPlugin).code.length == 0) revert NotPrepared();
211+
206212
address admin = acct("admin");
207213
vm.startBroadcast(admin);
208214
f = ExaAccountFactory(
209215
payable(CREATE3_FACTORY.deploy(
210-
keccak256(abi.encode("Exa Plugin", version)),
216+
salt,
211217
abi.encodePacked(
212218
vm.getCode("ExaAccountFactory.sol:ExaAccountFactory"),
213219
abi.encode(admin, ownerPlugin, exaPlugin, ACCOUNT_IMPL, ENTRYPOINT)
@@ -247,6 +253,16 @@ contract Redeployer is BaseScript {
247253
if (addr == address(0)) addr = CREATE3_FACTORY.getDeployed(acct("admin"), keccak256(abi.encode(salt)));
248254
}
249255

256+
function _factory() internal returns (ExaAccountFactory) {
257+
if (address(exaPlugin).code.length != 0) {
258+
PluginMetadata memory metadata = exaPlugin.pluginMetadata();
259+
address f = CREATE3_FACTORY.getDeployed(acct("admin"), keccak256(abi.encode(metadata.name, metadata.version)));
260+
if (f.code.length != 0) return ExaAccountFactory(payable(f));
261+
}
262+
return
263+
ExaAccountFactory(payable(CREATE3_FACTORY.getDeployed(acct("admin"), keccak256(abi.encode("ExaAccountFactory")))));
264+
}
265+
250266
function _protocolOrStub(string memory name, string memory stub) internal returns (address addr) {
251267
addr = protocol(name, false);
252268
if (addr == address(0)) addr = CREATE3_FACTORY.getDeployed(acct("admin"), keccak256(abi.encode(stub)));

‎contracts/test/Redeployer.t.sol‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,27 @@ contract RedeployerTest is ForkTest {
226226
assertEq(usdc.balanceOf(receiver), amount, "receiver should have USDC");
227227
}
228228

229+
function test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() external {
230+
vm.createSelectFork("polygon", 82_000_000);
231+
redeployer = new Redeployer();
232+
redeployer.prepare();
233+
ExaAccountFactory f1 = redeployer.deployExaFactory("1.1.0");
234+
ExaAccountFactory f2 = redeployer.deployExaFactory("1.1.0");
235+
assertEq(address(f1), address(f2));
236+
}
237+
238+
function test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() external {
239+
vm.createSelectFork("polygon", 82_000_000);
240+
redeployer = new Redeployer();
241+
redeployer.prepare();
242+
address deployer = acct("deployer");
243+
uint256 nonce = vm.getNonce(deployer);
244+
redeployer.serialProxies(nonce);
245+
address proxy = vm.computeCreateAddress(deployer, nonce);
246+
redeployer.deployExaFactory(proxy);
247+
redeployer.deployExaFactory(proxy);
248+
}
249+
229250
function test_deployExaFactory_reverts_whenNotPrepared() external {
230251
vm.createSelectFork("polygon", 82_000_000);
231252
redeployer = new Redeployer();

0 commit comments

Comments
 (0)