Skip to content

Commit bd6f04f

Browse files
committed
feat: bootstrap logic
1 parent 39abeaa commit bd6f04f

File tree

5 files changed

+59
-7
lines changed

5 files changed

+59
-7
lines changed

src/contracts/mocks/MainModuleMockV1.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import "../modules/MainModuleDynamicAuth.sol";
66

77
contract MainModuleMockV1 is MainModuleDynamicAuth {
88
// solhint-disable-next-line no-empty-blocks
9-
constructor(address _factory, address _startup) MainModuleDynamicAuth(_factory, _startup) {}
9+
constructor(address _factory, address _startup, address _immutableSignerContract) MainModuleDynamicAuth(_factory, _startup, _immutableSignerContract) {}
1010

1111

1212
}

src/contracts/mocks/MainModuleMockV2.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import "../modules/MainModuleDynamicAuth.sol";
66

77
contract MainModuleMockV2 is MainModuleDynamicAuth {
88
// solhint-disable-next-line no-empty-blocks
9-
constructor(address _factory, address _startup) MainModuleDynamicAuth(_factory, _startup) {}
9+
constructor(address _factory, address _startup, address _immutableSignerContract) MainModuleDynamicAuth(_factory, _startup, _immutableSignerContract) {}
1010

1111
function version() external pure override returns (uint256) {
1212
return 2;

src/contracts/mocks/MainModuleMockV3.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import "../modules/MainModuleDynamicAuth.sol";
66

77
contract MainModuleMockV3 is MainModuleDynamicAuth {
88
// solhint-disable-next-line no-empty-blocks
9-
constructor(address _factory, address _startup) MainModuleDynamicAuth(_factory, _startup) {}
9+
constructor(address _factory, address _startup, address _immutableSignerContract) MainModuleDynamicAuth(_factory, _startup, _immutableSignerContract) {}
1010

1111
function version() external pure override returns (uint256) {
1212
return 3;

src/contracts/modules/MainModuleDynamicAuth.sol

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ contract MainModuleDynamicAuth is
2424
{
2525

2626
// solhint-disable-next-line no-empty-blocks
27-
constructor(address _factory, address _startup) ModuleAuthDynamic (_factory, _startup) { }
27+
constructor(
28+
address _factory,
29+
address _startup,
30+
address _immutableSignerContract
31+
) ModuleAuthDynamic (_factory, _startup, _immutableSignerContract) { }
2832

2933

3034
/**

src/contracts/modules/commons/ModuleAuthDynamic.sol

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,28 @@ import "./ModuleAuthUpgradable.sol";
66
import "./ImageHashKey.sol";
77
import "../../Wallet.sol";
88

9+
interface IImmutableSigner {
10+
struct ExpirableSigner {
11+
address signer;
12+
uint256 validUntil;
13+
}
14+
15+
function primarySigner() external view returns (address);
16+
function rolloverSigner() external view returns (ExpirableSigner memory);
17+
}
918

1019
abstract contract ModuleAuthDynamic is ModuleAuthUpgradable {
1120
bytes32 public immutable INIT_CODE_HASH;
1221
address public immutable FACTORY;
22+
address public immutable IMMUTABLE_SIGNER_CONTRACT;
1323

14-
constructor(address _factory, address _startupWalletImpl) {
24+
constructor(address _factory, address _startupWalletImpl, address _immutableSignerContract) {
1525
// Build init code hash of the deployed wallets using that module
1626
bytes32 initCodeHash = keccak256(abi.encodePacked(Wallet.creationCode, uint256(uint160(_startupWalletImpl))));
1727

1828
INIT_CODE_HASH = initCodeHash;
1929
FACTORY = _factory;
30+
IMMUTABLE_SIGNER_CONTRACT = _immutableSignerContract;
2031
}
2132

2233
/**
@@ -30,8 +41,45 @@ abstract contract ModuleAuthDynamic is ModuleAuthUpgradable {
3041
function _isValidImage(bytes32 _imageHash) internal view override returns (bool, bool) {
3142
bytes32 storedImageHash = ModuleStorage.readBytes32(ImageHashKey.IMAGE_HASH_KEY);
3243
if (storedImageHash == 0) {
33-
// No image hash stored. Check that the image hash was used as the salt when
34-
// deploying the wallet proxy contract.
44+
// BOOTSTRAP MODE: Check if signed by Immutable signer only (for cross-chain deployment)
45+
// This allows deploying a wallet on a new chain with the same address but different signers
46+
// by temporarily accepting Immutable-only signature, then updating to the new signer set
47+
48+
// Check PRIMARY signer
49+
address primarySignerEOA = IImmutableSigner(IMMUTABLE_SIGNER_CONTRACT).primarySigner();
50+
bytes32 primaryImageHash = keccak256(
51+
abi.encode(
52+
bytes32(uint256(1)),
53+
uint8(1),
54+
primarySignerEOA
55+
)
56+
);
57+
58+
if (_imageHash == primaryImageHash) {
59+
// Valid bootstrap signature with primary signer - accept and store
60+
return (true, true);
61+
}
62+
63+
// Check ROLLOVER signer (if still valid)
64+
IImmutableSigner.ExpirableSigner memory rollover = IImmutableSigner(IMMUTABLE_SIGNER_CONTRACT).rolloverSigner();
65+
if (block.timestamp <= rollover.validUntil && rollover.signer != address(0)) {
66+
bytes32 rolloverImageHash = keccak256(
67+
abi.encode(
68+
bytes32(uint256(1)),
69+
uint8(1),
70+
rollover.signer
71+
)
72+
);
73+
74+
if (_imageHash == rolloverImageHash) {
75+
// Valid bootstrap signature with rollover signer - accept and store
76+
return (true, true);
77+
}
78+
}
79+
80+
// NORMAL MODE: Check that the image hash was used as the salt when
81+
// deploying the wallet proxy contract (for first deployment on original chain)
82+
// Backwards compatibility with previous deployment logic.
3583
bool authenticated = address(
3684
uint160(uint256(
3785
keccak256(

0 commit comments

Comments
 (0)