From 4d075fcabf0c567107984fae91b74da1022c03bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Mon, 8 Sep 2025 16:13:33 +0200 Subject: [PATCH 01/16] feat: pass encryption key along with deposit message --- src/validium/IL1ERC20GatewayValidium.sol | 9 ++++- src/validium/IL2ERC20GatewayValidium.sol | 4 +- src/validium/L1ERC20GatewayValidium.sol | 50 +++++++++++++++++++++--- src/validium/L1WETHGatewayValidium.sol | 9 ++++- 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/validium/IL1ERC20GatewayValidium.sol b/src/validium/IL1ERC20GatewayValidium.sol index 314e8c55..5e17bd8d 100644 --- a/src/validium/IL1ERC20GatewayValidium.sol +++ b/src/validium/IL1ERC20GatewayValidium.sol @@ -39,6 +39,9 @@ interface IL1ERC20GatewayValidium { bytes data ); + /// @notice Emitted when a new encryption key is added. + event NewEncryptionKey(uint256 indexed keyId, bytes key); + /************************* * Public View Functions * *************************/ @@ -61,7 +64,8 @@ interface IL1ERC20GatewayValidium { address _token, bytes memory _to, uint256 _amount, - uint256 _gasLimit + uint256 _gasLimit, + uint256 _keyId ) external payable; /// @notice Deposit some token to a recipient's account on L2. @@ -76,7 +80,8 @@ interface IL1ERC20GatewayValidium { address _realSender, bytes memory _to, uint256 _amount, - uint256 _gasLimit + uint256 _gasLimit, + uint256 _keyId ) external payable; /// @notice Complete ERC20 withdraw from L2 to L1 and send fund to recipient's account in L1. diff --git a/src/validium/IL2ERC20GatewayValidium.sol b/src/validium/IL2ERC20GatewayValidium.sol index aecf1f91..0b6233a3 100644 --- a/src/validium/IL2ERC20GatewayValidium.sol +++ b/src/validium/IL2ERC20GatewayValidium.sol @@ -18,12 +18,14 @@ interface IL2ERC20GatewayValidium is IL2ERC20Gateway { /// @param to The encrypted address of recipient in L2 to receive the token. /// @param amount The amount of the token to deposit. /// @param data Optional data to forward to recipient's account. + /// @param encryptionKey The encryption key to use for the deposit. function finalizeDepositERC20Encrypted( address l1Token, address l2Token, address from, bytes memory to, uint256 amount, - bytes calldata data + bytes calldata data, + bytes calldata encryptionKey // 33 bytes compressed key ) external payable; } diff --git a/src/validium/L1ERC20GatewayValidium.sol b/src/validium/L1ERC20GatewayValidium.sol index 8bdba236..719cb95a 100644 --- a/src/validium/L1ERC20GatewayValidium.sol +++ b/src/validium/L1ERC20GatewayValidium.sol @@ -33,6 +33,15 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { /// @dev Error thrown when amount is zero. error ErrorAmountIsZero(); + /// @dev Error thrown when encryption key length is invalid. + error ErrorInvalidEncryptionKeyLength(); + + /// @dev Error thrown the user attempts to use an encryption key that is unknown. + error UnknownEncryptionKey(); + + /// @dev Error thrown the user attempts to use an encryption key that is deprecated. + error DeprecatedEncryptionKey(); + /************* * Constants * *************/ @@ -53,6 +62,9 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { /// pass deploy data on first call to the token. mapping(address => address) private tokenMapping; + /// @notice An array of encryption keys (33-byte compressed ECC public keys). + bytes[] public encryptionKeys; + /*************** * Constructor * ***************/ @@ -93,6 +105,12 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { return ClonesUpgradeable.predictDeterministicAddress(l2TokenImplementation, _salt, l2TokenFactory); } + function latestEncryptionKey() public view returns (uint256, bytes memory) { + uint256 numKeys = encryptionKeys.length; + if (numKeys == 0) revert UnknownEncryptionKey(); + return (numKeys - 1, encryptionKeys[numKeys - 1]); + } + /***************************** * Public Mutating Functions * *****************************/ @@ -102,9 +120,10 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { address _token, bytes memory _to, uint256 _amount, - uint256 _gasLimit + uint256 _gasLimit, + uint256 _keyId ) external payable override { - _deposit(_token, _msgSender(), _to, _amount, new bytes(0), _gasLimit); + _deposit(_token, _msgSender(), _to, _amount, new bytes(0), _gasLimit, _keyId); } /// @inheritdoc IL1ERC20GatewayValidium @@ -113,9 +132,10 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { address _realSender, bytes memory _to, uint256 _amount, - uint256 _gasLimit + uint256 _gasLimit, + uint256 _keyId ) external payable override { - _deposit(_token, _realSender, _to, _amount, new bytes(0), _gasLimit); + _deposit(_token, _realSender, _to, _amount, new bytes(0), _gasLimit, _keyId); } /// @inheritdoc IL1ERC20GatewayValidium @@ -192,8 +212,15 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { bytes memory _to, uint256 _amount, bytes memory _data, - uint256 _gasLimit + uint256 _gasLimit, + uint256 _keyId ) internal virtual nonReentrant { + // Select encryption key for deposit. + uint256 _numKeys = encryptionKeys.length; + if (_keyId >= _numKeys) revert UnknownEncryptionKey(); + if (_keyId < _numKeys - 2) revert DeprecatedEncryptionKey(); // Note: we can make deprecation policy more flexible if needed. + bytes memory _encryptionKey = encryptionKeys[_keyId]; + // 1. Transfer token into this contract. _amount = _transferERC20In(_msgSender(), _token, _amount); if (_amount == 0) revert ErrorAmountIsZero(); @@ -217,7 +244,7 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { } bytes memory _message = abi.encodeCall( IL2ERC20GatewayValidium.finalizeDepositERC20Encrypted, - (_token, _l2Token, _from, _to, _amount, _l2Data) + (_token, _l2Token, _from, _to, _amount, _l2Data, _encryptionKey) ); // 3. Send message to L1ScrollMessenger. @@ -225,4 +252,15 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { emit DepositERC20(_token, _l2Token, _from, _to, _amount, _data); } + + /************************ + * Restricted Functions * + ************************/ + + function registerNewEncryptionKey(bytes memory _key) external onlyOwner { + if (_key.length != 33) revert ErrorInvalidEncryptionKeyLength(); + uint256 keyId = encryptionKeys.length; + encryptionKeys.push(_key); + emit NewEncryptionKey(keyId, _key); + } } diff --git a/src/validium/L1WETHGatewayValidium.sol b/src/validium/L1WETHGatewayValidium.sol index 1aa9be27..579fca94 100644 --- a/src/validium/L1WETHGatewayValidium.sol +++ b/src/validium/L1WETHGatewayValidium.sol @@ -50,7 +50,11 @@ contract L1WETHGatewayValidium { /// @notice Deposit ETH to L2 through the `L1ERC20GatewayValidium` contract. /// @param _to The encrypted address of recipient in L2 to receive the token. - function deposit(bytes memory _to, uint256 _amount) external payable { + function deposit( + bytes memory _to, + uint256 _amount, + uint256 _keyId + ) external payable { if (msg.value < _amount) revert ErrorInsufficientValue(); // WETH deposit is safe. @@ -62,7 +66,8 @@ contract L1WETHGatewayValidium { msg.sender, _to, _amount, - GAS_LIMIT + GAS_LIMIT, + _keyId ); } } From af55ac32861f70abda63534a61ba862abaaedf7d Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Tue, 9 Sep 2025 11:39:17 +0100 Subject: [PATCH 02/16] fix: allow only latest key --- src/validium/L1ERC20GatewayValidium.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/validium/L1ERC20GatewayValidium.sol b/src/validium/L1ERC20GatewayValidium.sol index 719cb95a..655fd562 100644 --- a/src/validium/L1ERC20GatewayValidium.sol +++ b/src/validium/L1ERC20GatewayValidium.sol @@ -217,8 +217,10 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { ) internal virtual nonReentrant { // Select encryption key for deposit. uint256 _numKeys = encryptionKeys.length; + + // Allow ONLY the latest key. if (_keyId >= _numKeys) revert UnknownEncryptionKey(); - if (_keyId < _numKeys - 2) revert DeprecatedEncryptionKey(); // Note: we can make deprecation policy more flexible if needed. + if (_keyId < _numKeys - 1) revert DeprecatedEncryptionKey(); bytes memory _encryptionKey = encryptionKeys[_keyId]; // 1. Transfer token into this contract. From aa5e5046c689c4cf09e0cc010388569be8129058 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Thu, 11 Sep 2025 12:20:22 +0100 Subject: [PATCH 03/16] feat: msg-queue-index based encryption key --- src/validium/IL1ERC20GatewayValidium.sol | 3 - src/validium/IScrollChainValidium.sol | 14 +++++ src/validium/L1ERC20GatewayValidium.sol | 47 +++------------ src/validium/ScrollChainValidium.sol | 75 +++++++++++++++++++++++- 4 files changed, 96 insertions(+), 43 deletions(-) diff --git a/src/validium/IL1ERC20GatewayValidium.sol b/src/validium/IL1ERC20GatewayValidium.sol index 5e17bd8d..343b74b6 100644 --- a/src/validium/IL1ERC20GatewayValidium.sol +++ b/src/validium/IL1ERC20GatewayValidium.sol @@ -39,9 +39,6 @@ interface IL1ERC20GatewayValidium { bytes data ); - /// @notice Emitted when a new encryption key is added. - event NewEncryptionKey(uint256 indexed keyId, bytes key); - /************************* * Public View Functions * *************************/ diff --git a/src/validium/IScrollChainValidium.sol b/src/validium/IScrollChainValidium.sol index f7013d18..7bf5f6b6 100644 --- a/src/validium/IScrollChainValidium.sol +++ b/src/validium/IScrollChainValidium.sol @@ -24,6 +24,12 @@ interface IScrollChainValidium { /// @param withdrawRoot The merkle root on layer2 after this batch. event FinalizeBatch(uint256 indexed batchIndex, bytes32 indexed batchHash, bytes32 stateRoot, bytes32 withdrawRoot); + /// @notice Emitted when a new encryption key is added. + /// @param keyId The incremental index of the key. + /// @param msgIndex The message queue index at the time of key rotation. + /// @param key The encryption key. + event NewEncryptionKey(uint256 indexed keyId, uint256 msgIndex, bytes key); + /************************* * Public View Functions * *************************/ @@ -50,6 +56,14 @@ interface IScrollChainValidium { /// @return Whether the batch is finalized by batch index. function isBatchFinalized(uint256 batchIndex) external view returns (bool); + /// @return The key-id of the latest encryption key. + /// @return The latest encryption key. + function getLatestEncryptionKey() external view returns (uint256, bytes); + + /// @param keyId The incremental index for the encryption key. + /// @return The encryption key with the given key-id. + function getEncryptionKey(uint256 keyId) external view returns (bytes); + /***************************** * Public Mutating Functions * *****************************/ diff --git a/src/validium/L1ERC20GatewayValidium.sol b/src/validium/L1ERC20GatewayValidium.sol index 655fd562..1128e650 100644 --- a/src/validium/L1ERC20GatewayValidium.sol +++ b/src/validium/L1ERC20GatewayValidium.sol @@ -33,15 +33,6 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { /// @dev Error thrown when amount is zero. error ErrorAmountIsZero(); - /// @dev Error thrown when encryption key length is invalid. - error ErrorInvalidEncryptionKeyLength(); - - /// @dev Error thrown the user attempts to use an encryption key that is unknown. - error UnknownEncryptionKey(); - - /// @dev Error thrown the user attempts to use an encryption key that is deprecated. - error DeprecatedEncryptionKey(); - /************* * Constants * *************/ @@ -52,6 +43,9 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { /// @notice The address of ScrollStandardERC20Factory contract in L2. address public immutable l2TokenFactory; + /// @notice The address of ScrollChainValidium contract in L2. + address public immutable scrollChainValidium; + /************* * Variables * *************/ @@ -62,9 +56,6 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { /// pass deploy data on first call to the token. mapping(address => address) private tokenMapping; - /// @notice An array of encryption keys (33-byte compressed ECC public keys). - bytes[] public encryptionKeys; - /*************** * Constructor * ***************/ @@ -79,12 +70,14 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { address _counterpart, address _messenger, address _l2TokenImplementation, - address _l2TokenFactory + address _l2TokenFactory, + address _scrollChainValidium ) ScrollGatewayBase(_counterpart, address(0), _messenger) { _disableInitializers(); l2TokenImplementation = _l2TokenImplementation; l2TokenFactory = _l2TokenFactory; + scrollChainValidium = _scrollChainValidium; } /// @notice Initialize the storage of L1ERC20GatewayValidium. @@ -105,12 +98,6 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { return ClonesUpgradeable.predictDeterministicAddress(l2TokenImplementation, _salt, l2TokenFactory); } - function latestEncryptionKey() public view returns (uint256, bytes memory) { - uint256 numKeys = encryptionKeys.length; - if (numKeys == 0) revert UnknownEncryptionKey(); - return (numKeys - 1, encryptionKeys[numKeys - 1]); - } - /***************************** * Public Mutating Functions * *****************************/ @@ -215,13 +202,8 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { uint256 _gasLimit, uint256 _keyId ) internal virtual nonReentrant { - // Select encryption key for deposit. - uint256 _numKeys = encryptionKeys.length; - - // Allow ONLY the latest key. - if (_keyId >= _numKeys) revert UnknownEncryptionKey(); - if (_keyId < _numKeys - 1) revert DeprecatedEncryptionKey(); - bytes memory _encryptionKey = encryptionKeys[_keyId]; + // Fetch the encryption key with the given key-id. + bytes memory _encryptionKey = IScrollChainValidium(scrollChainValidium).getEncryptionKey(_keyId); // 1. Transfer token into this contract. _amount = _transferERC20In(_msgSender(), _token, _amount); @@ -246,7 +228,7 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { } bytes memory _message = abi.encodeCall( IL2ERC20GatewayValidium.finalizeDepositERC20Encrypted, - (_token, _l2Token, _from, _to, _amount, _l2Data, _encryptionKey) + (_token, _l2Token, _from, _to, _amount, _l2Data) ); // 3. Send message to L1ScrollMessenger. @@ -254,15 +236,4 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { emit DepositERC20(_token, _l2Token, _from, _to, _amount, _data); } - - /************************ - * Restricted Functions * - ************************/ - - function registerNewEncryptionKey(bytes memory _key) external onlyOwner { - if (_key.length != 33) revert ErrorInvalidEncryptionKeyLength(); - uint256 keyId = encryptionKeys.length; - encryptionKeys.push(_key); - emit NewEncryptionKey(keyId, _key); - } } diff --git a/src/validium/ScrollChainValidium.sol b/src/validium/ScrollChainValidium.sol index e703beb7..7e9cd5ac 100644 --- a/src/validium/ScrollChainValidium.sol +++ b/src/validium/ScrollChainValidium.sol @@ -41,6 +41,15 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I /// @dev Thrown when given batch is not committed before. error ErrorBatchNotCommitted(); + /// @dev Error thrown when encryption key length is invalid. + error ErrorInvalidEncryptionKeyLength(); + + /// @dev Error thrown the user attempts to use an encryption key that is unknown. + error ErrorUnknownEncryptionKey(); + + /// @dev Error thrown the user attempts to use an encryption key that is deprecated. + error ErrorDeprecatedEncryptionKey(); + /************* * Constants * *************/ @@ -67,6 +76,17 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I /// @notice The address of `MultipleVersionRollupVerifier`. address public immutable verifier; + /*********** + * Structs * + ***********/ + + struct EncryptionKey { + // The on-chain message index when the key was set. + uint256 msgIndex; + // The 33-bytes compressed public key, i.e. encryption key. + bytes key; + } + /********************* * Storage Variables * *********************/ @@ -86,6 +106,9 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I /// @dev Mapping from batch index to corresponding withdraw root in Validium L3. mapping(uint256 => bytes32) public override withdrawRoots; + /// @dev An array of encryption keys. + EncryptionKey[] public encryptionKeys; + /*************** * Constructor * ***************/ @@ -127,6 +150,22 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I return _batchIndex <= lastFinalizedBatchIndex; } + /// @inheritdoc IScrollChainValidium + function getLatestEncryptionKey() external view override returns (uint256, bytes) { + uint256 _numKeys = encryptionKeys.length; + if (_numKeys == 0) revert ErrorInvalidEncryptionKeyLength(); + return (_numKeys - 1, encryptionKeys[_numKeys - 1].key); + } + + /// @inheritdoc IScrollChainValidium + function getEncryptionKey(uint256 _keyId) external view override returns (bytes) { + uint256 _numKeys = encryptionKeys.length; + if (_numKeys == 0) revert ErrorInvalidEncryptionKeyLength(); + if (_keyId >= _numKeys) revert ErrorUnknownEncryptionKey(); + if (_keyId < _numKeys - 1) revert ErrorDeprecatedEncryptionKey(); + return encryptionKeys[_numKeys - 1].key; + } + /***************************** * Public Mutating Functions * *****************************/ @@ -232,6 +271,17 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I * Restricted Functions * ************************/ + function registerNewEncryptionKey(bytes memory _key) external onlyOwner { + if (_key.length != 33) revert ErrorInvalidEncryptionKeyLength(); + uint256 _keyId = encryptionKeys.length; + + // The message from `nextCrossDomainMessageIndex` will utilise the newly registered encryption key. + uint256 _msgIndex = IL1MessageQueueV2(messageQueueV2).nextCrossDomainMessageIndex(); + encryptionKeys.push(EncryptionKey(_msgIndex, _key)); + + emit NewEncryptionKey(_keyId, _msgIndex, _key); + } + /// @notice Pause the contract /// @param _status The pause status to update. function setPause(bool _status) external onlyRole(DEFAULT_ADMIN_ROLE) { @@ -308,7 +358,9 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I bytes32 postStateRoot = stateRoots[batchIndex]; bytes32 withdrawRoot = withdrawRoots[batchIndex]; - // @todo public inputs TBD + // Get the encryption key at the time of on-chain message queue index. + bytes encryptionKey = _getEncryptionKey(totalL1MessagesPoppedOverall - 1); + bytes memory publicInputs = abi.encodePacked( layer2ChainId, messageQueueHash, @@ -317,7 +369,8 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I committedBatches[prevBatchIndex], // _prevBatchHash postStateRoot, batchHash, - withdrawRoot + withdrawRoot, + encryptionKey ); // verify bundle, choose the correct verifier based on the last batch @@ -364,4 +417,22 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I revert ErrorIncorrectBatchHash(); } } + + /// @dev Internal function to get the relevant encryption key that was used to encrypt messages up to the provided message index. + /// @param _msgIndex The on-chain message queue index being finalised. + /// @return The encryption key used at the time of the provided on-chain message queue index. + function _getEncryptionKey(uint256 _msgIndex) external view override returns (bytes) { + // Start from the "latest" key and continue fetching keys until we find the key + // that was rotated before the message index we have been provided. + uint256 _numKeys = encryptionKeys.length; + if (_numKeys == 0) revert ErrorInvalidEncryptionKeyLength(); + EncryptionKey memory _encryptionKey = encryptionKeys[--_numKeys]; + + while (_encryptionKey.msgIndex > _msgIndex) { + if (_numKeys == 0) revert ErrorInvalidEncryptionKeyLength(); + _encryptionKey = encryptionKeys[--_numKeys]; + } + + return _encryptionKey.key; + } } From d30269a78147f928d72e3d655ce41122ab32e9a7 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Thu, 11 Sep 2025 12:48:09 +0100 Subject: [PATCH 04/16] fix: build, lint --- src/test/validium/FastWithdrawVault.t.sol | 3 ++- src/validium/IL2ERC20GatewayValidium.sol | 4 +--- src/validium/IScrollChainValidium.sol | 4 ++-- src/validium/L1ERC20GatewayValidium.sol | 1 + src/validium/ScrollChainValidium.sol | 13 ++++++++----- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/test/validium/FastWithdrawVault.t.sol b/src/test/validium/FastWithdrawVault.t.sol index 1860cce8..b8d35524 100644 --- a/src/test/validium/FastWithdrawVault.t.sol +++ b/src/test/validium/FastWithdrawVault.t.sol @@ -267,7 +267,8 @@ contract FastWithdrawVaultTest is ValidiumTestBase { address(counterpartGateway), address(messenger), address(template), - address(factory) + address(factory), + address(scrollChainValidium) ) ) ); diff --git a/src/validium/IL2ERC20GatewayValidium.sol b/src/validium/IL2ERC20GatewayValidium.sol index 0b6233a3..aecf1f91 100644 --- a/src/validium/IL2ERC20GatewayValidium.sol +++ b/src/validium/IL2ERC20GatewayValidium.sol @@ -18,14 +18,12 @@ interface IL2ERC20GatewayValidium is IL2ERC20Gateway { /// @param to The encrypted address of recipient in L2 to receive the token. /// @param amount The amount of the token to deposit. /// @param data Optional data to forward to recipient's account. - /// @param encryptionKey The encryption key to use for the deposit. function finalizeDepositERC20Encrypted( address l1Token, address l2Token, address from, bytes memory to, uint256 amount, - bytes calldata data, - bytes calldata encryptionKey // 33 bytes compressed key + bytes calldata data ) external payable; } diff --git a/src/validium/IScrollChainValidium.sol b/src/validium/IScrollChainValidium.sol index 7bf5f6b6..61467d05 100644 --- a/src/validium/IScrollChainValidium.sol +++ b/src/validium/IScrollChainValidium.sol @@ -58,11 +58,11 @@ interface IScrollChainValidium { /// @return The key-id of the latest encryption key. /// @return The latest encryption key. - function getLatestEncryptionKey() external view returns (uint256, bytes); + function getLatestEncryptionKey() external view returns (uint256, bytes memory); /// @param keyId The incremental index for the encryption key. /// @return The encryption key with the given key-id. - function getEncryptionKey(uint256 keyId) external view returns (bytes); + function getEncryptionKey(uint256 keyId) external view returns (bytes memory); /***************************** * Public Mutating Functions * diff --git a/src/validium/L1ERC20GatewayValidium.sol b/src/validium/L1ERC20GatewayValidium.sol index 1128e650..4e052b11 100644 --- a/src/validium/L1ERC20GatewayValidium.sol +++ b/src/validium/L1ERC20GatewayValidium.sol @@ -10,6 +10,7 @@ import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ER import {IL1ScrollMessenger} from "../L1/IL1ScrollMessenger.sol"; import {IL1ERC20GatewayValidium} from "./IL1ERC20GatewayValidium.sol"; import {IL2ERC20GatewayValidium} from "./IL2ERC20GatewayValidium.sol"; +import {IScrollChainValidium} from "./IScrollChainValidium.sol"; import {ScrollGatewayBase} from "../libraries/gateway/ScrollGatewayBase.sol"; diff --git a/src/validium/ScrollChainValidium.sol b/src/validium/ScrollChainValidium.sol index 7e9cd5ac..8ac62153 100644 --- a/src/validium/ScrollChainValidium.sol +++ b/src/validium/ScrollChainValidium.sol @@ -63,6 +63,9 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I /// @notice The role for prover who can finalize batch. bytes32 public constant PROVER_ROLE = keccak256("PROVER_ROLE"); + /// @notice The role that can rotate encryption keys. + bytes32 public constant KEY_REGISTERER_ROLE = keccak256("KEY_REGISTERER_ROLE"); + /*********************** * Immutable Variables * ***********************/ @@ -151,14 +154,14 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I } /// @inheritdoc IScrollChainValidium - function getLatestEncryptionKey() external view override returns (uint256, bytes) { + function getLatestEncryptionKey() external view override returns (uint256, bytes memory) { uint256 _numKeys = encryptionKeys.length; if (_numKeys == 0) revert ErrorInvalidEncryptionKeyLength(); return (_numKeys - 1, encryptionKeys[_numKeys - 1].key); } /// @inheritdoc IScrollChainValidium - function getEncryptionKey(uint256 _keyId) external view override returns (bytes) { + function getEncryptionKey(uint256 _keyId) external view override returns (bytes memory) { uint256 _numKeys = encryptionKeys.length; if (_numKeys == 0) revert ErrorInvalidEncryptionKeyLength(); if (_keyId >= _numKeys) revert ErrorUnknownEncryptionKey(); @@ -271,7 +274,7 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I * Restricted Functions * ************************/ - function registerNewEncryptionKey(bytes memory _key) external onlyOwner { + function registerNewEncryptionKey(bytes memory _key) external onlyRole(KEY_REGISTERER_ROLE) { if (_key.length != 33) revert ErrorInvalidEncryptionKeyLength(); uint256 _keyId = encryptionKeys.length; @@ -359,7 +362,7 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I bytes32 withdrawRoot = withdrawRoots[batchIndex]; // Get the encryption key at the time of on-chain message queue index. - bytes encryptionKey = _getEncryptionKey(totalL1MessagesPoppedOverall - 1); + bytes memory encryptionKey = _getEncryptionKey(totalL1MessagesPoppedOverall - 1); bytes memory publicInputs = abi.encodePacked( layer2ChainId, @@ -421,7 +424,7 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I /// @dev Internal function to get the relevant encryption key that was used to encrypt messages up to the provided message index. /// @param _msgIndex The on-chain message queue index being finalised. /// @return The encryption key used at the time of the provided on-chain message queue index. - function _getEncryptionKey(uint256 _msgIndex) external view override returns (bytes) { + function _getEncryptionKey(uint256 _msgIndex) internal view returns (bytes memory) { // Start from the "latest" key and continue fetching keys until we find the key // that was rotated before the message index we have been provided. uint256 _numKeys = encryptionKeys.length; From 42150c63afcff03efff6b9d2bb3a5d02e40ffe68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 11 Sep 2025 15:06:58 +0200 Subject: [PATCH 05/16] fix tests --- src/test/validium/FastWithdrawVault.t.sol | 2 +- .../validium/L1ERC20GatewayValidium.t.sol | 53 +++++++++++++------ src/test/validium/L1WETHGatewayValidium.t.sol | 11 ++-- src/test/validium/ValidiumTestBase.t.sol | 2 + src/validium/L1ERC20GatewayValidium.sol | 4 +- src/validium/ScrollChainValidium.sol | 12 +++-- 6 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/test/validium/FastWithdrawVault.t.sol b/src/test/validium/FastWithdrawVault.t.sol index b8d35524..92930e3e 100644 --- a/src/test/validium/FastWithdrawVault.t.sol +++ b/src/test/validium/FastWithdrawVault.t.sol @@ -268,7 +268,7 @@ contract FastWithdrawVaultTest is ValidiumTestBase { address(messenger), address(template), address(factory), - address(scrollChainValidium) + address(rollup) ) ) ); diff --git a/src/test/validium/L1ERC20GatewayValidium.t.sol b/src/test/validium/L1ERC20GatewayValidium.t.sol index 240909d3..fd6efaed 100644 --- a/src/test/validium/L1ERC20GatewayValidium.t.sol +++ b/src/test/validium/L1ERC20GatewayValidium.t.sol @@ -14,6 +14,7 @@ import {AddressAliasHelper} from "../../libraries/common/AddressAliasHelper.sol" import {IL1ERC20GatewayValidium} from "../../validium/IL1ERC20GatewayValidium.sol"; import {IL2ERC20GatewayValidium} from "../../validium/IL2ERC20GatewayValidium.sol"; import {L1ERC20GatewayValidium} from "../../validium/L1ERC20GatewayValidium.sol"; +import {ScrollChainValidium} from "../../validium/ScrollChainValidium.sol"; import {TransferReentrantToken} from "../mocks/tokens/TransferReentrantToken.sol"; import {FeeOnTransferToken} from "../mocks/tokens/FeeOnTransferToken.sol"; @@ -128,47 +129,63 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase { _deposit(sender, amount, recipient, gasLimit); } + function testDepositERC20WrongKey( + uint256 amount, + bytes memory recipient, + uint256 gasLimit + ) public { + (uint256 keyId, ) = rollup.getLatestEncryptionKey(); + hevm.expectRevert(ScrollChainValidium.ErrorUnknownEncryptionKey.selector); + gateway.depositERC20(address(l1Token), recipient, amount, gasLimit, keyId + 1); + } + function testDepositReentrantToken(uint256 amount) public { + (uint256 keyId, ) = rollup.getLatestEncryptionKey(); + // should revert, reentrant before transfer reentrantToken.setReentrantCall( address(gateway), 0, abi.encodeWithSignature( - "depositERC20(address,bytes,uint256,uint256)", + "depositERC20(address,bytes,uint256,uint256,uint256)", address(reentrantToken), new bytes(0), amount, - defaultGasLimit + defaultGasLimit, + keyId ), true ); amount = bound(amount, 1, reentrantToken.balanceOf(address(this))); hevm.expectRevert("ReentrancyGuard: reentrant call"); - gateway.depositERC20(address(reentrantToken), new bytes(0), amount, defaultGasLimit); + + gateway.depositERC20(address(reentrantToken), new bytes(0), amount, defaultGasLimit, keyId); // should revert, reentrant after transfer reentrantToken.setReentrantCall( address(gateway), 0, abi.encodeWithSignature( - "depositERC20(address,bytes,uint256,uint256)", + "depositERC20(address,bytes,uint256,uint256,uint256)", address(reentrantToken), new bytes(0), amount, - defaultGasLimit + defaultGasLimit, + keyId ), false ); amount = bound(amount, 1, reentrantToken.balanceOf(address(this))); hevm.expectRevert("ReentrancyGuard: reentrant call"); - gateway.depositERC20(address(reentrantToken), new bytes(0), amount, defaultGasLimit); + gateway.depositERC20(address(reentrantToken), new bytes(0), amount, defaultGasLimit, keyId); } function testFeeOnTransferTokenFailed(uint256 amount) public { feeToken.setFeeRate(1e9); amount = bound(amount, 1, feeToken.balanceOf(address(this))); + (uint256 keyId, ) = rollup.getLatestEncryptionKey(); hevm.expectRevert(L1ERC20GatewayValidium.ErrorAmountIsZero.selector); - gateway.depositERC20(address(feeToken), new bytes(0), amount, defaultGasLimit); + gateway.depositERC20(address(feeToken), new bytes(0), amount, defaultGasLimit, keyId); } function testFeeOnTransferTokenSucceed(uint256 amount, uint256 feeRate) public { @@ -179,7 +196,8 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase { // should succeed, for valid amount uint256 balanceBefore = feeToken.balanceOf(address(gateway)); uint256 fee = (amount * feeRate) / 1e9; - gateway.depositERC20(address(feeToken), new bytes(0), amount, defaultGasLimit); + (uint256 keyId, ) = rollup.getLatestEncryptionKey(); + gateway.depositERC20(address(feeToken), new bytes(0), amount, defaultGasLimit, keyId); uint256 balanceAfter = feeToken.balanceOf(address(gateway)); assertEq(balanceBefore + amount - fee, balanceAfter); } @@ -245,7 +263,8 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase { amount = bound(amount, 1, l1Token.balanceOf(address(this))); // deposit some token to L1ERC20GatewayValidium - gateway.depositERC20(address(l1Token), new bytes(0), amount, defaultGasLimit); + (uint256 keyId, ) = rollup.getLatestEncryptionKey(); + gateway.depositERC20(address(l1Token), new bytes(0), amount, defaultGasLimit, keyId); // do finalize withdraw token bytes memory message = abi.encodeWithSelector( @@ -302,7 +321,8 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase { amount = bound(amount, 1, l1Token.balanceOf(address(this))); // deposit some token to L1ERC20GatewayValidium - gateway.depositERC20(address(l1Token), new bytes(0), amount, defaultGasLimit); + (uint256 keyId, ) = rollup.getLatestEncryptionKey(); + gateway.depositERC20(address(l1Token), new bytes(0), amount, defaultGasLimit, keyId); // do finalize withdraw token bytes memory message = abi.encodeWithSelector( @@ -385,11 +405,12 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase { ); if (amount == 0) { + (uint256 keyId, ) = rollup.getLatestEncryptionKey(); hevm.expectRevert(L1ERC20GatewayValidium.ErrorAmountIsZero.selector); if (from == address(this)) { - gateway.depositERC20(address(l1Token), recipient, amount, gasLimit); + gateway.depositERC20(address(l1Token), recipient, amount, gasLimit, keyId); } else { - gateway.depositERC20(address(l1Token), from, recipient, amount, gasLimit); + gateway.depositERC20(address(l1Token), from, recipient, amount, gasLimit, keyId); } } else { // emit QueueTransaction from L1MessageQueueV2 @@ -412,10 +433,11 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase { uint256 gatewayBalance = l1Token.balanceOf(address(gateway)); uint256 feeVaultBalance = address(feeVault).balance; assertEq(l1Messenger.messageSendTimestamp(keccak256(xDomainCalldata)), 0); + (uint256 keyId, ) = rollup.getLatestEncryptionKey(); if (from == address(this)) { - gateway.depositERC20(address(l1Token), recipient, amount, gasLimit); + gateway.depositERC20(address(l1Token), recipient, amount, gasLimit, keyId); } else { - gateway.depositERC20(address(l1Token), from, recipient, amount, gasLimit); + gateway.depositERC20(address(l1Token), from, recipient, amount, gasLimit, keyId); } assertEq(amount + gatewayBalance, l1Token.balanceOf(address(gateway))); assertEq(feeVaultBalance, address(feeVault).balance); @@ -433,7 +455,8 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase { address(counterpartGateway), address(messenger), address(template), - address(factory) + address(factory), + address(rollup) ) ) ); diff --git a/src/test/validium/L1WETHGatewayValidium.t.sol b/src/test/validium/L1WETHGatewayValidium.t.sol index 1a61fe5d..53b9ff5b 100644 --- a/src/test/validium/L1WETHGatewayValidium.t.sol +++ b/src/test/validium/L1WETHGatewayValidium.t.sol @@ -108,13 +108,15 @@ contract L1WETHGatewayValidiumTest is ValidiumTestBase { message ); + (uint256 keyId, ) = rollup.getLatestEncryptionKey(); + if (amount == 0) { hevm.expectRevert(L1ERC20GatewayValidium.ErrorAmountIsZero.selector); - wethGateway.deposit(recipient, amount); + wethGateway.deposit(recipient, amount, keyId); } else { // revert when ErrorInsufficientValue hevm.expectRevert(L1WETHGatewayValidium.ErrorInsufficientValue.selector); - wethGateway.deposit{value: amount - 1}(recipient, amount); + wethGateway.deposit{value: amount - 1}(recipient, amount, keyId); // emit QueueTransaction from L1MessageQueueV2 { @@ -137,7 +139,7 @@ contract L1WETHGatewayValidiumTest is ValidiumTestBase { uint256 gatewayBalance = weth.balanceOf(address(gateway)); uint256 feeVaultBalance = address(feeVault).balance; assertEq(l1Messenger.messageSendTimestamp(keccak256(xDomainCalldata)), 0); - wethGateway.deposit{value: amount}(recipient, amount); + wethGateway.deposit{value: amount}(recipient, amount, keyId); assertEq(ethBalance - amount, address(this).balance); assertEq(amount + gatewayBalance, weth.balanceOf(address(gateway))); assertEq(feeVaultBalance, address(feeVault).balance); @@ -155,7 +157,8 @@ contract L1WETHGatewayValidiumTest is ValidiumTestBase { address(counterpartGateway), address(messenger), address(template), - address(factory) + address(factory), + address(rollup) ) ) ); diff --git a/src/test/validium/ValidiumTestBase.t.sol b/src/test/validium/ValidiumTestBase.t.sol index 3cb89861..a48b88ee 100644 --- a/src/test/validium/ValidiumTestBase.t.sol +++ b/src/test/validium/ValidiumTestBase.t.sol @@ -130,6 +130,8 @@ abstract contract ValidiumTestBase is ScrollTestBase { address(new ScrollChainValidium(_chainId, address(messageQueueV2), address(verifier))) ); rollup.initialize(address(this)); + rollup.grantRole(rollup.KEY_MANAGER_ROLE(), address(this)); + rollup.registerNewEncryptionKey(hex"123456789012345678901234567890123456789012345678901234567890123456"); // Make nonzero block.timestamp hevm.warp(1); diff --git a/src/validium/L1ERC20GatewayValidium.sol b/src/validium/L1ERC20GatewayValidium.sol index 4e052b11..1580e439 100644 --- a/src/validium/L1ERC20GatewayValidium.sol +++ b/src/validium/L1ERC20GatewayValidium.sol @@ -203,8 +203,8 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium { uint256 _gasLimit, uint256 _keyId ) internal virtual nonReentrant { - // Fetch the encryption key with the given key-id. - bytes memory _encryptionKey = IScrollChainValidium(scrollChainValidium).getEncryptionKey(_keyId); + // Validate the encryption key with the given key-id. + IScrollChainValidium(scrollChainValidium).getEncryptionKey(_keyId); // 1. Transfer token into this contract. _amount = _transferERC20In(_msgSender(), _token, _amount); diff --git a/src/validium/ScrollChainValidium.sol b/src/validium/ScrollChainValidium.sol index 8ac62153..5977efef 100644 --- a/src/validium/ScrollChainValidium.sol +++ b/src/validium/ScrollChainValidium.sol @@ -64,7 +64,7 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I bytes32 public constant PROVER_ROLE = keccak256("PROVER_ROLE"); /// @notice The role that can rotate encryption keys. - bytes32 public constant KEY_REGISTERER_ROLE = keccak256("KEY_REGISTERER_ROLE"); + bytes32 public constant KEY_MANAGER_ROLE = keccak256("KEY_MANAGER_ROLE"); /*********************** * Immutable Variables * @@ -156,14 +156,14 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I /// @inheritdoc IScrollChainValidium function getLatestEncryptionKey() external view override returns (uint256, bytes memory) { uint256 _numKeys = encryptionKeys.length; - if (_numKeys == 0) revert ErrorInvalidEncryptionKeyLength(); + if (_numKeys == 0) revert ErrorUnknownEncryptionKey(); return (_numKeys - 1, encryptionKeys[_numKeys - 1].key); } /// @inheritdoc IScrollChainValidium function getEncryptionKey(uint256 _keyId) external view override returns (bytes memory) { uint256 _numKeys = encryptionKeys.length; - if (_numKeys == 0) revert ErrorInvalidEncryptionKeyLength(); + if (_numKeys == 0) revert ErrorUnknownEncryptionKey(); if (_keyId >= _numKeys) revert ErrorUnknownEncryptionKey(); if (_keyId < _numKeys - 1) revert ErrorDeprecatedEncryptionKey(); return encryptionKeys[_numKeys - 1].key; @@ -274,7 +274,7 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I * Restricted Functions * ************************/ - function registerNewEncryptionKey(bytes memory _key) external onlyRole(KEY_REGISTERER_ROLE) { + function registerNewEncryptionKey(bytes memory _key) external onlyRole(KEY_MANAGER_ROLE) { if (_key.length != 33) revert ErrorInvalidEncryptionKeyLength(); uint256 _keyId = encryptionKeys.length; @@ -362,7 +362,9 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I bytes32 withdrawRoot = withdrawRoots[batchIndex]; // Get the encryption key at the time of on-chain message queue index. - bytes memory encryptionKey = _getEncryptionKey(totalL1MessagesPoppedOverall - 1); + bytes memory encryptionKey = totalL1MessagesPoppedOverall == 0 + ? _getEncryptionKey(0) + : _getEncryptionKey(totalL1MessagesPoppedOverall - 1); bytes memory publicInputs = abi.encodePacked( layer2ChainId, From d06848299939628eab6fb9f4331a9643398f8665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 11 Sep 2025 15:35:52 +0200 Subject: [PATCH 06/16] change error --- src/validium/ScrollChainValidium.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/validium/ScrollChainValidium.sol b/src/validium/ScrollChainValidium.sol index 5977efef..a4880a3f 100644 --- a/src/validium/ScrollChainValidium.sol +++ b/src/validium/ScrollChainValidium.sol @@ -430,11 +430,11 @@ contract ScrollChainValidium is AccessControlUpgradeable, PausableUpgradeable, I // Start from the "latest" key and continue fetching keys until we find the key // that was rotated before the message index we have been provided. uint256 _numKeys = encryptionKeys.length; - if (_numKeys == 0) revert ErrorInvalidEncryptionKeyLength(); + if (_numKeys == 0) revert ErrorUnknownEncryptionKey(); EncryptionKey memory _encryptionKey = encryptionKeys[--_numKeys]; while (_encryptionKey.msgIndex > _msgIndex) { - if (_numKeys == 0) revert ErrorInvalidEncryptionKeyLength(); + if (_numKeys == 0) revert ErrorUnknownEncryptionKey(); _encryptionKey = encryptionKeys[--_numKeys]; } From 51ccac60fda824920e0e54984abca3f625f6c81e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 11 Sep 2025 14:07:29 +0200 Subject: [PATCH 07/16] feat: better configuration management for deterministic deployment --- .../deterministic/DeterministicDeployment.sol | 4 + scripts/deterministic/ScrollConfiguration.sol | 171 ++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 scripts/deterministic/ScrollConfiguration.sol diff --git a/scripts/deterministic/DeterministicDeployment.sol b/scripts/deterministic/DeterministicDeployment.sol index 01f5821d..1a81cba4 100644 --- a/scripts/deterministic/DeterministicDeployment.sol +++ b/scripts/deterministic/DeterministicDeployment.sol @@ -38,6 +38,8 @@ abstract contract DeterministicDeployment is Configuration { string private saltPrefix; bool private skipDeploy; + string private cfgContractsPath; + /********************** * Internal interface * **********************/ @@ -46,6 +48,8 @@ abstract contract DeterministicDeployment is Configuration { mode = _mode; skipDeploy = false; + cfgContractsPath = string(abi.encodePacked(workdir, "/config-contracts.toml")); + if (mode != ScriptMode.EmptyConfig) { super.initialize(workdir); } diff --git a/scripts/deterministic/ScrollConfiguration.sol b/scripts/deterministic/ScrollConfiguration.sol new file mode 100644 index 00000000..9eb2195a --- /dev/null +++ b/scripts/deterministic/ScrollConfiguration.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +import {Script} from "forge-std/Script.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +import {Configuration} from "./Configuration.sol"; + +/// @notice ScrollConfiguration allows inheriting contracts to read the TOML configuration file. +abstract contract ScrollConfiguration is Script, Configuration { + using stdToml for string; + + /**************************** + * Configuration parameters * + ****************************/ + + // general + string internal L1_RPC_ENDPOINT; + string internal L2_RPC_ENDPOINT; + + string internal CHAIN_NAME_L1; + string internal CHAIN_NAME_L2; + uint64 internal CHAIN_ID_L1; + uint64 internal CHAIN_ID_L2; + + uint256 internal MAX_TX_IN_CHUNK; + uint256 internal MAX_BLOCK_IN_CHUNK; + uint256 internal MAX_BATCH_IN_BUNDLE; + uint256 internal MAX_L1_MESSAGE_GAS_LIMIT; + uint256 internal FINALIZE_BATCH_DEADLINE_SEC; + uint256 internal RELAY_MESSAGE_DEADLINE_SEC; + + uint256 internal L1_CONTRACT_DEPLOYMENT_BLOCK; + + bool internal TEST_ENV_MOCK_FINALIZE_ENABLED; + uint256 internal TEST_ENV_MOCK_FINALIZE_TIMEOUT_SEC; + + // accounts + uint256 internal DEPLOYER_PRIVATE_KEY; + uint256 internal L1_COMMIT_SENDER_PRIVATE_KEY; + uint256 internal L1_FINALIZE_SENDER_PRIVATE_KEY; + uint256 internal L1_GAS_ORACLE_SENDER_PRIVATE_KEY; + uint256 internal L2_GAS_ORACLE_SENDER_PRIVATE_KEY; + + address internal DEPLOYER_ADDR; + address internal L1_COMMIT_SENDER_ADDR; + address internal L1_FINALIZE_SENDER_ADDR; + address internal L1_GAS_ORACLE_SENDER_ADDR; + address internal L2_GAS_ORACLE_SENDER_ADDR; + + address internal OWNER_ADDR; + + address internal L2GETH_SIGNER_ADDRESS; + + // db + string internal ROLLUP_EXPLORER_BACKEND_DB_CONNECTION_STRING; + + // genesis + uint256 internal L2_MAX_ETH_SUPPLY; + uint256 internal L2_DEPLOYER_INITIAL_BALANCE; + uint256 internal L2_SCROLL_MESSENGER_INITIAL_BALANCE; + + // contracts + string internal DEPLOYMENT_SALT; + + address internal L1_FEE_VAULT_ADDR; + + // coordinator + string internal CHUNK_COLLECTION_TIME_SEC; + string internal BATCH_COLLECTION_TIME_SEC; + string internal BUNDLE_COLLECTION_TIME_SEC; + string internal COORDINATOR_JWT_SECRET_KEY; + + // frontend + string internal EXTERNAL_RPC_URI_L1; + string internal EXTERNAL_RPC_URI_L2; + string internal BRIDGE_API_URI; + string internal ROLLUPSCAN_API_URI; + string internal EXTERNAL_EXPLORER_URI_L1; + string internal EXTERNAL_EXPLORER_URI_L2; + string internal ADMIN_SYSTEM_DASHBOARD_URI; + string internal GRAFANA_URI; + + /********************** + * Internal interface * + **********************/ + + function readConfig(string memory workdir) internal { + super.initialize(workdir); + + CHAIN_ID_L1 = uint64(cfg.readUint(".general.CHAIN_ID_L1")); + CHAIN_ID_L2 = uint64(cfg.readUint(".general.CHAIN_ID_L2")); + + MAX_TX_IN_CHUNK = cfg.readUint(".rollup.MAX_TX_IN_CHUNK"); + MAX_BLOCK_IN_CHUNK = cfg.readUint(".rollup.MAX_BLOCK_IN_CHUNK"); + MAX_BATCH_IN_BUNDLE = cfg.readUint(".rollup.MAX_BATCH_IN_BUNDLE"); + MAX_L1_MESSAGE_GAS_LIMIT = cfg.readUint(".rollup.MAX_L1_MESSAGE_GAS_LIMIT"); + FINALIZE_BATCH_DEADLINE_SEC = cfg.readUint(".rollup.FINALIZE_BATCH_DEADLINE_SEC"); + RELAY_MESSAGE_DEADLINE_SEC = cfg.readUint(".rollup.RELAY_MESSAGE_DEADLINE_SEC"); + + L1_CONTRACT_DEPLOYMENT_BLOCK = cfg.readUint(".general.L1_CONTRACT_DEPLOYMENT_BLOCK"); + + TEST_ENV_MOCK_FINALIZE_ENABLED = cfg.readBool(".rollup.TEST_ENV_MOCK_FINALIZE_ENABLED"); + TEST_ENV_MOCK_FINALIZE_TIMEOUT_SEC = cfg.readUint(".rollup.TEST_ENV_MOCK_FINALIZE_TIMEOUT_SEC"); + + DEPLOYER_PRIVATE_KEY = cfg.readUint(".accounts.DEPLOYER_PRIVATE_KEY"); + L1_COMMIT_SENDER_PRIVATE_KEY = cfg.readUint(".accounts.L1_COMMIT_SENDER_PRIVATE_KEY"); + L1_FINALIZE_SENDER_PRIVATE_KEY = cfg.readUint(".accounts.L1_FINALIZE_SENDER_PRIVATE_KEY"); + L1_GAS_ORACLE_SENDER_PRIVATE_KEY = cfg.readUint(".accounts.L1_GAS_ORACLE_SENDER_PRIVATE_KEY"); + L2_GAS_ORACLE_SENDER_PRIVATE_KEY = cfg.readUint(".accounts.L2_GAS_ORACLE_SENDER_PRIVATE_KEY"); + + DEPLOYER_ADDR = cfg.readAddress(".accounts.DEPLOYER_ADDR"); + L1_COMMIT_SENDER_ADDR = cfg.readAddress(".accounts.L1_COMMIT_SENDER_ADDR"); + L1_FINALIZE_SENDER_ADDR = cfg.readAddress(".accounts.L1_FINALIZE_SENDER_ADDR"); + L1_GAS_ORACLE_SENDER_ADDR = cfg.readAddress(".accounts.L1_GAS_ORACLE_SENDER_ADDR"); + L2_GAS_ORACLE_SENDER_ADDR = cfg.readAddress(".accounts.L2_GAS_ORACLE_SENDER_ADDR"); + + OWNER_ADDR = cfg.readAddress(".accounts.OWNER_ADDR"); + + L2GETH_SIGNER_ADDRESS = cfg.readAddress(".sequencer.L2GETH_SIGNER_ADDRESS"); + + L2_MAX_ETH_SUPPLY = cfg.readUint(".genesis.L2_MAX_ETH_SUPPLY"); + L2_DEPLOYER_INITIAL_BALANCE = cfg.readUint(".genesis.L2_DEPLOYER_INITIAL_BALANCE"); + L2_SCROLL_MESSENGER_INITIAL_BALANCE = L2_MAX_ETH_SUPPLY - L2_DEPLOYER_INITIAL_BALANCE; + + DEPLOYMENT_SALT = cfg.readString(".contracts.DEPLOYMENT_SALT"); + + L1_FEE_VAULT_ADDR = cfg.readAddress(".contracts.L1_FEE_VAULT_ADDR"); + + CHUNK_COLLECTION_TIME_SEC = cfg.readString(".coordinator.CHUNK_COLLECTION_TIME_SEC"); + BATCH_COLLECTION_TIME_SEC = cfg.readString(".coordinator.BATCH_COLLECTION_TIME_SEC"); + BUNDLE_COLLECTION_TIME_SEC = cfg.readString(".coordinator.BUNDLE_COLLECTION_TIME_SEC"); + COORDINATOR_JWT_SECRET_KEY = cfg.readString(".coordinator.COORDINATOR_JWT_SECRET_KEY"); + + runSanityCheck(); + } + + /********************* + * Private functions * + *********************/ + + function runSanityCheck() private view { + verifyAccount("DEPLOYER", DEPLOYER_PRIVATE_KEY, DEPLOYER_ADDR); + verifyAccount("L1_COMMIT_SENDER", L1_COMMIT_SENDER_PRIVATE_KEY, L1_COMMIT_SENDER_ADDR); + verifyAccount("L1_FINALIZE_SENDER", L1_FINALIZE_SENDER_PRIVATE_KEY, L1_FINALIZE_SENDER_ADDR); + verifyAccount("L1_GAS_ORACLE_SENDER", L1_GAS_ORACLE_SENDER_PRIVATE_KEY, L1_GAS_ORACLE_SENDER_ADDR); + verifyAccount("L2_GAS_ORACLE_SENDER", L2_GAS_ORACLE_SENDER_PRIVATE_KEY, L2_GAS_ORACLE_SENDER_ADDR); + } + + function verifyAccount( + string memory name, + uint256 privateKey, + address addr + ) private pure { + if (vm.addr(privateKey) != addr) { + revert( + string( + abi.encodePacked( + "[ERROR] ", + name, + "_ADDR (", + vm.toString(addr), + ") does not match ", + name, + "_PRIVATE_KEY" + ) + ) + ); + } + } +} From afd3d08af51ded396c4f8b9fdcaeac41cb15cdf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 11 Sep 2025 14:13:22 +0200 Subject: [PATCH 08/16] move things around --- scripts/deterministic/ScrollConfiguration.sol | 171 ------------------ 1 file changed, 171 deletions(-) delete mode 100644 scripts/deterministic/ScrollConfiguration.sol diff --git a/scripts/deterministic/ScrollConfiguration.sol b/scripts/deterministic/ScrollConfiguration.sol deleted file mode 100644 index 9eb2195a..00000000 --- a/scripts/deterministic/ScrollConfiguration.sol +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.24; - -import {Script} from "forge-std/Script.sol"; -import {stdToml} from "forge-std/StdToml.sol"; - -import {Configuration} from "./Configuration.sol"; - -/// @notice ScrollConfiguration allows inheriting contracts to read the TOML configuration file. -abstract contract ScrollConfiguration is Script, Configuration { - using stdToml for string; - - /**************************** - * Configuration parameters * - ****************************/ - - // general - string internal L1_RPC_ENDPOINT; - string internal L2_RPC_ENDPOINT; - - string internal CHAIN_NAME_L1; - string internal CHAIN_NAME_L2; - uint64 internal CHAIN_ID_L1; - uint64 internal CHAIN_ID_L2; - - uint256 internal MAX_TX_IN_CHUNK; - uint256 internal MAX_BLOCK_IN_CHUNK; - uint256 internal MAX_BATCH_IN_BUNDLE; - uint256 internal MAX_L1_MESSAGE_GAS_LIMIT; - uint256 internal FINALIZE_BATCH_DEADLINE_SEC; - uint256 internal RELAY_MESSAGE_DEADLINE_SEC; - - uint256 internal L1_CONTRACT_DEPLOYMENT_BLOCK; - - bool internal TEST_ENV_MOCK_FINALIZE_ENABLED; - uint256 internal TEST_ENV_MOCK_FINALIZE_TIMEOUT_SEC; - - // accounts - uint256 internal DEPLOYER_PRIVATE_KEY; - uint256 internal L1_COMMIT_SENDER_PRIVATE_KEY; - uint256 internal L1_FINALIZE_SENDER_PRIVATE_KEY; - uint256 internal L1_GAS_ORACLE_SENDER_PRIVATE_KEY; - uint256 internal L2_GAS_ORACLE_SENDER_PRIVATE_KEY; - - address internal DEPLOYER_ADDR; - address internal L1_COMMIT_SENDER_ADDR; - address internal L1_FINALIZE_SENDER_ADDR; - address internal L1_GAS_ORACLE_SENDER_ADDR; - address internal L2_GAS_ORACLE_SENDER_ADDR; - - address internal OWNER_ADDR; - - address internal L2GETH_SIGNER_ADDRESS; - - // db - string internal ROLLUP_EXPLORER_BACKEND_DB_CONNECTION_STRING; - - // genesis - uint256 internal L2_MAX_ETH_SUPPLY; - uint256 internal L2_DEPLOYER_INITIAL_BALANCE; - uint256 internal L2_SCROLL_MESSENGER_INITIAL_BALANCE; - - // contracts - string internal DEPLOYMENT_SALT; - - address internal L1_FEE_VAULT_ADDR; - - // coordinator - string internal CHUNK_COLLECTION_TIME_SEC; - string internal BATCH_COLLECTION_TIME_SEC; - string internal BUNDLE_COLLECTION_TIME_SEC; - string internal COORDINATOR_JWT_SECRET_KEY; - - // frontend - string internal EXTERNAL_RPC_URI_L1; - string internal EXTERNAL_RPC_URI_L2; - string internal BRIDGE_API_URI; - string internal ROLLUPSCAN_API_URI; - string internal EXTERNAL_EXPLORER_URI_L1; - string internal EXTERNAL_EXPLORER_URI_L2; - string internal ADMIN_SYSTEM_DASHBOARD_URI; - string internal GRAFANA_URI; - - /********************** - * Internal interface * - **********************/ - - function readConfig(string memory workdir) internal { - super.initialize(workdir); - - CHAIN_ID_L1 = uint64(cfg.readUint(".general.CHAIN_ID_L1")); - CHAIN_ID_L2 = uint64(cfg.readUint(".general.CHAIN_ID_L2")); - - MAX_TX_IN_CHUNK = cfg.readUint(".rollup.MAX_TX_IN_CHUNK"); - MAX_BLOCK_IN_CHUNK = cfg.readUint(".rollup.MAX_BLOCK_IN_CHUNK"); - MAX_BATCH_IN_BUNDLE = cfg.readUint(".rollup.MAX_BATCH_IN_BUNDLE"); - MAX_L1_MESSAGE_GAS_LIMIT = cfg.readUint(".rollup.MAX_L1_MESSAGE_GAS_LIMIT"); - FINALIZE_BATCH_DEADLINE_SEC = cfg.readUint(".rollup.FINALIZE_BATCH_DEADLINE_SEC"); - RELAY_MESSAGE_DEADLINE_SEC = cfg.readUint(".rollup.RELAY_MESSAGE_DEADLINE_SEC"); - - L1_CONTRACT_DEPLOYMENT_BLOCK = cfg.readUint(".general.L1_CONTRACT_DEPLOYMENT_BLOCK"); - - TEST_ENV_MOCK_FINALIZE_ENABLED = cfg.readBool(".rollup.TEST_ENV_MOCK_FINALIZE_ENABLED"); - TEST_ENV_MOCK_FINALIZE_TIMEOUT_SEC = cfg.readUint(".rollup.TEST_ENV_MOCK_FINALIZE_TIMEOUT_SEC"); - - DEPLOYER_PRIVATE_KEY = cfg.readUint(".accounts.DEPLOYER_PRIVATE_KEY"); - L1_COMMIT_SENDER_PRIVATE_KEY = cfg.readUint(".accounts.L1_COMMIT_SENDER_PRIVATE_KEY"); - L1_FINALIZE_SENDER_PRIVATE_KEY = cfg.readUint(".accounts.L1_FINALIZE_SENDER_PRIVATE_KEY"); - L1_GAS_ORACLE_SENDER_PRIVATE_KEY = cfg.readUint(".accounts.L1_GAS_ORACLE_SENDER_PRIVATE_KEY"); - L2_GAS_ORACLE_SENDER_PRIVATE_KEY = cfg.readUint(".accounts.L2_GAS_ORACLE_SENDER_PRIVATE_KEY"); - - DEPLOYER_ADDR = cfg.readAddress(".accounts.DEPLOYER_ADDR"); - L1_COMMIT_SENDER_ADDR = cfg.readAddress(".accounts.L1_COMMIT_SENDER_ADDR"); - L1_FINALIZE_SENDER_ADDR = cfg.readAddress(".accounts.L1_FINALIZE_SENDER_ADDR"); - L1_GAS_ORACLE_SENDER_ADDR = cfg.readAddress(".accounts.L1_GAS_ORACLE_SENDER_ADDR"); - L2_GAS_ORACLE_SENDER_ADDR = cfg.readAddress(".accounts.L2_GAS_ORACLE_SENDER_ADDR"); - - OWNER_ADDR = cfg.readAddress(".accounts.OWNER_ADDR"); - - L2GETH_SIGNER_ADDRESS = cfg.readAddress(".sequencer.L2GETH_SIGNER_ADDRESS"); - - L2_MAX_ETH_SUPPLY = cfg.readUint(".genesis.L2_MAX_ETH_SUPPLY"); - L2_DEPLOYER_INITIAL_BALANCE = cfg.readUint(".genesis.L2_DEPLOYER_INITIAL_BALANCE"); - L2_SCROLL_MESSENGER_INITIAL_BALANCE = L2_MAX_ETH_SUPPLY - L2_DEPLOYER_INITIAL_BALANCE; - - DEPLOYMENT_SALT = cfg.readString(".contracts.DEPLOYMENT_SALT"); - - L1_FEE_VAULT_ADDR = cfg.readAddress(".contracts.L1_FEE_VAULT_ADDR"); - - CHUNK_COLLECTION_TIME_SEC = cfg.readString(".coordinator.CHUNK_COLLECTION_TIME_SEC"); - BATCH_COLLECTION_TIME_SEC = cfg.readString(".coordinator.BATCH_COLLECTION_TIME_SEC"); - BUNDLE_COLLECTION_TIME_SEC = cfg.readString(".coordinator.BUNDLE_COLLECTION_TIME_SEC"); - COORDINATOR_JWT_SECRET_KEY = cfg.readString(".coordinator.COORDINATOR_JWT_SECRET_KEY"); - - runSanityCheck(); - } - - /********************* - * Private functions * - *********************/ - - function runSanityCheck() private view { - verifyAccount("DEPLOYER", DEPLOYER_PRIVATE_KEY, DEPLOYER_ADDR); - verifyAccount("L1_COMMIT_SENDER", L1_COMMIT_SENDER_PRIVATE_KEY, L1_COMMIT_SENDER_ADDR); - verifyAccount("L1_FINALIZE_SENDER", L1_FINALIZE_SENDER_PRIVATE_KEY, L1_FINALIZE_SENDER_ADDR); - verifyAccount("L1_GAS_ORACLE_SENDER", L1_GAS_ORACLE_SENDER_PRIVATE_KEY, L1_GAS_ORACLE_SENDER_ADDR); - verifyAccount("L2_GAS_ORACLE_SENDER", L2_GAS_ORACLE_SENDER_PRIVATE_KEY, L2_GAS_ORACLE_SENDER_ADDR); - } - - function verifyAccount( - string memory name, - uint256 privateKey, - address addr - ) private pure { - if (vm.addr(privateKey) != addr) { - revert( - string( - abi.encodePacked( - "[ERROR] ", - name, - "_ADDR (", - vm.toString(addr), - ") does not match ", - name, - "_PRIVATE_KEY" - ) - ) - ); - } - } -} From 1515434dc017c1f5f7cf0e93a072255c56cccaf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 11 Sep 2025 14:21:56 +0200 Subject: [PATCH 09/16] fix todos --- scripts/deterministic/DeterministicDeployment.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/deterministic/DeterministicDeployment.sol b/scripts/deterministic/DeterministicDeployment.sol index 1a81cba4..01f5821d 100644 --- a/scripts/deterministic/DeterministicDeployment.sol +++ b/scripts/deterministic/DeterministicDeployment.sol @@ -38,8 +38,6 @@ abstract contract DeterministicDeployment is Configuration { string private saltPrefix; bool private skipDeploy; - string private cfgContractsPath; - /********************** * Internal interface * **********************/ @@ -48,8 +46,6 @@ abstract contract DeterministicDeployment is Configuration { mode = _mode; skipDeploy = false; - cfgContractsPath = string(abi.encodePacked(workdir, "/config-contracts.toml")); - if (mode != ScriptMode.EmptyConfig) { super.initialize(workdir); } From 527f90fa19b9634817fc917036ef6d3e659f3718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 11 Sep 2025 14:35:44 +0200 Subject: [PATCH 10/16] feat: validium deployment scripts --- scripts/deterministic/validium/Constants.sol | 5 + .../validium/DeployValidium.s.sol | 601 ++++++++++++++++++ .../validium/GenerateGenesis.s.sol | 249 ++++++++ .../validium/ValidiumConfiguration.sol | 66 ++ .../validium/workdir/config-contracts.toml | 33 + .../validium/workdir/config.toml | 46 ++ .../validium/workdir/genesis.json.template | 57 ++ 7 files changed, 1057 insertions(+) create mode 100644 scripts/deterministic/validium/Constants.sol create mode 100644 scripts/deterministic/validium/DeployValidium.s.sol create mode 100644 scripts/deterministic/validium/GenerateGenesis.s.sol create mode 100644 scripts/deterministic/validium/ValidiumConfiguration.sol create mode 100644 scripts/deterministic/validium/workdir/config-contracts.toml create mode 100644 scripts/deterministic/validium/workdir/config.toml create mode 100644 scripts/deterministic/validium/workdir/genesis.json.template diff --git a/scripts/deterministic/validium/Constants.sol b/scripts/deterministic/validium/Constants.sol new file mode 100644 index 00000000..5c9377c1 --- /dev/null +++ b/scripts/deterministic/validium/Constants.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +/// @dev The default minimum withdraw amount configured on L2TxFeeVault. +uint256 constant FEE_VAULT_MIN_WITHDRAW_AMOUNT = 1 ether; diff --git a/scripts/deterministic/validium/DeployValidium.s.sol b/scripts/deterministic/validium/DeployValidium.s.sol new file mode 100644 index 00000000..de71a5f2 --- /dev/null +++ b/scripts/deterministic/validium/DeployValidium.s.sol @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {stdToml} from "forge-std/StdToml.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +// host contracts +import {L1MessageQueueV2} from "../../../src/L1/rollup/L1MessageQueueV2.sol"; +import {EmptyL1MessageQueueV1} from "../../../src/validium/EmptyL1MessageQueueV1.sol"; +import {L1ScrollMessengerValidium} from "../../../src/validium/L1ScrollMessengerValidium.sol"; +import {ScrollChainValidium} from "../../../src/validium/ScrollChainValidium.sol"; +import {ScrollChainValidiumMock} from "../../../src/mocks/ScrollChainValidiumMock.sol"; +import {L1ERC20GatewayValidium} from "../../../src/validium/L1ERC20GatewayValidium.sol"; +import {L1WETHGatewayValidium} from "../../../src/validium/L1WETHGatewayValidium.sol"; +import {SystemConfig} from "../../../src/L1/system-contract/SystemConfig.sol"; +import {L1GatewayRouter} from "../../../src/L1/gateways/L1GatewayRouter.sol"; +import {L1ETHGateway} from "../../../src/L1/gateways/L1ETHGateway.sol"; +import {FastWithdrawVault} from "../../../src/validium/FastWithdrawVault.sol"; + +// validium contracts +import {L1GasPriceOracle} from "../../../src/L2/predeploys/L1GasPriceOracle.sol"; +import {L2MessageQueue} from "../../../src/L2/predeploys/L2MessageQueue.sol"; +import {L2ScrollMessenger} from "../../../src/L2/L2ScrollMessenger.sol"; +import {L2SystemConfig} from "../../../src/L2/L2SystemConfig.sol"; +import {L2TxFeeVault} from "../../../src/L2/predeploys/L2TxFeeVault.sol"; +import {L2GatewayRouter} from "../../../src/L2/gateways/L2GatewayRouter.sol"; +import {L2ETHGateway} from "../../../src/L2/gateways/L2ETHGateway.sol"; +import {L2StandardERC20Gateway} from "../../../src/L2/gateways/L2StandardERC20Gateway.sol"; +import {ScrollStandardERC20} from "../../../src/libraries/token/ScrollStandardERC20.sol"; +import {ScrollStandardERC20FactorySetOwner} from "../contracts/ScrollStandardERC20FactorySetOwner.sol"; + +// misc +import {DeterministicDeployment} from "../DeterministicDeployment.sol"; +import {EmptyContract} from "../../../src/misc/EmptyContract.sol"; +import {FEE_VAULT_MIN_WITHDRAW_AMOUNT} from "./Constants.sol"; +import {ProxyAdminSetOwner} from "../contracts/ProxyAdminSetOwner.sol"; +import {Whitelist} from "../../../src/L2/predeploys/Whitelist.sol"; +import {WrappedEther} from "../../../src/L2/predeploys/WrappedEther.sol"; + +import {ValidiumConfiguration} from "./ValidiumConfiguration.sol"; + +contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { + using stdToml for string; + + /*********************** + * Contracts to deploy * + ***********************/ + + // Host addresses + address internal HOST_ENFORCED_TX_GATEWAY_ADDR; + address internal HOST_ERC20_GATEWAY_ADDR; + address internal HOST_IMPLEMENTATION_PLACEHOLDER_ADDR; + address internal HOST_MESSAGE_QUEUE_ADDR; + address internal HOST_MESSENGER_ADDR; + address internal HOST_MESSENGER_WHITELIST_ADDR; + address internal HOST_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR; + address internal HOST_PROXY_ADMIN_ADDR; + address internal HOST_SYSTEM_CONFIG_ADDR; + address internal HOST_VALIDIUM_ADDR; + address internal HOST_WETH_ADDR; + address internal HOST_WETH_GATEWAY_ADDR; + address internal HOST_FAST_WITHDRAW_VAULT_ADDR; + + // Validium addresses + address internal VALIDIUM_ERC20_GATEWAY_ADDR; + address internal VALIDIUM_GAS_PRICE_ORACLE_ADDR; + address internal VALIDIUM_GAS_PRICE_ORACLE_WHITELIST_ADDR; + address internal VALIDIUM_GATEWAY_ROUTER_ADDR; + address internal VALIDIUM_IMPLEMENTATION_PLACEHOLDER_ADDR; + address internal VALIDIUM_MESSAGE_QUEUE_ADDR; + address internal VALIDIUM_MESSENGER_ADDR; + address internal VALIDIUM_PROXY_ADMIN_ADDR; + address internal VALIDIUM_STANDARD_ERC20_FACTORY_ADDR; + address internal VALIDIUM_STANDARD_ERC20_TOKEN_ADDR; + address internal VALIDIUM_SYSTEM_CONFIG_ADDR; + address internal VALIDIUM_TX_FEE_VAULT_ADDR; + + /************* + * Utilities * + *************/ + + enum Layer { + None, + Host, // The host layer on which the validium is deployed, usually L2. + Validium // The validium layer, i.e. L3. + } + + Layer private broadcastLayer = Layer.None; + + /// @dev Only broadcast code block if we run the script on the specified layer. + modifier broadcast(Layer layer) { + // live deployment + if (broadcastLayer == layer) { + vm.startBroadcast(DEPLOYER_PRIVATE_KEY); + _; + vm.stopBroadcast(); + } + // simulation + else { + // make sure we use the correct sender in simulation + vm.startPrank(DEPLOYER_ADDR); + _; + vm.stopPrank(); + } + } + + /// @dev Only execute block if we run the script on the specified layer. + modifier only(Layer layer) { + if (broadcastLayer != layer) { + return; + } + _; + } + + function parseLayer(string memory raw) private pure returns (Layer) { + if (keccak256(bytes(raw)) == keccak256(bytes("host"))) { + return Layer.Host; + } else if (keccak256(bytes(raw)) == keccak256(bytes("validium"))) { + return Layer.Validium; + } else if (keccak256(bytes(raw)) == keccak256(bytes(""))) { + return Layer.None; + } else { + revert(string(abi.encodePacked("[ERROR] unknown layer: ", raw))); + } + } + + /*************** + * Entry point * + ***************/ + + function run( + string memory workdir, + string memory layer, + string memory scriptMode + ) public { + readConfig(workdir); + + broadcastLayer = parseLayer(layer); + ScriptMode mode = parseScriptMode(scriptMode); + + DeterministicDeployment.initialize(mode, workdir); + + deployAllContracts(); + initializeHostContracts(); + initializeValidiumContracts(); + } + + /********************** + * Internal interface * + **********************/ + + function predictAllContracts() internal { + skipDeployment(); + deployAllContracts(); + } + + /********************* + * Private functions * + *********************/ + + function deployHostProxy(string memory name) private returns (address) { + bytes memory args = abi.encode( + notnull(HOST_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(HOST_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + return deploy(name, type(TransparentUpgradeableProxy).creationCode, args); + } + + function deployValidiumProxy(string memory name) private returns (address) { + bytes memory args = abi.encode( + notnull(VALIDIUM_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(VALIDIUM_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + return deploy(name, type(TransparentUpgradeableProxy).creationCode, args); + } + + function transferOwnership(address addr, address newOwner) private { + if (Ownable(addr).owner() != newOwner) { + Ownable(addr).transferOwnership(newOwner); + } + } + + function deployAllContracts() private { + deployHostContracts1stPass(); + deployValidiumContracts1stPass(); + deployHostContracts2ndPass(); + deployValidiumContracts2ndPass(); + } + + // @notice deployHostContracts1stPass deploys host-layer contracts whose initialization does not depend on any validium-layer addresses. + function deployHostContracts1stPass() private broadcast(Layer.Host) { + deployHostWeth(); + deployHostProxyAdmin(); + + // todo + HOST_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR = address(1); + HOST_ENFORCED_TX_GATEWAY_ADDR = address(1); + + // deploy empty proxies + HOST_VALIDIUM_ADDR = deployHostProxy("HOST_VALIDIUM_PROXY"); + HOST_MESSAGE_QUEUE_ADDR = deployHostProxy("HOST_MESSAGE_QUEUE_PROXY"); + HOST_SYSTEM_CONFIG_ADDR = deployHostProxy("HOST_SYSTEM_CONFIG_PROXY"); + HOST_MESSENGER_ADDR = deployHostProxy("HOST_MESSENGER_PROXY"); + HOST_ERC20_GATEWAY_ADDR = deployHostProxy("HOST_ERC20_GATEWAY_PROXY"); + HOST_WETH_GATEWAY_ADDR = deployHostProxy("HOST_WETH_GATEWAY_PROXY"); + HOST_FAST_WITHDRAW_VAULT_ADDR = deployHostProxy("HOST_FAST_WITHDRAW_VAULT_PROXY"); + + // deploy implementations + deployHostValidium(); + deployHostMessageQueue(); + deployHostSystemConfig(); + deployHostMessengerWhitelist(); + deployHostWethGateway(); + deployHostFastWithdrawVault(); + } + + // @notice deployValidiumContracts1stPass deploys validium-layer contracts whose initialization does not depend on any host-layer addresses. + function deployValidiumContracts1stPass() private broadcast(Layer.Validium) { + deployValidiumProxyAdmin(); + + // todo + VALIDIUM_GATEWAY_ROUTER_ADDR = address(1); + + // predeploys + deployValidiumMessageQueue(); + deployValidiumGasPriceOracle(); + deployValidiumWhitelist(); + deployValidiumTxFeeVault(); + + // deploy empty proxies + VALIDIUM_MESSENGER_ADDR = deployValidiumProxy("VALIDIUM_MESSENGER_PROXY"); + VALIDIUM_SYSTEM_CONFIG_ADDR = deployValidiumProxy("VALIDIUM_SYSTEM_CONFIG_PROXY"); + VALIDIUM_ERC20_GATEWAY_ADDR = deployValidiumProxy("VALIDIUM_ERC20_GATEWAY_PROXY"); + + // deploy implementations + deployValidiumSystemConfig(); + deployValidiumStandardErc20Factory(); + } + + // @notice deployHostContracts2ndPass deploys host-layer contracts whose initialization depends on some validium-layer addresses. + function deployHostContracts2ndPass() private broadcast(Layer.Host) { + deployHostMessenger(); // depends on VALIDIUM_MESSENGER_ADDR + deployHostErc20Gateway(); // depends on VALIDIUM_ERC20_GATEWAY_ADDR + } + + // @notice deployValidiumContracts2ndPass deploys validium-layer contracts whose initialization depends on some host-layer addresses. + function deployValidiumContracts2ndPass() private broadcast(Layer.Validium) { + deployValidiumMessenger(); // depends on HOST_MESSENGER_ADDR + deployValidiumErc20Gateway(); // depends on HOST_ERC20_GATEWAY_ADDR + } + + // @notice initializeHostContracts initializes contracts deployed on the host layer. + function initializeHostContracts() private broadcast(Layer.Host) only(Layer.Host) { + initializeHostValidium(); + initializeHostSystemConfig(); + initializeHostMessageQueue(); + initializeHostMessenger(); + initializeHostMessengerWhitelist(); + + initializeHostErc20Gateway(); + initializeHostFastWithdrawVault(); + + // lockTokensOnL1(); + // transferL1ContractOwnership(); + } + + // @notice initializeValidiumContracts initializes contracts deployed on the validium layer. + function initializeValidiumContracts() private broadcast(Layer.Validium) only(Layer.Validium) { + initializeValidiumMessageQueue(); + initializeValidiumTxFeeVault(); + initializeValidiumGasPriceOracle(); + initializeValidiumMessenger(); + initializeValidiumSystemConfig(); + + initializeValidiumErc20Gateway(); + + // transferL2ContractOwnership(); + } + + /******************** + * Host: Deployment * + *******************/ + + function deployHostWeth() private { + HOST_WETH_ADDR = deploy("HOST_WETH", type(WrappedEther).creationCode); + } + + function deployHostProxyAdmin() private { + bytes memory args = abi.encode(DEPLOYER_ADDR); + HOST_PROXY_ADMIN_ADDR = deploy("HOST_PROXY_ADMIN", type(ProxyAdminSetOwner).creationCode, args); + + HOST_IMPLEMENTATION_PLACEHOLDER_ADDR = deploy( + "HOST_IMPLEMENTATION_PLACEHOLDER", + type(EmptyContract).creationCode + ); + } + + function deployHostValidium() private { + bytes memory args = abi.encode( + CHAIN_ID_VALIDIUM, + notnull(HOST_MESSAGE_QUEUE_ADDR), + notnull(HOST_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR) + ); + + // TODO + // bytes memory creationCode = type(ScrollChainValidium).creationCode; + bytes memory creationCode = type(ScrollChainValidiumMock).creationCode; + + // if (TEST_ENV_MOCK_FINALIZE_ENABLED) { + // creationCode = type(ScrollChainValidiumMockFinalize).creationCode; + // } + + address impl = deploy("HOST_VALIDIUM_IMPLEMENTATION", creationCode, args); + upgrade(HOST_PROXY_ADMIN_ADDR, HOST_VALIDIUM_ADDR, impl); + } + + function deployHostMessageQueue() private { + address queueV1 = deploy("HOST_EMPTY_MESSAGE_QUEUE_V1", type(EmptyL1MessageQueueV1).creationCode); + + bytes memory args = abi.encode( + notnull(HOST_MESSENGER_ADDR), + notnull(HOST_VALIDIUM_ADDR), + notnull(HOST_ENFORCED_TX_GATEWAY_ADDR), + notnull(queueV1), + notnull(HOST_SYSTEM_CONFIG_ADDR) + ); + + address impl = deploy("HOST_MESSAGE_QUEUE_IMPLEMENTATION", type(L1MessageQueueV2).creationCode, args); + + upgrade(HOST_PROXY_ADMIN_ADDR, HOST_MESSAGE_QUEUE_ADDR, impl); + } + + function deployHostSystemConfig() private { + address impl = deploy("HOST_SYSTEM_CONFIG_IMPLEMENTATION", type(SystemConfig).creationCode); + upgrade(HOST_PROXY_ADMIN_ADDR, HOST_SYSTEM_CONFIG_ADDR, impl); + } + + function deployHostMessengerWhitelist() private { + bytes memory args = abi.encode(notnull(OWNER_ADDR)); + HOST_MESSENGER_WHITELIST_ADDR = deploy("HOST_MESSENGER_WHITELIST", type(Whitelist).creationCode, args); + } + + function deployHostWethGateway() private { + bytes memory args = abi.encode(notnull(HOST_WETH_ADDR), notnull(HOST_ERC20_GATEWAY_ADDR)); + + address impl = deploy("HOST_WETH_GATEWAY_IMPLEMENTATION", type(L1WETHGatewayValidium).creationCode, args); + + upgrade(HOST_PROXY_ADMIN_ADDR, HOST_WETH_GATEWAY_ADDR, impl); + } + + function deployHostFastWithdrawVault() private { + bytes memory args = abi.encode(notnull(HOST_WETH_ADDR), notnull(HOST_ERC20_GATEWAY_ADDR)); + + address impl = deploy("HOST_FAST_WITHDRAW_VAULT_IMPLEMENTATION", type(FastWithdrawVault).creationCode, args); + + upgrade(HOST_PROXY_ADMIN_ADDR, HOST_FAST_WITHDRAW_VAULT_ADDR, impl); + } + + function deployHostMessenger() private { + bytes memory args = abi.encode( + notnull(VALIDIUM_MESSENGER_ADDR), + notnull(HOST_VALIDIUM_ADDR), + notnull(HOST_MESSAGE_QUEUE_ADDR), + notnull(HOST_MESSENGER_WHITELIST_ADDR) + ); + + address impl = deploy("HOST_MESSENGER_IMPLEMENTATION", type(L1ScrollMessengerValidium).creationCode, args); + + upgrade(HOST_PROXY_ADMIN_ADDR, HOST_MESSENGER_ADDR, impl); + } + + function deployHostErc20Gateway() private { + bytes memory args = abi.encode( + notnull(VALIDIUM_ERC20_GATEWAY_ADDR), + notnull(HOST_MESSENGER_ADDR), + notnull(VALIDIUM_STANDARD_ERC20_TOKEN_ADDR), + notnull(VALIDIUM_STANDARD_ERC20_FACTORY_ADDR) + ); + + address impl = deploy("HOST_ERC20_GATEWAY_IMPLEMENTATION", type(L1ERC20GatewayValidium).creationCode, args); + + upgrade(HOST_PROXY_ADMIN_ADDR, HOST_ERC20_GATEWAY_ADDR, impl); + } + + /************************ + * Validium: Deployment * + ***********************/ + + function deployValidiumProxyAdmin() private { + bytes memory args = abi.encode(DEPLOYER_ADDR); + VALIDIUM_PROXY_ADMIN_ADDR = deploy("VALIDIUM_PROXY_ADMIN", type(ProxyAdminSetOwner).creationCode, args); + + VALIDIUM_IMPLEMENTATION_PLACEHOLDER_ADDR = deploy( + "VALIDIUM_IMPLEMENTATION_PLACEHOLDER", + type(EmptyContract).creationCode + ); + } + + function deployValidiumMessageQueue() private { + bytes memory args = abi.encode(DEPLOYER_ADDR); + VALIDIUM_MESSAGE_QUEUE_ADDR = deploy("VALIDIUM_MESSAGE_QUEUE", type(L2MessageQueue).creationCode, args); + } + + function deployValidiumGasPriceOracle() private { + bytes memory args = abi.encode(DEPLOYER_ADDR, true); + VALIDIUM_GAS_PRICE_ORACLE_ADDR = deploy("VALIDIUM_GAS_PRICE_ORACLE", type(L1GasPriceOracle).creationCode, args); + } + + function deployValidiumWhitelist() private { + bytes memory args = abi.encode(DEPLOYER_ADDR); + VALIDIUM_GAS_PRICE_ORACLE_WHITELIST_ADDR = deploy( + "VALIDIUM_GAS_PRICE_ORACLE_WHITELIST", + type(Whitelist).creationCode, + args + ); + } + + function deployValidiumTxFeeVault() private { + bytes memory args = abi.encode(DEPLOYER_ADDR, L1_FEE_VAULT_ADDR, FEE_VAULT_MIN_WITHDRAW_AMOUNT); + VALIDIUM_TX_FEE_VAULT_ADDR = deploy("VALIDIUM_TX_FEE_VAULT", type(L2TxFeeVault).creationCode, args); + } + + function deployValidiumSystemConfig() private { + address impl = deploy("VALIDIUM_SYSTEM_CONFIG_IMPLEMENTATION", type(L2SystemConfig).creationCode, new bytes(0)); + upgrade(VALIDIUM_PROXY_ADMIN_ADDR, VALIDIUM_SYSTEM_CONFIG_ADDR, impl); + } + + function deployValidiumStandardErc20Factory() private { + VALIDIUM_STANDARD_ERC20_TOKEN_ADDR = deploy( + "VALIDIUM_STANDARD_ERC20_TOKEN", + type(ScrollStandardERC20).creationCode + ); + + bytes memory args = abi.encode( + VALIDIUM_ERC20_GATEWAY_ADDR, // Note: The factory contract must be owned by the ERC20 gateway contract. + notnull(VALIDIUM_STANDARD_ERC20_TOKEN_ADDR) + ); + + VALIDIUM_STANDARD_ERC20_FACTORY_ADDR = deploy( + "VALIDIUM_STANDARD_ERC20_FACTORY", + type(ScrollStandardERC20FactorySetOwner).creationCode, + args + ); + } + + function deployValidiumMessenger() private { + bytes memory args = abi.encode(notnull(HOST_MESSENGER_ADDR), notnull(VALIDIUM_MESSAGE_QUEUE_ADDR)); + + address impl = deploy("VALIDIUM_MESSENGER_IMPLEMENTATION", type(L2ScrollMessenger).creationCode, args); + + upgrade(VALIDIUM_PROXY_ADMIN_ADDR, VALIDIUM_MESSENGER_ADDR, impl); + } + + function deployValidiumErc20Gateway() private { + bytes memory args = abi.encode( + notnull(HOST_ERC20_GATEWAY_ADDR), + notnull(VALIDIUM_GATEWAY_ROUTER_ADDR), + notnull(VALIDIUM_MESSENGER_ADDR), + notnull(VALIDIUM_STANDARD_ERC20_FACTORY_ADDR) + ); + + address impl = deploy("VALIDIUM_ERC20_GATEWAY_IMPLEMENTATION", type(L2StandardERC20Gateway).creationCode, args); + + upgrade(VALIDIUM_PROXY_ADMIN_ADDR, VALIDIUM_ERC20_GATEWAY_ADDR, impl); + } + + /************************ + * Host: initialization * + ************************/ + + function initializeHostValidium() private { + if (getInitializeCount(HOST_VALIDIUM_ADDR) == 0) { + ScrollChainValidium(HOST_VALIDIUM_ADDR).initialize( + notnull(OWNER_ADDR) // TODO + ); + } + + // TODO + ScrollChainValidium(HOST_VALIDIUM_ADDR).grantRole(keccak256("GENESIS_IMPORTER_ROLE"), COMMIT_SENDER_ADDR); + ScrollChainValidium(HOST_VALIDIUM_ADDR).grantRole(keccak256("SEQUENCER_ROLE"), COMMIT_SENDER_ADDR); + ScrollChainValidium(HOST_VALIDIUM_ADDR).grantRole(keccak256("PROVER_ROLE"), FINALIZE_SENDER_ADDR); + + // ScrollChainValidium(HOST_VALIDIUM_ADDR).updateEncryptionKey(vm.parseBytes(SEQUENCER_ENCRYPTION_KEY)); + } + + function initializeHostSystemConfig() private { + address owner = HOST_PROXY_ADMIN_ADDR; // TODO + address signer = SEQUENCER_SIGNER_ADDRESS; + + SystemConfig.MessageQueueParameters memory messageQueueParameters = SystemConfig.MessageQueueParameters({ + maxGasLimit: uint32(VALIDIUM_GAS_LIMIT), + baseFeeOverhead: 1000000000, + baseFeeScalar: 1000000000 + }); + + SystemConfig.EnforcedBatchParameters memory enforcedBatchParameters = SystemConfig.EnforcedBatchParameters({ + maxDelayEnterEnforcedMode: uint24(2**24 - 1), + maxDelayMessageQueue: uint24(2**24 - 1) + }); + + if (getInitializeCount(HOST_SYSTEM_CONFIG_ADDR) == 0) { + SystemConfig(HOST_SYSTEM_CONFIG_ADDR).initialize( + owner, + signer, + messageQueueParameters, + enforcedBatchParameters + ); + } + } + + function initializeHostMessageQueue() private { + if (getInitializeCount(HOST_MESSAGE_QUEUE_ADDR) == 0) { + L1MessageQueueV2(HOST_MESSAGE_QUEUE_ADDR).initialize(); + } + } + + function initializeHostMessenger() private { + if (getInitializeCount(HOST_MESSENGER_ADDR) == 0) { + L1ScrollMessengerValidium(payable(HOST_MESSENGER_ADDR)).initialize( + notnull(VALIDIUM_MESSENGER_ADDR), + notnull(L1_FEE_VAULT_ADDR), + notnull(HOST_VALIDIUM_ADDR), + notnull(HOST_MESSAGE_QUEUE_ADDR) + ); + } + } + + function initializeHostMessengerWhitelist() private { + address[] memory gateways = new address[](2); + gateways[0] = HOST_ERC20_GATEWAY_ADDR; + Whitelist(HOST_MESSENGER_WHITELIST_ADDR).updateWhitelistStatus(gateways, true); + } + + function initializeHostErc20Gateway() private { + if (getInitializeCount(HOST_ERC20_GATEWAY_ADDR) == 0) { + L1ERC20GatewayValidium(payable(HOST_ERC20_GATEWAY_ADDR)).initialize(); + } + } + + function initializeHostFastWithdrawVault() private { + if (getInitializeCount(HOST_FAST_WITHDRAW_VAULT_ADDR) == 0) { + FastWithdrawVault(payable(HOST_FAST_WITHDRAW_VAULT_ADDR)).initialize( + notnull(OWNER_ADDR), + notnull(FAST_WITHDRAW_SIGNER_ADDR) + ); + } + } + + /**************************** + * Validium: initialization * + ***************************/ + + function initializeValidiumMessageQueue() private { + if (L2MessageQueue(VALIDIUM_MESSAGE_QUEUE_ADDR).messenger() != notnull(VALIDIUM_MESSENGER_ADDR)) { + L2MessageQueue(VALIDIUM_MESSAGE_QUEUE_ADDR).initialize(VALIDIUM_MESSENGER_ADDR); + } + } + + function initializeValidiumTxFeeVault() private { + if (L2TxFeeVault(payable(VALIDIUM_TX_FEE_VAULT_ADDR)).messenger() != notnull(VALIDIUM_MESSENGER_ADDR)) { + L2TxFeeVault(payable(VALIDIUM_TX_FEE_VAULT_ADDR)).updateMessenger(VALIDIUM_MESSENGER_ADDR); + } + } + + function initializeValidiumGasPriceOracle() private { + if ( + address(L1GasPriceOracle(VALIDIUM_GAS_PRICE_ORACLE_ADDR).whitelist()) != + notnull(VALIDIUM_GAS_PRICE_ORACLE_WHITELIST_ADDR) + ) { + L1GasPriceOracle(VALIDIUM_GAS_PRICE_ORACLE_ADDR).updateWhitelist(VALIDIUM_GAS_PRICE_ORACLE_WHITELIST_ADDR); + } + } + + function initializeValidiumMessenger() private { + if (getInitializeCount(VALIDIUM_MESSENGER_ADDR) == 0) { + L2ScrollMessenger(payable(VALIDIUM_MESSENGER_ADDR)).initialize(notnull(HOST_MESSENGER_ADDR)); + } + } + + function initializeValidiumSystemConfig() private { + if (getInitializeCount(VALIDIUM_SYSTEM_CONFIG_ADDR) == 0) { + L2SystemConfig(payable(VALIDIUM_SYSTEM_CONFIG_ADDR)).initialize(notnull(OWNER_ADDR)); + } + } + + function initializeValidiumErc20Gateway() private { + if (getInitializeCount(VALIDIUM_ERC20_GATEWAY_ADDR) == 0) { + L2StandardERC20Gateway(VALIDIUM_ERC20_GATEWAY_ADDR).initialize( + notnull(HOST_ERC20_GATEWAY_ADDR), + notnull(VALIDIUM_GATEWAY_ROUTER_ADDR), + notnull(VALIDIUM_MESSENGER_ADDR), + notnull(VALIDIUM_STANDARD_ERC20_FACTORY_ADDR) + ); + } + } +} diff --git a/scripts/deterministic/validium/GenerateGenesis.s.sol b/scripts/deterministic/validium/GenerateGenesis.s.sol new file mode 100644 index 00000000..cc0b436b --- /dev/null +++ b/scripts/deterministic/validium/GenerateGenesis.s.sol @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +import {console} from "forge-std/console.sol"; + +import {L1GasPriceOracle} from "../../../src/L2/predeploys/L1GasPriceOracle.sol"; +import {L2MessageQueue} from "../../../src/L2/predeploys/L2MessageQueue.sol"; +import {L2TxFeeVault} from "../../../src/L2/predeploys/L2TxFeeVault.sol"; +import {Whitelist} from "../../../src/L2/predeploys/Whitelist.sol"; + +import {FEE_VAULT_MIN_WITHDRAW_AMOUNT} from "./Constants.sol"; +import {DeployValidium} from "./DeployValidium.s.sol"; +import {DeterministicDeployment, DETERMINISTIC_DEPLOYMENT_PROXY_ADDR} from "../DeterministicDeployment.sol"; + +contract GenerateGenesis is DeployValidium { + /*************** + * Entry point * + ***************/ + + string private genesisAllocTmpPath; + + function run(string memory workdir) public { + readConfig(workdir); + + string memory templatePath = string(abi.encodePacked(workdir, "/genesis.json.template")); + string memory outPath = string(abi.encodePacked(workdir, "/genesis.json")); + genesisAllocTmpPath = string(abi.encodePacked(workdir, "/__genesis-alloc.json")); + + DeterministicDeployment.initialize(ScriptMode.VerifyConfig, workdir); + predictAllContracts(); + + generateGenesisAlloc(); + generateGenesisJson(templatePath, outPath); + + // clean up temporary files + vm.removeFile(genesisAllocTmpPath); + } + + /********************* + * Private functions * + *********************/ + + function generateGenesisAlloc() private { + if (vm.exists(genesisAllocTmpPath)) { + vm.removeFile(genesisAllocTmpPath); + } + + // Scroll predeploys + setValidiumMessageQueue(); + setValidiumGasPriceOracle(); + setValidiumWhitelist(); + setValidiumFeeVault(); + + // other predeploys + setDeterministicDeploymentProxy(); + setSafeSingletonFactory(); + + // reset sender + vm.resetNonce(msg.sender); + + // prefunded accounts + prefundValidiumMessenger(); + prefundL2Deployer(); + + // write to file + vm.dumpState(genesisAllocTmpPath); + sortJsonByKeys(genesisAllocTmpPath); + } + + function setValidiumMessageQueue() internal { + address predeployAddr = tryGetOverride("VALIDIUM_MESSAGE_QUEUE"); + + if (predeployAddr == address(0)) { + return; + } + + // set code + L2MessageQueue _queue = new L2MessageQueue(DEPLOYER_ADDR); + vm.etch(predeployAddr, address(_queue).code); + + // set storage + bytes32 _ownerSlot = hex"0000000000000000000000000000000000000000000000000000000000000052"; + vm.store(predeployAddr, _ownerSlot, vm.load(address(_queue), _ownerSlot)); + + // reset so it's not included state dump + vm.etch(address(_queue), ""); + vm.resetNonce(address(_queue)); + } + + function setValidiumGasPriceOracle() internal { + address predeployAddr = tryGetOverride("VALIDIUM_GAS_PRICE_ORACLE"); + + if (predeployAddr == address(0)) { + return; + } + + // set code + L1GasPriceOracle _oracle = new L1GasPriceOracle(DEPLOYER_ADDR); + vm.etch(predeployAddr, address(_oracle).code); + + // set storage + bytes32 _ownerSlot = hex"0000000000000000000000000000000000000000000000000000000000000000"; + vm.store(predeployAddr, _ownerSlot, vm.load(address(_oracle), _ownerSlot)); + + bytes32 _isCurieSlot = hex"0000000000000000000000000000000000000000000000000000000000000008"; + vm.store(predeployAddr, _isCurieSlot, bytes32(uint256(1))); + + bytes32 _penaltyThresholdSlot = hex"0000000000000000000000000000000000000000000000000000000000000009"; + vm.store(predeployAddr, _penaltyThresholdSlot, bytes32(uint256(1e9))); + + bytes32 _penaltyFactorSlot = hex"000000000000000000000000000000000000000000000000000000000000000a"; + vm.store(predeployAddr, _penaltyFactorSlot, bytes32(uint256(1e9))); + + bytes32 _isFeynmanSlot = hex"000000000000000000000000000000000000000000000000000000000000000b"; + vm.store(predeployAddr, _isFeynmanSlot, bytes32(uint256(1))); + + // reset so it's not included state dump + vm.etch(address(_oracle), ""); + vm.resetNonce(address(_oracle)); + } + + function setValidiumWhitelist() internal { + address predeployAddr = tryGetOverride("VALIDIUM_GAS_PRICE_ORACLE_WHITELIST"); + + if (predeployAddr == address(0)) { + return; + } + + // set code + Whitelist _whitelist = new Whitelist(DEPLOYER_ADDR); + vm.etch(predeployAddr, address(_whitelist).code); + + // set storage + bytes32 _ownerSlot = hex"0000000000000000000000000000000000000000000000000000000000000000"; + vm.store(predeployAddr, _ownerSlot, vm.load(address(_whitelist), _ownerSlot)); + + // reset so it's not included state dump + vm.etch(address(_whitelist), ""); + vm.resetNonce(address(_whitelist)); + } + + function setValidiumFeeVault() internal { + address predeployAddr = tryGetOverride("VALIDIUM_TX_FEE_VAULT"); + + if (predeployAddr == address(0)) { + return; + } + + // set code + address _vaultAddr; + vm.prank(DEPLOYER_ADDR); + L2TxFeeVault _vault = new L2TxFeeVault(DEPLOYER_ADDR, L1_FEE_VAULT_ADDR, FEE_VAULT_MIN_WITHDRAW_AMOUNT); + vm.prank(DEPLOYER_ADDR); + _vault.updateMessenger(VALIDIUM_MESSENGER_ADDR); + _vaultAddr = address(_vault); + + vm.etch(predeployAddr, _vaultAddr.code); + + // set storage + bytes32 _ownerSlot = hex"0000000000000000000000000000000000000000000000000000000000000000"; + vm.store(predeployAddr, _ownerSlot, vm.load(_vaultAddr, _ownerSlot)); + + bytes32 _minWithdrawAmountSlot = hex"0000000000000000000000000000000000000000000000000000000000000001"; + vm.store(predeployAddr, _minWithdrawAmountSlot, vm.load(_vaultAddr, _minWithdrawAmountSlot)); + + bytes32 _messengerSlot = hex"0000000000000000000000000000000000000000000000000000000000000002"; + vm.store(predeployAddr, _messengerSlot, vm.load(_vaultAddr, _messengerSlot)); + + bytes32 _recipientSlot = hex"0000000000000000000000000000000000000000000000000000000000000003"; + vm.store(predeployAddr, _recipientSlot, vm.load(_vaultAddr, _recipientSlot)); + + // reset so it's not included state dump + vm.etch(_vaultAddr, ""); + vm.resetNonce(_vaultAddr); + } + + function setDeterministicDeploymentProxy() internal { + bytes + memory code = hex"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"; + vm.etch(DETERMINISTIC_DEPLOYMENT_PROXY_ADDR, code); + } + + function setSafeSingletonFactory() internal { + bytes + memory code = hex"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"; + vm.etch(0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7, code); + } + + function prefundValidiumMessenger() internal { + vm.deal(VALIDIUM_MESSENGER_ADDR, VALIDIUM_MESSENGER_INITIAL_BALANCE); + } + + function prefundL2Deployer() internal { + vm.deal(DEPLOYER_ADDR, VALIDIUM_DEPLOYER_INITIAL_BALANCE); + } + + function generateGenesisJson(string memory templatePath, string memory outPath) private { + // initialize template file + if (vm.exists(outPath)) { + vm.removeFile(outPath); + } + + string memory template = vm.readFile(templatePath); + vm.writeFile(outPath, template); + + // general config + vm.writeJson(vm.toString(CHAIN_ID_VALIDIUM), outPath, ".config.chainId"); + + uint256 timestamp = vm.unixTime() / 1000; + vm.writeJson(vm.toString(bytes32(timestamp)), outPath, ".timestamp"); + + // serialize explicitly as string, otherwise foundry will serialize it as number + string memory gasLimit = string(abi.encodePacked('"', vm.toString(VALIDIUM_GAS_LIMIT), '"')); + vm.writeJson(gasLimit, outPath, ".gasLimit"); + + // scroll-specific config + vm.writeJson(vm.toString(HOST_SYSTEM_CONFIG_ADDR), outPath, ".config.systemContract.system_contract_address"); + + vm.writeJson(vm.toString(VALIDIUM_TX_FEE_VAULT_ADDR), outPath, ".config.scroll.feeVaultAddress"); + + // serialize explicitly as string, otherwise foundry will serialize it as number + string memory l1ChainId = string(abi.encodePacked('"', vm.toString(CHAIN_ID_HOST), '"')); + vm.writeJson(l1ChainId, outPath, ".config.scroll.l1Config.l1ChainId"); + + vm.writeJson(vm.toString(HOST_MESSAGE_QUEUE_ADDR), outPath, ".config.scroll.l1Config.l1MessageQueueV2Address"); + + vm.writeJson(vm.toString(HOST_VALIDIUM_ADDR), outPath, ".config.scroll.l1Config.scrollChainAddress"); + + vm.writeJson( + vm.toString(VALIDIUM_SYSTEM_CONFIG_ADDR), + outPath, + ".config.scroll.l1Config.l2SystemConfigAddress" + ); + + // predeploys and prefunded accounts + string memory alloc = vm.readFile(genesisAllocTmpPath); + vm.writeJson(alloc, outPath, ".alloc"); + } + + /// @notice Sorts the allocs by address + // source: https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/scripts/L2Genesis.s.sol + function sortJsonByKeys(string memory _path) private { + string[] memory commands = new string[](3); + commands[0] = "/bin/bash"; + commands[1] = "-c"; + commands[2] = string.concat("cat <<< $(jq -S '.' ", _path, ") > ", _path); + vm.ffi(commands); + } +} diff --git a/scripts/deterministic/validium/ValidiumConfiguration.sol b/scripts/deterministic/validium/ValidiumConfiguration.sol new file mode 100644 index 00000000..3275eb80 --- /dev/null +++ b/scripts/deterministic/validium/ValidiumConfiguration.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +import {stdToml} from "forge-std/StdToml.sol"; + +import {Configuration} from "../Configuration.sol"; + +/// @notice Configuration allows inheriting contracts to read the TOML configuration file. +abstract contract ValidiumConfiguration is Configuration { + using stdToml for string; + + /**************************** + * Configuration parameters * + ****************************/ + + // general + uint64 internal CHAIN_ID_HOST; + uint64 internal CHAIN_ID_VALIDIUM; + + uint256 internal VALIDIUM_GAS_LIMIT; + uint256 internal VALIDIUM_MAX_ETH_SUPPLY; + uint256 internal VALIDIUM_DEPLOYER_INITIAL_BALANCE; + uint256 internal VALIDIUM_MESSENGER_INITIAL_BALANCE; + + // accounts + address internal DEPLOYER_ADDR; + address internal OWNER_ADDR; + address internal L1_FEE_VAULT_ADDR; + address internal SEQUENCER_SIGNER_ADDRESS; + address internal COMMIT_SENDER_ADDR; + address internal FINALIZE_SENDER_ADDR; + address internal FAST_WITHDRAW_SIGNER_ADDR; + + // keys + uint256 internal DEPLOYER_PRIVATE_KEY; + string internal SEQUENCER_ENCRYPTION_KEY; + string internal SEQUENCER_DECRYPTION_KEY; + + /********************** + * Internal interface * + **********************/ + + function readConfig(string memory workdir) internal { + super.initialize(workdir); + + CHAIN_ID_HOST = uint64(cfg.readUint(".general.chain_id_host")); + CHAIN_ID_VALIDIUM = uint64(cfg.readUint(".general.chain_id_validium")); + + VALIDIUM_GAS_LIMIT = cfg.readUint(".general.gas_limit"); + VALIDIUM_MAX_ETH_SUPPLY = cfg.readUint(".general.max_eth_supply"); + VALIDIUM_DEPLOYER_INITIAL_BALANCE = cfg.readUint(".general.deployer_initial_balance"); + VALIDIUM_MESSENGER_INITIAL_BALANCE = VALIDIUM_MAX_ETH_SUPPLY - VALIDIUM_DEPLOYER_INITIAL_BALANCE; + + DEPLOYER_ADDR = cfg.readAddress(".accounts.deployer"); + OWNER_ADDR = cfg.readAddress(".accounts.owner"); + L1_FEE_VAULT_ADDR = cfg.readAddress(".accounts.fee_vault"); + SEQUENCER_SIGNER_ADDRESS = cfg.readAddress(".accounts.sequencer"); + COMMIT_SENDER_ADDR = cfg.readAddress(".accounts.commit_sender"); + FINALIZE_SENDER_ADDR = cfg.readAddress(".accounts.finalize_sender"); + FAST_WITHDRAW_SIGNER_ADDR = cfg.readAddress(".accounts.fast_withdraw_signer"); + + DEPLOYER_PRIVATE_KEY = cfg.readUint(".keys.deployer"); + SEQUENCER_ENCRYPTION_KEY = cfg.readString(".keys.sequencer_encryption"); + SEQUENCER_DECRYPTION_KEY = cfg.readString(".keys.sequencer_decryption"); + } +} diff --git a/scripts/deterministic/validium/workdir/config-contracts.toml b/scripts/deterministic/validium/workdir/config-contracts.toml new file mode 100644 index 00000000..552e4a0b --- /dev/null +++ b/scripts/deterministic/validium/workdir/config-contracts.toml @@ -0,0 +1,33 @@ +HOST_WETH_ADDR = "0x0000000000000000000000000000000000000000" +HOST_PROXY_ADMIN_ADDR = "0x0000000000000000000000000000000000000000" +HOST_IMPLEMENTATION_PLACEHOLDER_ADDR = "0x0000000000000000000000000000000000000000" +HOST_VALIDIUM_PROXY_ADDR = "0x0000000000000000000000000000000000000000" +HOST_MESSAGE_QUEUE_PROXY_ADDR = "0x0000000000000000000000000000000000000000" +HOST_SYSTEM_CONFIG_PROXY_ADDR = "0x0000000000000000000000000000000000000000" +HOST_MESSENGER_PROXY_ADDR = "0x0000000000000000000000000000000000000000" +HOST_ERC20_GATEWAY_PROXY_ADDR = "0x0000000000000000000000000000000000000000" +HOST_WETH_GATEWAY_PROXY_ADDR = "0x0000000000000000000000000000000000000000" +HOST_FAST_WITHDRAW_VAULT_PROXY_ADDR = "0x0000000000000000000000000000000000000000" +HOST_MESSENGER_WHITELIST_ADDR = "0x0000000000000000000000000000000000000000" +HOST_VALIDIUM_IMPLEMENTATION_ADDR = "0x0000000000000000000000000000000000000000" +HOST_EMPTY_MESSAGE_QUEUE_V1_ADDR = "0x0000000000000000000000000000000000000000" +HOST_MESSAGE_QUEUE_IMPLEMENTATION_ADDR = "0x0000000000000000000000000000000000000000" +HOST_SYSTEM_CONFIG_IMPLEMENTATION_ADDR = "0x0000000000000000000000000000000000000000" +HOST_MESSENGER_IMPLEMENTATION_ADDR = "0x0000000000000000000000000000000000000000" +HOST_ERC20_GATEWAY_IMPLEMENTATION_ADDR = "0x0000000000000000000000000000000000000000" +HOST_WETH_GATEWAY_IMPLEMENTATION_ADDR = "0x0000000000000000000000000000000000000000" +HOST_FAST_WITHDRAW_VAULT_IMPLEMENTATION_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_PROXY_ADMIN_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_IMPLEMENTATION_PLACEHOLDER_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_MESSAGE_QUEUE_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_GAS_PRICE_ORACLE_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_GAS_PRICE_ORACLE_WHITELIST_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_TX_FEE_VAULT_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_MESSENGER_PROXY_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_SYSTEM_CONFIG_PROXY_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_ERC20_GATEWAY_PROXY_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_STANDARD_ERC20_TOKEN_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_STANDARD_ERC20_FACTORY_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_MESSENGER_IMPLEMENTATION_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_SYSTEM_CONFIG_IMPLEMENTATION_ADDR = "0x0000000000000000000000000000000000000000" +VALIDIUM_ERC20_GATEWAY_IMPLEMENTATION_ADDR = "0x0000000000000000000000000000000000000000" \ No newline at end of file diff --git a/scripts/deterministic/validium/workdir/config.toml b/scripts/deterministic/validium/workdir/config.toml new file mode 100644 index 00000000..612b2769 --- /dev/null +++ b/scripts/deterministic/validium/workdir/config.toml @@ -0,0 +1,46 @@ +[general] + +chain_id_host = 22222222 +chain_id_validium = 33333333 + +gas_limit = 20_000_000 +max_eth_supply = "226156424291633194186662080095093570025917938800079226639565593765455331328" +deployer_initial_balance = 1000000000000000000 + + +[accounts] + +deployer = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +fee_vault = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +sequencer = "0x20fc66f922534612f9185734e92b5e99edd53ecf" +commit_sender = "0x22Cda1e7cB5BFFE17bcedE09aa277D50D689CD15" +finalize_sender = "0x9d842De42C7b467c9862c29F57Ed487e25e154Ad" +fast_withdraw_signer = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + + +[keys] + +# note: for now we simply use Anvil's dev accounts +deployer = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + +# Public key used for encrypting deposits. +sequencer_encryption = "02bce905b751cbf4032d6b853904e23cea1c2fd1d6b0c84b4888b0ec6af97cbcd7" + +# Private key used for decrypting deposits. +sequencer_decryption = "9fca2e8bf31078e47ac0cce462867b8093f2649ffd5be1cca89215d9e23822c4" + + +[contracts] + +DEPLOYMENT_SALT = "devnetSalt-000" + +[contracts.overrides] + +# Uncomment the following line for Sepolia deployment. +# HOST_WETH = "0x5300000000000000000000000000000000000004" + +VALIDIUM_MESSAGE_QUEUE = "0x5300000000000000000000000000000000000000" +VALIDIUM_GAS_PRICE_ORACLE = "0x5300000000000000000000000000000000000002" +VALIDIUM_GAS_PRICE_ORACLE_WHITELIST = "0x5300000000000000000000000000000000000003" +VALIDIUM_TX_FEE_VAULT = "0x5300000000000000000000000000000000000005" diff --git a/scripts/deterministic/validium/workdir/genesis.json.template b/scripts/deterministic/validium/workdir/genesis.json.template new file mode 100644 index 00000000..dc7dbcd5 --- /dev/null +++ b/scripts/deterministic/validium/workdir/genesis.json.template @@ -0,0 +1,57 @@ +{ + "config": { + "chainId": null, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "archimedesBlock": 0, + "shanghaiBlock": 0, + "bernoulliBlock": 0, + "curieBlock": 0, + "darwinTime": 0, + "darwinV2Time": 0, + "euclidTime": 0, + "euclidV2Time": 0, + "feynmanTime": 0, + "systemContract": { + "period": 1, + "blocks_per_second": 2, + "system_contract_address": null, + "system_contract_slot": "0x0000000000000000000000000000000000000000000000000000000000000067" + }, + "scroll": { + "useZktrie": false, + "maxTxPerBlock": 100, + "maxTxPayloadBytesPerBlock": 122880, + "feeVaultAddress": null, + "l1Config": { + "l1ChainId": null, + "l1MessageQueueAddress": "0x0000000000000000000000000000000000000001", + "l1MessageQueueV2Address": null, + "l1MessageQueueV2DeploymentBlock": 0, + "scrollChainAddress": null, + "l2SystemConfigAddress": null, + "numL1MessagesPerBlock": "10" + }, + "genesisStateRoot": "0x08d535cc60f40af5dd3b31e0998d7567c2d568b224bed2ba26070aeb078d1339", + "missingHeaderFieldsSHA256": "0x9062e2fa1200dca63bee1d18d429572f134f5f0c98cb4852f62fc394e33cf6e6" + } + }, + "nonce": "0x0", + "timestamp": null, + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": null, + "difficulty": "0x1", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "baseFeePerGas": "0x1", + "alloc": null +} From f304a248fd797a4de037fc40323e68a8ace27749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 11 Sep 2025 16:48:15 +0200 Subject: [PATCH 11/16] register encryption key --- scripts/deterministic/validium/DeployValidium.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/deterministic/validium/DeployValidium.s.sol b/scripts/deterministic/validium/DeployValidium.s.sol index de71a5f2..94e1f3a7 100644 --- a/scripts/deterministic/validium/DeployValidium.s.sol +++ b/scripts/deterministic/validium/DeployValidium.s.sol @@ -485,7 +485,7 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { ScrollChainValidium(HOST_VALIDIUM_ADDR).grantRole(keccak256("SEQUENCER_ROLE"), COMMIT_SENDER_ADDR); ScrollChainValidium(HOST_VALIDIUM_ADDR).grantRole(keccak256("PROVER_ROLE"), FINALIZE_SENDER_ADDR); - // ScrollChainValidium(HOST_VALIDIUM_ADDR).updateEncryptionKey(vm.parseBytes(SEQUENCER_ENCRYPTION_KEY)); + ScrollChainValidium(HOST_VALIDIUM_ADDR).registerNewEncryptionKey(vm.parseBytes(SEQUENCER_ENCRYPTION_KEY)); } function initializeHostSystemConfig() private { From 6ea748b44387d6dbc8012afef928a717e490e16b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 11 Sep 2025 16:51:45 +0200 Subject: [PATCH 12/16] update foundry.toml --- foundry.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/foundry.toml b/foundry.toml index 3ebf29b5..d574bacb 100644 --- a/foundry.toml +++ b/foundry.toml @@ -36,5 +36,6 @@ ffi = true fs_permissions = [ { access='read-write', path='./scripts/deterministic/scroll/config' }, + { access='read-write', path='./scripts/deterministic/validium/workdir' }, { access='read-write', path='../../config' }, ] \ No newline at end of file From df0fd75615e7c41709cf44afb6455afa42d78d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 11 Sep 2025 17:27:25 +0200 Subject: [PATCH 13/16] add fixes, address todos --- scripts/deterministic/Configuration.sol | 7 +-- .../deterministic/DeterministicDeployment.sol | 2 +- .../validium/DeployValidium.s.sol | 49 +++++++++++++------ .../deterministic/validium/workdir/.gitignore | 1 + 4 files changed, 40 insertions(+), 19 deletions(-) create mode 100644 scripts/deterministic/validium/workdir/.gitignore diff --git a/scripts/deterministic/Configuration.sol b/scripts/deterministic/Configuration.sol index 89535fa6..3684de53 100644 --- a/scripts/deterministic/Configuration.sol +++ b/scripts/deterministic/Configuration.sol @@ -14,6 +14,7 @@ abstract contract Configuration is Script { string internal cfg; string internal contractsCfg; + string internal contractsCfgPath; /********************** * Internal interface * @@ -23,7 +24,7 @@ abstract contract Configuration is Script { string memory cfgPath = string(abi.encodePacked(workdir, "/config.toml")); cfg = vm.readFile(cfgPath); - string memory contractsCfgPath = string(abi.encodePacked(workdir, "/config-contracts.toml")); + contractsCfgPath = string(abi.encodePacked(workdir, "/config-contracts.toml")); contractsCfg = vm.readFile(contractsCfgPath); } @@ -39,8 +40,8 @@ abstract contract Configuration is Script { return cfg.readString(key); } - function writeToml(address addr, string memory tomlPath) internal { - vm.writeToml(vm.toString(addr), cfg, tomlPath); + function writeContract(address addr, string memory tomlPath) internal { + vm.writeToml(vm.toString(addr), contractsCfgPath, tomlPath); } /// @dev Ensure that `addr` is not the zero address. diff --git a/scripts/deterministic/DeterministicDeployment.sol b/scripts/deterministic/DeterministicDeployment.sol index 01f5821d..3cb0eefe 100644 --- a/scripts/deterministic/DeterministicDeployment.sol +++ b/scripts/deterministic/DeterministicDeployment.sol @@ -207,7 +207,7 @@ abstract contract DeterministicDeployment is Configuration { string memory tomlPath = string(abi.encodePacked(".", name, "_ADDR")); if (mode == ScriptMode.WriteConfig) { - writeToml(addr, tomlPath); + writeContract(addr, tomlPath); return; } diff --git a/scripts/deterministic/validium/DeployValidium.s.sol b/scripts/deterministic/validium/DeployValidium.s.sol index 94e1f3a7..c5443071 100644 --- a/scripts/deterministic/validium/DeployValidium.s.sol +++ b/scripts/deterministic/validium/DeployValidium.s.sol @@ -197,10 +197,14 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { deployHostWeth(); deployHostProxyAdmin(); - // todo - HOST_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR = address(1); + // Note: we do not use the enforced gateway on the validium L3, + // but it is required to be a non-zero address for initializing + // some other contracts. We just use address(1). HOST_ENFORCED_TX_GATEWAY_ADDR = address(1); + // TODO: deploy verifier + HOST_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR = address(1); + // deploy empty proxies HOST_VALIDIUM_ADDR = deployHostProxy("HOST_VALIDIUM_PROXY"); HOST_MESSAGE_QUEUE_ADDR = deployHostProxy("HOST_MESSAGE_QUEUE_PROXY"); @@ -223,7 +227,9 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { function deployValidiumContracts1stPass() private broadcast(Layer.Validium) { deployValidiumProxyAdmin(); - // todo + // Note: we do not use the gateway router on the validium L3, + // but it is required to be a non-zero address for initializing + // some other contracts. We just use address(1). VALIDIUM_GATEWAY_ROUTER_ADDR = address(1); // predeploys @@ -307,7 +313,7 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { notnull(HOST_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR) ); - // TODO + // TODO: disable mock mode // bytes memory creationCode = type(ScrollChainValidium).creationCode; bytes memory creationCode = type(ScrollChainValidiumMock).creationCode; @@ -379,7 +385,8 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { notnull(VALIDIUM_ERC20_GATEWAY_ADDR), notnull(HOST_MESSENGER_ADDR), notnull(VALIDIUM_STANDARD_ERC20_TOKEN_ADDR), - notnull(VALIDIUM_STANDARD_ERC20_FACTORY_ADDR) + notnull(VALIDIUM_STANDARD_ERC20_FACTORY_ADDR), + notnull(HOST_VALIDIUM_ADDR) ); address impl = deploy("HOST_ERC20_GATEWAY_IMPLEMENTATION", type(L1ERC20GatewayValidium).creationCode, args); @@ -474,22 +481,34 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { ************************/ function initializeHostValidium() private { - if (getInitializeCount(HOST_VALIDIUM_ADDR) == 0) { - ScrollChainValidium(HOST_VALIDIUM_ADDR).initialize( - notnull(OWNER_ADDR) // TODO - ); + ScrollChainValidium validium = ScrollChainValidium(HOST_VALIDIUM_ADDR); + + if (getInitializeCount(HOST_VALIDIUM_ADDR) != 0) { + // assume initialization went through correctly + return; } - // TODO - ScrollChainValidium(HOST_VALIDIUM_ADDR).grantRole(keccak256("GENESIS_IMPORTER_ROLE"), COMMIT_SENDER_ADDR); - ScrollChainValidium(HOST_VALIDIUM_ADDR).grantRole(keccak256("SEQUENCER_ROLE"), COMMIT_SENDER_ADDR); - ScrollChainValidium(HOST_VALIDIUM_ADDR).grantRole(keccak256("PROVER_ROLE"), FINALIZE_SENDER_ADDR); + // temporarily set deployer as admin + validium.initialize(notnull(DEPLOYER_ADDR)); + + // grant operational roles + validium.grantRole(validium.GENESIS_IMPORTER_ROLE(), notnull(COMMIT_SENDER_ADDR)); + validium.grantRole(validium.SEQUENCER_ROLE(), notnull(COMMIT_SENDER_ADDR)); + validium.grantRole(validium.PROVER_ROLE(), notnull(FINALIZE_SENDER_ADDR)); + validium.grantRole(validium.KEY_MANAGER_ROLE(), notnull(DEPLOYER_ADDR)); + + validium.registerNewEncryptionKey(vm.parseBytes(SEQUENCER_ENCRYPTION_KEY)); + + // transfer roles to owner + validium.grantRole(validium.KEY_MANAGER_ROLE(), notnull(OWNER_ADDR)); + validium.renounceRole(validium.KEY_MANAGER_ROLE(), DEPLOYER_ADDR); - ScrollChainValidium(HOST_VALIDIUM_ADDR).registerNewEncryptionKey(vm.parseBytes(SEQUENCER_ENCRYPTION_KEY)); + validium.grantRole(validium.DEFAULT_ADMIN_ROLE(), notnull(OWNER_ADDR)); + validium.renounceRole(validium.DEFAULT_ADMIN_ROLE(), DEPLOYER_ADDR); } function initializeHostSystemConfig() private { - address owner = HOST_PROXY_ADMIN_ADDR; // TODO + address owner = OWNER_ADDR; address signer = SEQUENCER_SIGNER_ADDRESS; SystemConfig.MessageQueueParameters memory messageQueueParameters = SystemConfig.MessageQueueParameters({ diff --git a/scripts/deterministic/validium/workdir/.gitignore b/scripts/deterministic/validium/workdir/.gitignore new file mode 100644 index 00000000..c222e5d6 --- /dev/null +++ b/scripts/deterministic/validium/workdir/.gitignore @@ -0,0 +1 @@ +genesis.json From 99d595fcc9f67af5b7c64ef86bd6225735ef27b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 12 Sep 2025 15:27:31 +0200 Subject: [PATCH 14/16] fix deployment issue when deployer != owner --- scripts/deterministic/validium/DeployValidium.s.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/deterministic/validium/DeployValidium.s.sol b/scripts/deterministic/validium/DeployValidium.s.sol index c5443071..a588b435 100644 --- a/scripts/deterministic/validium/DeployValidium.s.sol +++ b/scripts/deterministic/validium/DeployValidium.s.sol @@ -179,6 +179,7 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { return deploy(name, type(TransparentUpgradeableProxy).creationCode, args); } + // TODO: transfer all contracts to OWNER function transferOwnership(address addr, address newOwner) private { if (Ownable(addr).owner() != newOwner) { Ownable(addr).transferOwnership(newOwner); @@ -347,7 +348,7 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { } function deployHostMessengerWhitelist() private { - bytes memory args = abi.encode(notnull(OWNER_ADDR)); + bytes memory args = abi.encode(notnull(DEPLOYER_ADDR)); HOST_MESSENGER_WHITELIST_ADDR = deploy("HOST_MESSENGER_WHITELIST", type(Whitelist).creationCode, args); } From da76b1f7027f4fc32b5a1153d41e0306c0955f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 19 Sep 2025 13:43:49 +0200 Subject: [PATCH 15/16] transfer ownership after deployment --- .../validium/DeployValidium.s.sol | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/scripts/deterministic/validium/DeployValidium.s.sol b/scripts/deterministic/validium/DeployValidium.s.sol index a588b435..cffe80d9 100644 --- a/scripts/deterministic/validium/DeployValidium.s.sol +++ b/scripts/deterministic/validium/DeployValidium.s.sol @@ -272,8 +272,7 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { initializeHostErc20Gateway(); initializeHostFastWithdrawVault(); - // lockTokensOnL1(); - // transferL1ContractOwnership(); + transferHostContractOwnership(); } // @notice initializeValidiumContracts initializes contracts deployed on the validium layer. @@ -283,10 +282,9 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { initializeValidiumGasPriceOracle(); initializeValidiumMessenger(); initializeValidiumSystemConfig(); - initializeValidiumErc20Gateway(); - // transferL2ContractOwnership(); + transferValidiumContractOwnership(); } /******************** @@ -498,6 +496,7 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { validium.grantRole(validium.PROVER_ROLE(), notnull(FINALIZE_SENDER_ADDR)); validium.grantRole(validium.KEY_MANAGER_ROLE(), notnull(DEPLOYER_ADDR)); + // register initial sequencer key validium.registerNewEncryptionKey(vm.parseBytes(SEQUENCER_ENCRYPTION_KEY)); // transfer roles to owner @@ -514,8 +513,8 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { SystemConfig.MessageQueueParameters memory messageQueueParameters = SystemConfig.MessageQueueParameters({ maxGasLimit: uint32(VALIDIUM_GAS_LIMIT), - baseFeeOverhead: 1000000000, - baseFeeScalar: 1000000000 + baseFeeOverhead: 0, + baseFeeScalar: 0 }); SystemConfig.EnforcedBatchParameters memory enforcedBatchParameters = SystemConfig.EnforcedBatchParameters({ @@ -551,7 +550,7 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { } function initializeHostMessengerWhitelist() private { - address[] memory gateways = new address[](2); + address[] memory gateways = new address[](1); gateways[0] = HOST_ERC20_GATEWAY_ADDR; Whitelist(HOST_MESSENGER_WHITELIST_ADDR).updateWhitelistStatus(gateways, true); } @@ -571,6 +570,17 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { } } + function transferHostContractOwnership() private { + transferOwnership(HOST_PROXY_ADMIN_ADDR, OWNER_ADDR); + // HOST_VALIDIUM_ADDR already coverd in initializeHostValidium + transferOwnership(HOST_MESSAGE_QUEUE_ADDR, OWNER_ADDR); + transferOwnership(HOST_SYSTEM_CONFIG_ADDR, OWNER_ADDR); + transferOwnership(HOST_MESSENGER_ADDR, OWNER_ADDR); + transferOwnership(HOST_MESSENGER_WHITELIST_ADDR, OWNER_ADDR); + transferOwnership(HOST_ERC20_GATEWAY_ADDR, OWNER_ADDR); + // HOST_FAST_WITHDRAW_VAULT_ADDR already covered in initializeHostFastWithdrawVault + } + /**************************** * Validium: initialization * ***************************/ @@ -618,4 +628,16 @@ contract DeployValidium is ValidiumConfiguration, DeterministicDeployment { ); } } + + function transferValidiumContractOwnership() private { + transferOwnership(VALIDIUM_MESSAGE_QUEUE_ADDR, OWNER_ADDR); + transferOwnership(VALIDIUM_GAS_PRICE_ORACLE_ADDR, OWNER_ADDR); + transferOwnership(VALIDIUM_GAS_PRICE_ORACLE_WHITELIST_ADDR, OWNER_ADDR); + transferOwnership(VALIDIUM_TX_FEE_VAULT_ADDR, OWNER_ADDR); + + transferOwnership(VALIDIUM_PROXY_ADMIN_ADDR, OWNER_ADDR); + transferOwnership(VALIDIUM_MESSENGER_ADDR, OWNER_ADDR); + transferOwnership(VALIDIUM_SYSTEM_CONFIG_ADDR, OWNER_ADDR); + transferOwnership(VALIDIUM_ERC20_GATEWAY_ADDR, OWNER_ADDR); + } } From 40a5b3403d78a15323af45998be922ccf55dcef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Mon, 29 Sep 2025 08:44:55 +0200 Subject: [PATCH 16/16] remove incorrect genesis fields --- scripts/deterministic/validium/workdir/genesis.json.template | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/deterministic/validium/workdir/genesis.json.template b/scripts/deterministic/validium/workdir/genesis.json.template index dc7dbcd5..7b412dd2 100644 --- a/scripts/deterministic/validium/workdir/genesis.json.template +++ b/scripts/deterministic/validium/workdir/genesis.json.template @@ -40,9 +40,7 @@ "scrollChainAddress": null, "l2SystemConfigAddress": null, "numL1MessagesPerBlock": "10" - }, - "genesisStateRoot": "0x08d535cc60f40af5dd3b31e0998d7567c2d568b224bed2ba26070aeb078d1339", - "missingHeaderFieldsSHA256": "0x9062e2fa1200dca63bee1d18d429572f134f5f0c98cb4852f62fc394e33cf6e6" + } } }, "nonce": "0x0",