Skip to content
Draft
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
2 changes: 1 addition & 1 deletion src/contracts/mocks/MainModuleMockV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "../modules/MainModuleDynamicAuth.sol";

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


}
2 changes: 1 addition & 1 deletion src/contracts/mocks/MainModuleMockV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "../modules/MainModuleDynamicAuth.sol";

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

function version() external pure override returns (uint256) {
return 2;
Expand Down
2 changes: 1 addition & 1 deletion src/contracts/mocks/MainModuleMockV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "../modules/MainModuleDynamicAuth.sol";

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

function version() external pure override returns (uint256) {
return 3;
Expand Down
6 changes: 5 additions & 1 deletion src/contracts/modules/MainModuleDynamicAuth.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ contract MainModuleDynamicAuth is
{

// solhint-disable-next-line no-empty-blocks
constructor(address _factory, address _startup) ModuleAuthDynamic (_factory, _startup) { }
constructor(
address _factory,
address _startup,
address _immutableSignerContract
) ModuleAuthDynamic (_factory, _startup, _immutableSignerContract) { }


/**
Expand Down
54 changes: 51 additions & 3 deletions src/contracts/modules/commons/ModuleAuthDynamic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,28 @@ import "./ModuleAuthUpgradable.sol";
import "./ImageHashKey.sol";
import "../../Wallet.sol";

interface IImmutableSigner {
struct ExpirableSigner {
address signer;
uint256 validUntil;
}

function primarySigner() external view returns (address);
function rolloverSigner() external view returns (ExpirableSigner memory);
}

abstract contract ModuleAuthDynamic is ModuleAuthUpgradable {
bytes32 public immutable INIT_CODE_HASH;
address public immutable FACTORY;
address public immutable IMMUTABLE_SIGNER_CONTRACT;

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

INIT_CODE_HASH = initCodeHash;
FACTORY = _factory;
IMMUTABLE_SIGNER_CONTRACT = _immutableSignerContract;
}

/**
Expand All @@ -30,8 +41,45 @@ abstract contract ModuleAuthDynamic is ModuleAuthUpgradable {
function _isValidImage(bytes32 _imageHash) internal view override returns (bool, bool) {
bytes32 storedImageHash = ModuleStorage.readBytes32(ImageHashKey.IMAGE_HASH_KEY);
if (storedImageHash == 0) {
// No image hash stored. Check that the image hash was used as the salt when
// deploying the wallet proxy contract.
// BOOTSTRAP MODE: Check if signed by Immutable signer only (for cross-chain deployment)
// This allows deploying a wallet on a new chain with the same address but different signers
// by temporarily accepting Immutable-only signature, then updating to the new signer set

// Check PRIMARY signer
address primarySignerEOA = IImmutableSigner(IMMUTABLE_SIGNER_CONTRACT).primarySigner();
bytes32 primaryImageHash = keccak256(
abi.encode(
bytes32(uint256(1)),
uint8(1),
primarySignerEOA
)
);

if (_imageHash == primaryImageHash) {
// Valid bootstrap signature with primary signer - accept and store
return (true, true);
}

// Check ROLLOVER signer (if still valid)
IImmutableSigner.ExpirableSigner memory rollover = IImmutableSigner(IMMUTABLE_SIGNER_CONTRACT).rolloverSigner();
if (block.timestamp <= rollover.validUntil && rollover.signer != address(0)) {
bytes32 rolloverImageHash = keccak256(
abi.encode(
bytes32(uint256(1)),
uint8(1),
rollover.signer
)
);

if (_imageHash == rolloverImageHash) {
// Valid bootstrap signature with rollover signer - accept and store
return (true, true);
}
}

// NORMAL MODE: Check that the image hash was used as the salt when
// deploying the wallet proxy contract (for first deployment on original chain)
// Backwards compatibility with previous deployment logic.
bool authenticated = address(
uint160(uint256(
keccak256(
Expand Down
Loading