Repository: taikoxyz/taiko-mono
Pull request #19509: refactor(protocol): add commands for print and dryrun proposal actions
Author: login: dantaik
Labels: name: area.protocol
Created: 2025-05-22T05:39:37Z | Merged: 2025-05-25T05:55:04Z



Diff:
diff --git a/packages/protocol/contracts/layer1/mainnet/FooUpgradeable.sol b/packages/protocol/contracts/layer1/mainnet/FooUpgradeable.sol
new file mode 100644
index 00000000000..097cbb32f4e
--- /dev/null
+++ b/packages/protocol/contracts/layer1/mainnet/FooUpgradeable.sol
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+
+import "src/shared/common/EssentialContract.sol";
+import "src/shared/libs/LibAddress.sol";
+
+/// @title FooUpgradeable
+/// @custom:security-contact security@taiko.xyz
+contract FooUpgradeable is EssentialContract {
+    using SafeERC20 for IERC20;
+
+    event TokenSent(address token, address to, uint256 amount);
+
+    uint256[50] private __gap;
+
+    receive() external payable { }
+
+    constructor() EssentialContract() { }
+
+    /// @param _owner Should be DAOController so that L1 TaikoDAO can be in charge
+    function init(address _owner) external initializer {
+        __Essential_init(_owner);
+    }
+
+    /// @notice Withdraw tokens or ether with the amount equivalent to etherAmount
+    /// @param _token Token contract address or address(0) if ether
+    /// @param _to The recipient address
+    /// @param _amount The amount to withdraw
+    function withdraw(
+        address _token,
+        address _to,
+        uint256 _amount
+    )
+        external
+        onlyOwner
+        nonReentrant
+    {
+        if (_token == address(0)) {
+            LibAddress.sendEtherAndVerify(_to, _amount);
+        } else {
+            IERC20(_token).safeTransfer(_to, _amount);
+        }
+
+        emit TokenSent(_token, _to, _amount);
+    }
+}
diff --git a/packages/protocol/contracts/layer1/mainnet/TaikoDAOController.sol b/packages/protocol/contracts/layer1/mainnet/TaikoDAOController.sol
index f3aa933f293..0262dc27ca6 100644
--- a/packages/protocol/contracts/layer1/mainnet/TaikoDAOController.sol
+++ b/packages/protocol/contracts/layer1/mainnet/TaikoDAOController.sol
@@ -21,7 +21,7 @@ contract TaikoDAOController is Controller {
     /// @notice Execute a list of actions.
     /// @param _actions The actions to execute
     /// @return results_ The raw returned data from the action
-    function execute(Action[] calldata _actions)
+    function execute(bytes calldata _actions)
         external
         nonReentrant
         onlyOwner
diff --git a/packages/protocol/contracts/layer1/mainnet/libs/LibL1Addrs.sol b/packages/protocol/contracts/layer1/mainnet/libs/LibL1Addrs.sol
index 6c5e584da85..5e5a47bd906 100644
--- a/packages/protocol/contracts/layer1/mainnet/libs/LibL1Addrs.sol
+++ b/packages/protocol/contracts/layer1/mainnet/libs/LibL1Addrs.sol
@@ -5,7 +5,7 @@ pragma solidity ^0.8.24;
 /// @custom:security-contact security@taiko.xyz
 library LibL1Addrs {
     address public constant DAO = 0x9CDf589C941ee81D75F34d3755671d614f7cf261;
-    address public constant DAO_CONTROLLER = 0xfC3C4ca95a8C4e5a587373f1718CD91301d6b2D3;
+    address public constant DAO_CONTROLLER = 0x75Ba76403b13b26AD1beC70D6eE937314eeaCD0a;
     address public constant FORCED_INCLUSION_STORE = 0x05d88855361808fA1d7fc28084Ef3fCa191c4e03;
     address public constant TAIKO_WRAPPER = 0x9F9D2fC7abe74C79f86F0D1212107692430eef72;
     address public constant INBOX = 0x06a9Ab27c7e2255df1815E6CC0168d7755Feb19a;
diff --git a/packages/protocol/contracts/layer2/mainnet/BarUpgradeable.sol b/packages/protocol/contracts/layer2/mainnet/BarUpgradeable.sol
new file mode 100644
index 00000000000..8c92553fb78
--- /dev/null
+++ b/packages/protocol/contracts/layer2/mainnet/BarUpgradeable.sol
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+
+import "src/shared/common/EssentialContract.sol";
+import "src/shared/libs/LibAddress.sol";
+
+/// @title BarUpgradeable
+/// @notice The contract serves as a playground for testing DelegateOwner on mainnet
+/// @custom:security-contact security@taiko.xyz
+contract BarUpgradeable is EssentialContract {
+    using SafeERC20 for IERC20;
+
+    event TokenSent(address token, address to, uint256 amount);
+
+    uint256[50] private __gap;
+
+    receive() external payable { }
+
+    constructor() EssentialContract() { }
+
+    /// @param _owner Should be DelegateOwner so that L1 TaikoDAO can be in charge
+    function init(address _owner) external initializer {
+        __Essential_init(_owner);
+    }
+
+    /// @notice Withdraw tokens or ether with the amount equivalent to etherAmount
+    /// @param _token Token contract address or address(0) if ether
+    /// @param _to The recipient address
+    /// @param _amount The amount to withdraw
+    function withdraw(
+        address _token,
+        address _to,
+        uint256 _amount
+    )
+        external
+        onlyOwner
+        nonReentrant
+    {
+        if (_token == address(0)) {
+            LibAddress.sendEtherAndVerify(_to, _amount);
+        } else {
+            IERC20(_token).safeTransfer(_to, _amount);
+        }
+
+        emit TokenSent(_token, _to, _amount);
+    }
+}
diff --git a/packages/protocol/contracts/layer2/mainnet/DelegateController.sol b/packages/protocol/contracts/layer2/mainnet/DelegateController.sol
index d9047fae0b6..d8e564c8290 100644
--- a/packages/protocol/contracts/layer2/mainnet/DelegateController.sol
+++ b/packages/protocol/contracts/layer2/mainnet/DelegateController.sol
@@ -22,18 +22,7 @@ contract DelegateController is Controller, IMessageInvocable {
     error InvalidTxId();
     error SenderNotL1DaoController();
 
-    // __reserved0 and __reserved1 are here to make sure this contract's layout is compatible with
-    // the DelegateOwner contract.
-
-    // solhint-disable var-name-mixedcase
-    uint256 private __reserved0;
-
-    /// @notice The last processed execution ID.
-    uint64 public lastExecutionId; // slot 2
-
-    // solhint-disable var-name-mixedcase
-    address private __reserved1; //
-    uint256[48] private __gap;
+    uint256[50] private __gap;
 
     constructor(uint64 _l1ChainId, address _l2Bridge, address _daoController) Controller() {
         l1ChainId = _l1ChainId;
@@ -54,12 +43,9 @@ contract DelegateController is Controller, IMessageInvocable {
             ctx.srcChainId == l1ChainId && ctx.from == daoController, SenderNotL1DaoController()
         );
 
-        (uint64 executionId, Controller.Action[] memory actions) =
-            abi.decode(_data, (uint64, Controller.Action[]));
-
-        // Check txID
+        uint64 executionId = uint64(bytes8(_data[:8]));
         require(executionId == 0 || executionId == ++lastExecutionId, InvalidTxId());
 
-        _executeActions(actions);
+        _executeActions(_data[8:]);
     }
 }
diff --git a/packages/protocol/contracts/layer2/mainnet/libs/LibL2Addrs.sol b/packages/protocol/contracts/layer2/mainnet/libs/LibL2Addrs.sol
index 4daab262f9f..65413aada32 100644
--- a/packages/protocol/contracts/layer2/mainnet/libs/LibL2Addrs.sol
+++ b/packages/protocol/contracts/layer2/mainnet/libs/LibL2Addrs.sol
@@ -4,7 +4,7 @@ pragma solidity ^0.8.24;
 /// @title LibL2Addrs
 /// @custom:security-contact security@taiko.xyz
 library LibL2Addrs {
-    address public constant DELEGATE_CONTROLLER = 0x6D840cCbAea6077331A394B9104a7bAfe93AEa4A;
+    address public constant DELEGATE_CONTROLLER = 0xfA06E15B8b4c5BF3FC5d9cfD083d45c53Cbe8C7C;
     address public constant PERMISSIONLESS_EXECUTOR = 0x4EBeC8a624ac6f01Bb6C7F13947E6Af3727319CA;
     address public constant ANCHOR = 0x1670000000000000000000000000000000010001;
     address public constant BRIDGE = 0x1670000000000000000000000000000000000001;
diff --git a/packages/protocol/contracts/shared/goverance/Controller.sol b/packages/protocol/contracts/shared/goverance/Controller.sol
index 9214862d26d..f2af29ca905 100644
--- a/packages/protocol/contracts/shared/goverance/Controller.sol
+++ b/packages/protocol/contracts/shared/goverance/Controller.sol
@@ -9,6 +9,8 @@ import "src/shared/libs/LibBytes.sol";
 /// Ether and tokens.
 /// @custom:security-contact security@taiko.xyz
 abstract contract Controller is EssentialContract {
+    event DryrunGasCost(uint256 gasUsed);
+
     error DryrunSucceeded();
 
     struct Action {
@@ -17,8 +19,18 @@ abstract contract Controller is EssentialContract {
         bytes data;
     }
 
-    // For backward compatibility reasons, this contract reserves no storage slots.
-    // bytes32[50] private __gap;
+    // __reserved0 and __reserved1 are here to make sure this contract's layout is compatible with
+    // the DelegateOwner contract.
+
+    // solhint-disable var-name-mixedcase
+    uint256 private __reserved0;
+
+    /// @notice The last processed execution ID.
+    uint64 public lastExecutionId; // slot 2
+
+    // solhint-disable var-name-mixedcase
+    address private __reserved1; //
+    uint256[48] private __gap;
 
     event ActionExecuted(address indexed target, uint256 value, bytes data);
 
@@ -34,18 +46,23 @@ abstract contract Controller is EssentialContract {
         Ownable2StepUpgradeable(_contractToOwn).acceptOwnership();
     }
 
-    function dryrun(Action[] calldata _actions) external payable {
+    function dryrun(bytes calldata _actions) external payable {
+        uint256 gas = gasleft();
         _executeActions(_actions);
+        emit DryrunGasCost(gas - gasleft());
+
+        // Always revert!
         revert DryrunSucceeded();
     }
 
     /// @notice Execute a list of actions.
     /// @param _actions The actions to execute
     /// @return results_ The raw returned data from the action
-    function _executeActions(Action[] memory _actions) internal returns (bytes[] memory results_) {
-        results_ = new bytes[](_actions.length);
-        for (uint256 i; i < _actions.length; ++i) {
-            results_[i] = _executeAction(_actions[i]);
+    function _executeActions(bytes calldata _actions) internal returns (bytes[] memory results_) {
+        Action[] memory actions = abi.decode(_actions, (Action[]));
+        results_ = new bytes[](actions.length);
+        for (uint256 i; i < actions.length; ++i) {
+            results_[i] = _executeAction(actions[i]);
         }
     }
 
diff --git a/packages/protocol/deployments/mainnet-contract-logs-L1.md b/packages/protocol/deployments/mainnet-contract-logs-L1.md
index 9f3a2a2b722..ba6994a93ad 100644
--- a/packages/protocol/deployments/mainnet-contract-logs-L1.md
+++ b/packages/protocol/deployments/mainnet-contract-logs-L1.md
@@ -522,6 +522,7 @@
 
 ### TaikoDAOController
 
-- proxy: `0xfC3C4ca95a8C4e5a587373f1718CD91301d6b2D3`
+- ens: `controller.taiko.eth`
+- proxy: `0x75Ba76403b13b26AD1beC70D6eE937314eeaCD0a`
 - Owner (Taiko DAO): `0x9CDf589C941ee81D75F34d3755671d614f7cf261`
-- impl: `0xd1934807041B168f383870A0d8F565aDe2DF9D7D`
+- impl: `0x4347df63bdC82b8835fC9FF47bC5a71a12cC0f06`
diff --git a/packages/protocol/deployments/mainnet-contract-logs-L2.md b/packages/protocol/deployments/mainnet-contract-logs-L2.md
index d0cba0c21bf..20633e16c85 100644
--- a/packages/protocol/deployments/mainnet-contract-logs-L2.md
+++ b/packages/protocol/deployments/mainnet-contract-logs-L2.md
@@ -185,10 +185,11 @@
 
 #### delegate_controller
 
-- proxy: `0x6D840cCbAea6077331A394B9104a7bAfe93AEa4A`
-- impl: `0xd85BC772a1B8Db1291CDbf4965c3e3fF72AD3aF5`
-- owner: `0x6D840cCbAea6077331A394B9104a7bAfe93AEa4A`
+- proxy: `0xfA06E15B8b4c5BF3FC5d9cfD083d45c53Cbe8C7C`
+- impl: `0x6900f893Fb1dcf2868c7799Ac9a2dAf1c046c6bF`
+- owner: `0xfA06E15B8b4c5BF3FC5d9cfD083d45c53Cbe8C7C`
 - logs:
+  - https://taikoscan.io/tx/0x2d60398c8f22ebccc3e3dfc5771f95cb104b2ea003230444879b08d0649d01af
 
 ## Pacaya Contracts
 
diff --git a/packages/protocol/foundry.toml b/packages/protocol/foundry.toml
index 64238eb7f1b..36a05b1ce9d 100644
--- a/packages/protocol/foundry.toml
+++ b/packages/protocol/foundry.toml
@@ -35,6 +35,7 @@ fs_permissions = [
   { access = "read", path = "./out" },
   { access = "read-write", path = "./deployments" },
   { access = "read-write", path = "./gas-reports" },
+  { access = "read-write", path = "./script/layer1/proposals/" },
   { access = "read", path = "./test" },
   { access = "read", path = "./script/" },
   { access = "read", path = "./genesis" },
diff --git a/packages/protocol/gas-reports/layer1-contracts.txt b/packages/protocol/gas-reports/layer1-contracts.txt
index b21b6637d45..b3dd22936ae 100644
--- a/packages/protocol/gas-reports/layer1-contracts.txt
+++ b/packages/protocol/gas-reports/layer1-contracts.txt
@@ -118,11 +118,11 @@ TestPreconfWhitelist:test_whitelist_noDelay_addThenRemoveOneOperator() (gas: 157
 TestPreconfWhitelist:test_whitelist_removeNonExistingOperatorWillRevert() (gas: 25210)
 TestPreconfWhitelist:test_whitelist_selfRemoval() (gas: 180711)
 TestTaikoDAOController:test_TaikoDAOController_InitialOwner() (gas: 17879)
-TestTaikoDAOController:test_TaikoDAOController_acceptOwnershipOf() (gas: 49477)
-TestTaikoDAOController:test_TaikoDAOController_execute() (gas: 40235)
-TestTaikoDAOController:test_TaikoDAOController_executeNotOwner() (gas: 31573)
-TestTaikoDAOController:test_TaikoDAOController_receiveAndSendEther() (gas: 119741)
-TestTaikoDAOController:test_TaikoDAOController_transferERC20() (gas: 601975)
+TestTaikoDAOController:test_TaikoDAOController_acceptOwnershipOf() (gas: 49495)
+TestTaikoDAOController:test_TaikoDAOController_execute() (gas: 40355)
+TestTaikoDAOController:test_TaikoDAOController_executeNotOwner() (gas: 31597)
+TestTaikoDAOController:test_TaikoDAOController_receiveAndSendEther() (gas: 119921)
+TestTaikoDAOController:test_TaikoDAOController_transferERC20() (gas: 602095)
 TestTokenUnlock:test_tokenunlock_delegate() (gas: 212098)
 TestTokenUnlock:test_tokenunlock_multiple_vest_withdrawal() (gas: 233910)
 TestTokenUnlock:test_tokenunlock_multiple_vest_withdrawing() (gas: 464418)
diff --git a/packages/protocol/layout/layer1-contracts.txt b/packages/protocol/layout/layer1-contracts.txt
index 740882d545a..454cefc8112 100644
--- a/packages/protocol/layout/layer1-contracts.txt
+++ b/packages/protocol/layout/layer1-contracts.txt
@@ -851,7 +851,15 @@
 |
 | __gap                       | uint256[49] | 202  | 0      | 1568  |
 |
-| __gap                       | bytes32[50] | 251  | 0      | 1600  |
+| __reserved0                 | uint256     | 251  | 0      | 32    |
+|
+| lastExecutionId             | uint64      | 252  | 0      | 8     |
+|
+| __reserved1                 | address     | 252  | 8      | 20    |
+|
+| __gap                       | uint256[48] | 253  | 0      | 1536  |
+|
+| __gap                       | bytes32[50] | 301  | 0      | 1600  |
 ╰-----------------------------+-------------+------+--------+-------+--------------------------------------------------------------------╯
 
 
diff --git a/packages/protocol/layout/layer2-contracts.txt b/packages/protocol/layout/layer2-contracts.txt
index a524bb4b695..b643370714c 100644
--- a/packages/protocol/layout/layer2-contracts.txt
+++ b/packages/protocol/layout/layer2-contracts.txt
@@ -136,6 +136,8 @@
 | __reserved1                 | address     | 252  | 8      | 20    |
 |
 | __gap                       | uint256[48] | 253  | 0      | 1536  |
+|
+| __gap                       | uint256[50] | 301  | 0      | 1600  |
 ╰-----------------------------+-------------+------+--------+-------+--------------------------------------------------------------------╯
 
 
diff --git a/packages/protocol/package.json b/packages/protocol/package.json
index 84374ac5076..b76066b344c 100644
--- a/packages/protocol/package.json
+++ b/packages/protocol/package.json
@@ -23,6 +23,12 @@
     "eslint": "pnpm exec eslint --fix --ignore-path .eslintignore --ext .js,.ts .",
     "fmt:sol": "forge fmt && pnpm solhint 'contracts/**/*.sol'",
     "test:coverage": "mkdir -p coverage && forge coverage --report lcov && lcov --ignore-errors inconsistent --ignore-errors unused --remove ./lcov.info -o ./coverage/lcov.info 'test/' 'script/' 'contracts/thirdparty/' && genhtml coverage/lcov.info --branch-coverage --output-dir coverage --ignore-errors category --ignore-errors inconsistent --ignore-errors corrupt && open coverage/index.html",
+    "proposal": "FOUNDRY_PROFILE=layer1 MODE=print forge script script/layer1/proposals/Proposal$P.s.sol:Proposal$P",
+    "proposal:dryrun:l1": "FOUNDRY_PROFILE=layer1 MODE=l1dryrun forge script --chain-id=1 --broadcast --rpc-url https://ethereum.publicnode.com script/layer1/proposals/Proposal$P.s.sol:Proposal$P",
+    "proposal:dryrun:l2": "FOUNDRY_PROFILE=layer2 MODE=l2dryrun forge script --chain-id=167000 --broadcast --rpc-url https://rpc.taiko.xyz script/layer1/proposals/Proposal$P.s.sol:Proposal$P",
+    "proposal:print": "FOUNDRY_PROFILE=layer1 MODE=print forge script --via-ir script/layer1/proposals/Proposal$PROPOSAL.s.sol:Proposal$PROPOSAL",
+    "proposal:l1dryrun": "FOUNDRY_PROFILE=layer1 MODE=l1dryrun forge script --via-ir --chain-id=1 script/layer1/proposals/Proposal$PROPOSAL.s.sol:Proposal$PROPOSAL",
+    "proposal:l2dryrun": "FOUNDRY_PROFILE=layer2 MODE=l2dryrun forge script --via-ir --chain-id=167000 script/layer1/proposals/Proposal$PROPOSAL.s.sol:Proposal$PROPOSAL",
     "genesis:gen": "pnpm compile:l2 && pnpm compile:shared && ts-node ./test/genesis/generate/generate.ts",
     "genesis:test": "./test/genesis/genesis.test.sh",
     "export:simconf": "mkdir -p simulation/out && FOUNDRY_PROFILE=layer2 forge test --match-test 'test_L2_NoFeeCheck_simulation' -vv > simulation/out/simconf_$(date +%s).txt"
diff --git a/packages/protocol/script/layer1/mainnet/DeployFooUpgradeable.s.sol b/packages/protocol/script/layer1/mainnet/DeployFooUpgradeable.s.sol
new file mode 100644
index 00000000000..1de115b3493
--- /dev/null
+++ b/packages/protocol/script/layer1/mainnet/DeployFooUpgradeable.s.sol
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import "script/BaseScript.sol";
+import { LibL1Addrs as L1 } from "src/layer1/mainnet/libs/LibL1Addrs.sol";
+import { FooUpgradeable } from "src/layer1/mainnet/FooUpgradeable.sol";
+
+//  forge script --rpc-url  https://rpc.mainnet.taiko.xyz script/DeployDelegateController.s.sol
+contract DeployFooUpgradeable is BaseScript {
+    function run() external broadcast {
+        // To verify the contract, run the following command:
+        // FOUNDRY_PROFILE=layer2 forge verify-contract \
+        // 0x0919082b1e30d212e0359ea10cCCD6955ddc8263 \
+        // contracts/layer2/mainnet/Barupgradeable.sol:BarUpgradeable \
+        // --watch \
+        // --verifier-url https://api.taikoscan.io/api \
+        // --etherscan-api-key (echo $ETHERSCAN_API_KEY)
+
+        //   > 'bar_upgradeable'
+        //          proxy   : 0x877DDC3AebDD3010714B16769d6dB0Cb11abaF30
+        //          impl    : 0x0919082b1e30d212e0359ea10cCCD6955ddc8263
+        //          owner   : 0xe1eFEd95aDc9250A633ac3f6Ff8BA3F2cD0855A4
+        //          chain id: 167000
+        address fooUpgradeableImpl1 = address(new FooUpgradeable());
+
+        console.log("fooUpgradeableImpl1", fooUpgradeableImpl1);
+        deploy({
+            name: "foo_upgradeable",
+            impl: fooUpgradeableImpl1,
+            data: abi.encodeCall(FooUpgradeable.init, (L1.DAO_CONTROLLER))
+        });
+    }
+}
diff --git a/packages/protocol/script/layer1/mainnet/DeployTaikoDAOController.s.sol b/packages/protocol/script/layer1/mainnet/DeployTaikoDAOController.s.sol
index 4b26800b269..0d9f94cf86b 100644
--- a/packages/protocol/script/layer1/mainnet/DeployTaikoDAOController.s.sol
+++ b/packages/protocol/script/layer1/mainnet/DeployTaikoDAOController.s.sol
@@ -2,11 +2,20 @@
 pragma solidity ^0.8.24;
 
 import "src/layer1/mainnet/TaikoDAOController.sol";
+import "src/layer1/mainnet/libs/LibL1Addrs.sol";
 import "script/BaseScript.sol";
 
 contract DeployTaikoDAOController is BaseScript {
     function run() external broadcast {
-        address taikoDaoControllerImpl2 = address(new TaikoDAOController());
-        console2.log("taikoDaoControllerImpl2:", taikoDaoControllerImpl2);
+        address taikoDaoControllerImpl3 = address(new TaikoDAOController());
+        console2.log("taikoDaoControllerImpl3:", taikoDaoControllerImpl3);
+        // 0x4347df63bdC82b8835fC9FF47bC5a71a12cC0f06
+
+        // 0x75Ba76403b13b26AD1beC70D6eE937314eeaCD0a
+        deploy({
+            name: "taiko_dao_controller2",
+            impl: taikoDaoControllerImpl3,
+            data: abi.encodeCall(TaikoDAOController.init, (LibL1Addrs.DAO))
+        });
     }
 }
diff --git a/packages/protocol/script/layer1/proposals/BuildProposal.sol b/packages/protocol/script/layer1/proposals/BuildProposal.sol
index 394c9fcb47f..b7d0994143e 100644
--- a/packages/protocol/script/layer1/proposals/BuildProposal.sol
+++ b/packages/protocol/script/layer1/proposals/BuildProposal.sol
@@ -3,6 +3,7 @@ pragma solidity ^0.8.24;
 
 import "forge-std/src/Script.sol";
 import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
 import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
 import "src/layer1/mainnet/TaikoDAOController.sol";
 import "src/shared/bridge/IBridge.sol";
@@ -10,15 +11,93 @@ import { LibL1Addrs as L1 } from "src/layer1/mainnet/libs/LibL1Addrs.sol";
 import { LibL2Addrs as L2 } from "src/layer2/mainnet/libs/LibL2Addrs.sol";
 
 abstract contract BuildProposal is Script {
+    error TargetIsZeroAddress();
+    error TargetIsDAOController();
+    error DelegateControllerNotSelfOwned();
+    error DelegateControllerIncorrectL2Bridge();
+    error DelegateControllerIncorrectDaoController();
+
     modifier broadcast() {
         vm.startBroadcast();
         _;
         vm.stopBroadcast();
     }
 
+    function run() external {
+        string memory mode = vm.envString("MODE");
+        if (keccak256(abi.encodePacked(mode)) == keccak256(abi.encodePacked("print"))) {
+            logProposalAction(vm.envString("P"));
+        } else if (keccak256(abi.encodePacked(mode)) == keccak256(abi.encodePacked("l1dryrun"))) {
+            dryrunL1Actions();
+        } else if (keccak256(abi.encodePacked(mode)) == keccak256(abi.encodePacked("l2dryrun"))) {
+            dryrunL2Actions();
+        } else {
+            console2.log("Error: Invalid mode. Must be one of: print, l1dryrun, l2dryrun");
+        }
+    }
+
+    function proposalConfig()
+        internal
+        pure
+        virtual
+        returns (uint64 l2ExecutionId, uint32 l2GasLimit)
+    { }
+
     function buildL1Actions() internal pure virtual returns (Controller.Action[] memory);
     function buildL2Actions() internal pure virtual returns (Controller.Action[] memory);
 
+    function logProposalAction(string memory proposalId) internal {
+        Controller.Action[] memory allActions = _buildAllActions();
+
+        string memory fileName =
+            string.concat("./script/layer1/proposals/Proposal", proposalId, ".action.md");
+
+        string memory fileContent = string(
+            abi.encodePacked(
+                "# Proposal",
+                proposalId,
+                "\n",
+                "- To (DAO Controller): `",
+                vm.toString(L1.DAO_CONTROLLER),
+                "`\n- Value: `0`\n- Calldata: `",
+                vm.toString(abi.encode(allActions)),
+                "`\n"
+            )
+        );
+
+        vm.writeFile(fileName, fileContent);
+
+        console2.log(fileContent);
+        console2.log("Proposal action details written to", fileName);
+    }
+
+    function dryrunL1Actions() internal broadcast {
+        Controller(payable(L1.DAO_CONTROLLER)).dryrun(abi.encode(_buildAllActions()));
+    }
+
+    function dryrunL2Actions() internal broadcast {
+        require(
+            Ownable(L2.DELEGATE_CONTROLLER).owner() == L2.DELEGATE_CONTROLLER,
+            DelegateControllerNotSelfOwned()
+        );
+
+        (bool success, bytes memory result) =
+            L2.DELEGATE_CONTROLLER.staticcall(abi.encodeWithSignature("l2Bridge()"));
+        require(
+            success && abi.decode(result, (address)) == L2.BRIDGE,
+            DelegateControllerIncorrectL2Bridge()
+        );
+
+        (success, result) =
+            L2.DELEGATE_CONTROLLER.staticcall(abi.encodeWithSignature("daoController()"));
+        require(
+            success && abi.decode(result, (address)) == L1.DAO_CONTROLLER,
+            DelegateControllerIncorrectDaoController()
+        );
+
+        Controller(payable(L2.DELEGATE_CONTROLLER)).dryrun(abi.encode(buildL2Actions()));
+    }
+
     function buildUpgradeAction(
         address _target,
         address _newImpl
@@ -34,39 +113,31 @@ abstract contract BuildProposal is Script {
         });
     }
 
-    function tryrunL1Actions(uint64 executionId, uint32 l2GasLimit) internal broadcast {
-        TaikoDAOController(payable(L1.DAO_CONTROLLER)).dryrun{ value: 0 }(
-            buildAllActions(executionId, l2GasLimit)
-        );
-    }
+    function _buildAllActions() private pure returns (Controller.Action[] memory allActions_) {
+        (uint64 l2ExecutionId, uint32 l2GasLimit) = proposalConfig();
 
-    function tryrunL2Actions() internal broadcast {
-        Controller(payable(L2.DELEGATE_CONTROLLER)).dryrun{ value: 0 }(buildL2Actions());
-    }
-
-    function buildAllActions(
-        uint64 executionId,
-        uint32 l2GasLimit
-    )
-        internal
-        pure
-        returns (Controller.Action[] memory allActions_)
-    {
         Controller.Action[] memory l1Actions = buildL1Actions();
         allActions_ = new Controller.Action[](l1Actions.length + 1);
 
         for (uint256 i; i < l1Actions.length; ++i) {
             allActions_[i] = l1Actions[i];
+            require(l1Actions[i].target != address(0), TargetIsZeroAddress());
+            require(l1Actions[i].target != L1.DAO_CONTROLLER, TargetIsDAOController());
         }
 
         Controller.Action[] memory l2Actions = buildL2Actions();
+        for (uint256 i; i < l2Actions.length; ++i) {
+            require(l2Actions[i].target != address(0), TargetIsZeroAddress());
+        }
 
         IBridge.Message memory message;
+        message.srcOwner = L1.DAO_CONTROLLER;
+        message.destOwner = L2.PERMISSIONLESS_EXECUTOR;
         message.destChainId = 167_000;
         message.gasLimit = l2GasLimit;
-        message.destOwner = L2.PERMISSIONLESS_EXECUTOR;
         message.data = abi.encodeCall(
-            IMessageInvocable.onMessageInvocation, (abi.encode(executionId, l2Actions))
+            IMessageInvocable.onMessageInvocation,
+            (abi.encodePacked(l2ExecutionId, abi.encode(l2Actions)))
         );
 
         allActions_[l1Actions.length] = Controller.Action({
@@ -75,12 +146,4 @@ abstract contract BuildProposal is Script {
             data: abi.encodeCall(IBridge.sendMessage, (message))
         });
     }
-
-    function logProposalAction(uint64 executionId, uint32 l2GasLimit) internal pure {
-        Controller.Action[] memory allActions = buildAllActions(executionId, l2GasLimit);
-
-        console2.log("Proposal Action--------------------------------");
-        console2.log("Target:", L1.DAO_CONTROLLER);
-        console2.logBytes(abi.encodeCall(TaikoDAOController.execute, (allActions)));
-    }
 }
diff --git a/packages/protocol/script/layer1/proposals/Proposal0002.action.md b/packages/protocol/script/layer1/proposals/Proposal0002.action.md
new file mode 100644
index 00000000000..9b0b5c96656
--- /dev/null
+++ b/packages/protocol/script/layer1/proposals/Proposal0002.action.md
@@ -0,0 +1,5 @@
+# Proposal0002
+
+- To (DAO Controller): `0x75Ba76403b13b26AD1beC70D6eE937314eeaCD0a`
+- Value: `0`
+- Calldata: `0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000a58e81fe9b61b5c3fe2afd33cf304c454abfc7cb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c47f002700000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000014636f6e74726f6c6c65722e7461696b6f2e65746800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0a0d6bd4aa94f53f3fb2c88488202a9e9ed2c5500000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000f0a0d6bd4aa94f53f3fb2c88488202a9e9ed2c5500000000000000000000000000000000000000000000000000000000000f4240000000000000000000000000000000000000000000000000000000000000000000000000000000004c234082e57d7f82ab8326a338d8f17fabedbd97000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024f2fde38b000000000000000000000000f0a0d6bd4aa94f53f3fb2c88488202a9e9ed2c5500000000000000000000000000000000000000000000000000000000000000000000000000000000d60247c6848b7ca29eddf63aa924e53db6ddd8ec0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008041bdb003700000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017d78400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000075ba76403b13b26ad1bec70d6ee937314eeacd0a0000000000000000000000000000000000000000000000000000000000028c580000000000000000000000004ebec8a624ac6f01bb6c7f13947e6af3727319ca00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000006447f07c947000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000005e800000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000f0a0d6bd4aa94f53f3fb2c88488202a9e9ed2c5500000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a9d23408b9ba935c230493c40c73824df71a0975000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000f0a0d6bd4aa94f53f3fb2c88488202a9e9ed2c550000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000877ddc3aebdd3010714b16769d6db0cb11abaf300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000243659cfe6000000000000000000000000ca335abacade77a8c3e2e82b551b3e3337f2caf400000000000000000000000000000000000000000000000000000000000000000000000000000000877ddc3aebdd3010714b16769d6db0cb11abaf30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024f2fde38b000000000000000000000000f0a0d6bd4aa94f53f3fb2c88488202a9e9ed2c5500000000000000000000000000000000000000000000000000000000000000000000000000000000877ddc3aebdd3010714b16769d6db0cb11abaf30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064d9caed120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0a0d6bd4aa94f53f3fb2c88488202a9e9ed2c5500000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000000000000000000000877ddc3aebdd3010714b16769d6db0cb11abaf30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064d9caed12000000000000000000000000a9d23408b9ba935c230493c40c73824df71a0975000000000000000000000000f0a0d6bd4aa94f53f3fb2c88488202a9e9ed2c550000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000`
diff --git a/packages/protocol/script/layer1/proposals/Proposal0002.md b/packages/protocol/script/layer1/proposals/Proposal0002.md
new file mode 100644
index 00000000000..9c92445601e
--- /dev/null
+++ b/packages/protocol/script/layer1/proposals/Proposal0002.md
@@ -0,0 +1,81 @@
+# Cross-Chain Proposal Test
+
+This proposal demonstrates the Taiko DAO's capability to execute multiple cross-chain actions involving both Layer 1 (L1) and Layer 2 (L2) operations.
+
+## L1 Actions
+
+The following L1 actions will be executed by the DAOController:
+
+- Set the reverse ENS name of DAOController to "controller.taiko.eth"
+- Transfer 0.0001 ETH from DAOController to Daniel Wang
+- Transfer 1 USDC from DAOController to Daniel Wang
+- Transfer FooUpgradeable's ownership to Daniel Wang
+
+These actions aim to validate that the DAOController can also securely manage and transfer both Ether and ERC20 tokens.
+
+## L2 Actions
+
+The following L2 actions will be executed via the DelegateController contract:
+
+- Transfer 0.0001 Ether from DelegateController to Daniel Wang
+- Transfer 1 TAIKO from DelegateController to Daniel Wang
+- Upgrade BarUpgradeable to use a new (but identical)implementation
+- Transfer BarUpgradeable's ownership to Daniel Wang
+- Transfer 0.0001 Ether from BarUpgradeable to Daniel Wang
+- Transfer 1 TAIKO from BarUpgradeable to Daniel Wang
+
+These actions aim to validate that the DelegateController can also securely manage and transfer both Ether and tokens.
+
+## Action Verification
+
+### Action Generation
+
+All actions in this proposal are generated using the following script:
+
+[Proposal0002.s.sol](https://github.com/taikoxyz/taiko-mono/blob/main/packages/protocol/script/layer1/proposals/Proposal0002.s.sol)
+
+Please review the `proposalConfig()`, `buildL1Actions()`, and `buildL2Actions()` functions for logic verification.
+
+### proposalConfig()
+
+This function returns execution parameters for L2:
+
+- `l2ExecutionId`: Determines the execution order. Here, `l2ExecutionId = 1`, which enforces that the proposal must execute in sequence. Setting this value to `0` would skip ordering constraints, allowing execution at any time after approval.
+- `l2GasLimit`: Specifies the minimum gas required for executing L2 actions. This proposal sets the limit to `25,000,000`, allowing any L2 address to trigger execution.
+
+### buildL1Actions()
+
+This function generates all L1 transactions. The final action in the list sends a cross-chain message via the Taiko bridge, embedding the L2 action payload. This message targets the L2 `DelegateController` contract to execute the L2 actions sequentially.
+
+###buildL2Actions()
+
+This function defines the atomic set of L2 transactions. If any single action fails, the entire batch is reverted. Nevertheless, the proposal is still marked as executed from the DAO's perspective.
+
+L2 execution is permissionless and may be initiated by any address that supplies a gas limit of at least `25,000,000`.
+
+### Action Bytecode Verification
+
+To confirm the action data matches this proposal, clone the Taiko [monorepo](https://github.com/taikoxyz/taiko-mono), navigate to `packages/protocol`, run `pnpm install`, then:
+
+```bash
+P=0002 pnpm proposal
+```
+
+This will print the generated calldata and write it to `Proposal0002.action.md` for comparison. Byte-by-byte validation can then be performed.
+
+### Dryrun Execution
+
+To test the executability of the actions, use:
+
+```bash
+P=0002 pnpm proposal:dryrun:l1
+P=0002 pnpm proposal:dryrun:l2
+```
+
+If the execution ends in a revert with `DryrunSucceeded`, this indicates the current on-chain state allows for successful execution. Note that state changes from other proposals may affect future outcomes.
+
+Please be aware that `l2ExecutionId` won't be verified during the dryrun.
+
+## Risks and Contingencies
+
+Despite thorough testing, execution risks remain. Should the actions fail, a follow-up proposal will be submitted. This process contributes to continuous improvement in proposal reliability and infrastructure resilience.
diff --git a/packages/protocol/script/layer1/proposals/Proposal0002.s.sol b/packages/protocol/script/layer1/proposals/Proposal0002.s.sol
index 39e1f82b024..65dca628143 100644
--- a/packages/protocol/script/layer1/proposals/Proposal0002.s.sol
+++ b/packages/protocol/script/layer1/proposals/Proposal0002.s.sol
@@ -8,85 +8,101 @@ interface IBarContract {
     function withdraw(address _token, address _to, uint256 _amount) external;
 }
 
-// FOUNDRY_PROFILE=layer1 forge script script/layer1/proposals/Proposal0002.s.sol:Proposal0002
+interface IReverseRegistrar {
+    function setName(string memory name) external returns (bytes32);
+}
+
+// To print the proposal action data: `P=0002 pnpm proposal`
+// To dryrun the proposal on L1: `P=0002 pnpm proposal:dryrun:l1`
+// To dryrun the proposal on L2: `P=0002 pnpm proposal:dryrun:l2`
 contract Proposal0002 is BuildProposal {
     // L1 contracts
+    address public constant L1_FOO_CONTRACT = 0x4c234082E57d7f82AB8326A338d8F17FAbEdbd97;
     address public constant L1_DANIEL_WANG_ADDRESS = 0xf0A0d6Bd4aA94F53F3FB2c88488202a9E9eD2c55;
 
     // L2 contracts
-    address public constant L2_DELEGATE_CONTROLLER_NEW_IMPL =
-        0x15a4109238d5673C9E6Cca27831AEF1AfdA99830;
-    address public constant L2_BAR_CONTRACT = 0xD381F8e696a8e20a5d0c0a8658e5C1Cb23C0AB69;
-    address public constant L2_BAR_CONTRACT_NEW_IMPL = 0x4c234082E57d7f82AB8326A338d8F17FAbEdbd97;
+    address public constant L2_BAR_CONTRACT = 0x0e577Bb67d38c18E4B9508984DA36d6D316ade58;
+    address public constant L2_BAR_CONTRACT_NEW_IMPL = 0x7fBd8DbA7678eDb9eaDf83e204372d8a39F75398;
     address public constant L2_DANIEL_WANG_ADDRESS = 0xf0A0d6Bd4aA94F53F3FB2c88488202a9E9eD2c55;
 
-    function run() external pure {
-        // FOUNDRY_PROFILE=layer1 forge script \
-        // script/layer1/proposals/Proposal0002.s.sol:Proposal0002 \
-
-        logProposalAction({ executionId: 1, l2GasLimit: 25_000_000 });
-
-        // FOUNDRY_PROFILE=layer2 forge script \
-        // script/layer1/proposals/Proposal0002.s.sol:Proposal0002 \
-        // --private-key $(echo $PRIVATE_KEY) \
-        // --chain 167000 --broadcast --rpc-url https://rpc.taiko.xyz \
-
-        // tryrunL2Actions();
-
-        // FOUNDRY_PROFILE=layer1 forge script \
-        // script/layer1/proposals/Proposal0002.s.sol:Proposal0002 \
-        // --private-key $(echo $PRIVATE_KEY) \
-        // --chain 1 --rpc-url ${echo $RPC_URL} \
-
-        // tryrunL1Actions({ executionId: 1, l2GasLimit: 25_000_000 });
+    function proposalConfig()
+        internal
+        pure
+        override
+        returns (uint64 l2ExecutionId, uint32 l2GasLimit)
+    {
+        l2ExecutionId = 1;
+        l2GasLimit = 25_000_000;
     }
 
     function buildL1Actions() internal pure override returns (Controller.Action[] memory actions) {
-        actions = new Controller.Action[](2);
+        actions = new Controller.Action[](4);
 
-        // Transfer 0.001 ETH from DAO Controller to Daniel Wang
-        actions[0] =
-            Controller.Action({ target: L1_DANIEL_WANG_ADDRESS, value: 0.001 ether, data: "" });
+        // Set the reverse ENS name of the DAOController to "controller.taiko.eth"
+        actions[0] = Controller.Action({
+            target: L1.ENS_REVERSE_REGISTRAR,
+            value: 0,
+            data: abi.encodeCall(IReverseRegistrar.setName, ("controller.taiko.eth"))
+        });
 
-        // Transfer 1 USDC from DAO Controller to Daniel Wang
-        actions[1] = Controller.Action({
+        // Transfer 0.0001 ETH from DAOController to Daniel Wang
+        actions[1] =
+            Controller.Action({ target: L1_DANIEL_WANG_ADDRESS, value: 0.0001 ether, data: "" });
+
+        // Transfer 1 USDC from DAOController to Daniel Wang
+        actions[2] = Controller.Action({
             target: L1.USDC_TOKEN,
             value: 0,
             data: abi.encodeCall(IERC20.transfer, (L1_DANIEL_WANG_ADDRESS, 1e6))
         });
+
+        // Transfer FooUpgradeable's ownership to Daniel Wang
+        actions[3] = Controller.Action({
+            target: L1_FOO_CONTRACT,
+            value: 0,
+            data: abi.encodeCall(Ownable.transferOwnership, (L1_DANIEL_WANG_ADDRESS))
+        });
     }
 
     function buildL2Actions() internal pure override returns (Controller.Action[] memory actions) {
-        actions = new Controller.Action[](5);
+        actions = new Controller.Action[](6);
 
-        // Upgrade DelegateController to a new implementation
-        actions[0] = buildUpgradeAction(L2.DELEGATE_CONTROLLER, L2_DELEGATE_CONTROLLER_NEW_IMPL);
+        // Transfer 0.0001 Ether from DelegateController to Daniel Wang
+        actions[0] =
+            Controller.Action({ target: L2_DANIEL_WANG_ADDRESS, value: 0.0001 ether, data: "" });
 
-        // Transfer 1 TAIKO to Daniel Wang
+        // Transfer 1 TAIKO from DelegateController to Daniel Wang
         actions[1] = Controller.Action({
             target: L2.TAIKO_TOKEN,
             value: 0,
             data: abi.encodeCall(IERC20.transfer, (L2_DANIEL_WANG_ADDRESS, 1 ether))
         });
 
-        // Upgrade Bar contract to a new implementation
+        // Upgrade BarUpgradeable to use a new (but identical)implementation
         actions[2] = buildUpgradeAction(L2_BAR_CONTRACT, L2_BAR_CONTRACT_NEW_IMPL);
 
-        // Transfer 1 TAIKO from Bar contract to Daniel Wang
+        // Transfer BarUpgradeable's ownership to Daniel Wang
         actions[3] = Controller.Action({
+            target: L2_BAR_CONTRACT,
+            value: 0,
+            data: abi.encodeCall(Ownable.transferOwnership, (L2_DANIEL_WANG_ADDRESS))
+        });
+
+        // Transfer 0.0001 Ether from BarUpgradeable to Daniel Wang
+        actions[4] = Controller.Action({
             target: L2_BAR_CONTRACT,
             value: 0,
             data: abi.encodeCall(
-                IBarContract.withdraw, (L2.TAIKO_TOKEN, L2_DANIEL_WANG_ADDRESS, 1 ether)
+                IBarContract.withdraw, (address(0), L2_DANIEL_WANG_ADDRESS, 0.0001 ether)
             )
         });
 
-        // Transfer 0.001 Ether from Bar contract to Daniel Wang
-        actions[4] = Controller.Action({
+        // Transfer 1 TAIKO from BarUpgradeable to Daniel Wang
+        actions[5] = Controller.Action({
             target: L2_BAR_CONTRACT,
             value: 0,
             data: abi.encodeCall(
-                IBarContract.withdraw, (address(0), L2_DANIEL_WANG_ADDRESS, 0.001 ether)
+                IBarContract.withdraw, (L2.TAIKO_TOKEN, L2_DANIEL_WANG_ADDRESS, 1 ether)
             )
         });
     }
diff --git a/packages/protocol/script/layer2/mainnet/DeployBarUpgradeable.s.sol b/packages/protocol/script/layer2/mainnet/DeployBarUpgradeable.s.sol
new file mode 100644
index 00000000000..260292f18bb
--- /dev/null
+++ b/packages/protocol/script/layer2/mainnet/DeployBarUpgradeable.s.sol
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import "script/BaseScript.sol";
+import { LibL2Addrs as L2 } from "src/layer2/mainnet/libs/LibL2Addrs.sol";
+import { BarUpgradeable } from "src/layer2/mainnet/Barupgradeable.sol";
+
+//  forge script --rpc-url  https://rpc.mainnet.taiko.xyz script/DeployDelegateController.s.sol
+contract DeployBarUpgradeable is BaseScript {
+    function run() external broadcast {
+        // To verify the contract, run the following command:
+        // FOUNDRY_PROFILE=layer2 forge verify-contract \
+        // 0x0919082b1e30d212e0359ea10cCCD6955ddc8263 \
+        // contracts/layer2/mainnet/Barupgradeable.sol:BarUpgradeable \
+        // --watch \
+        // --verifier-url https://api.taikoscan.io/api \
+        // --etherscan-api-key (echo $ETHERSCAN_API_KEY)
+
+        //  > 'bar_upgradeable'
+        //          proxy   : 0x0e577Bb67d38c18E4B9508984DA36d6D316ade58
+        //          impl    : 0x49c3a8c535c42939D38d46dF56d49e8B2cb58409
+        //          owner   : 0xfA06E15B8b4c5BF3FC5d9cfD083d45c53Cbe8C7C
+        //          chain id: 167000
+        address barUpgradeableImpl3 = address(new BarUpgradeable());
+        console.log("barUpgradeableImpl3", barUpgradeableImpl3);
+
+        // deploy({
+        //     name: "bar_upgradeable",
+        //     impl: barUpgradeableImpl3,
+        //     data: abi.encodeCall(BarUpgradeable.init, (L2.DELEGATE_CONTROLLER))
+        // });
+    }
+}
diff --git a/packages/protocol/script/layer2/mainnet/DeployDelegateController.s.sol b/packages/protocol/script/layer2/mainnet/DeployDelegateController.s.sol
index b6347240d14..a04f70a19a6 100644
--- a/packages/protocol/script/layer2/mainnet/DeployDelegateController.s.sol
+++ b/packages/protocol/script/layer2/mainnet/DeployDelegateController.s.sol
@@ -12,7 +12,18 @@ import { LibL2Addrs as L2 } from "src/layer2/mainnet/libs/LibL2Addrs.sol";
 //  forge script --rpc-url  https://rpc.mainnet.taiko.xyz script/DeployDelegateController.s.sol
 contract DeployDelegateController is BaseScript {
     function run() external broadcast {
-        address delegateControllerImpl1 = address(
+        // To verify the contract, run the following command:
+        // FOUNDRY_PROFILE=layer2 forge verify-contract \
+        // 0x6f4006D0f805B55D1106dFdDfb73C3D53d12c12D \
+        // contracts/layer2/mainnet/DelegateController.sol:DelegateController \
+        // --watch \
+        // --constructor-args
+        // 0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000001670000000000000000000000000000000000001000000000000000000000000fc3c4ca95a8c4e5a587373f1718cd91301d6b2d3
+        // \
+        // --verifier-url https://api.taikoscan.io/api \
+        // --etherscan-api-key (echo $ETHERSCAN_API_KEY)
+
+        address delegateControllerImpl4 = address(
             new DelegateController(
                 uint64(LibNetwork.ETHEREUM_MAINNET), L2.BRIDGE, L1.DAO_CONTROLLER
             )
@@ -20,7 +31,7 @@ contract DeployDelegateController is BaseScript {
 
         deploy({
             name: "delegate_controller",
-            impl: delegateControllerImpl1,
+            impl: delegateControllerImpl4,
             data: abi.encodeCall(DelegateController.init, ())
         });
     }
diff --git a/packages/protocol/test/layer1/governance/TaikoDAOController.t.sol b/packages/protocol/test/layer1/governance/TaikoDAOController.t.sol
index ea6f25783c2..92acd60a473 100644
--- a/packages/protocol/test/layer1/governance/TaikoDAOController.t.sol
+++ b/packages/protocol/test/layer1/governance/TaikoDAOController.t.sol
@@ -66,7 +66,7 @@ contract TestTaikoDAOController is Layer1Test {
 
         Controller.Action[] memory actions = new Controller.Action[](1);
         actions[0] = Controller.Action({ target: target, value: 0, data: data });
-        bytes[] memory results = daoController.execute(actions);
+        bytes[] memory results = daoController.execute(abi.encode(actions));
 
         assertEq(
             results[0], abi.encode("someFunction"), "Forwarded call should return correct data"
@@ -80,7 +80,7 @@ contract TestTaikoDAOController is Layer1Test {
 
         vm.startPrank(newOwner);
         vm.expectRevert("Ownable: caller is not the owner");
-        daoController.execute(actions);
+        daoController.execute(abi.encode(actions));
         vm.stopPrank();
     }
 
@@ -104,7 +104,7 @@ contract TestTaikoDAOController is Layer1Test {
         actions[0] = Controller.Action({ target: address(David), value: 0.01 ether, data: "" });
 
         actions[1] = Controller.Action({ target: address(Frank), value: 0.02 ether, data: "" });
-        daoController.execute(actions);
+        daoController.execute(abi.encode(actions));
 
         assertEq(address(daoController).balance, 0.07 ether);
         assertEq(address(David).balance, 0.01 ether);
@@ -122,7 +122,7 @@ contract TestTaikoDAOController is Layer1Test {
             value: 0,
             data: abi.encodeWithSignature("transfer(address,uint256)", address(David), 100 ether)
         });
-        daoController.execute(actions);
+        daoController.execute(abi.encode(actions));
         assertEq(erc20.balanceOf(address(David)), 100 ether);
         assertEq(erc20.balanceOf(address(daoController)), 900 ether);
     }
diff --git a/packages/protocol/test/layer2/mainnet/DelegateController.t.sol b/packages/protocol/test/layer2/mainnet/DelegateController.t.sol
index 435ecc0d020..b2433827841 100644
--- a/packages/protocol/test/layer2/mainnet/DelegateController.t.sol
+++ b/packages/protocol/test/layer2/mainnet/DelegateController.t.sol
@@ -40,7 +40,7 @@ contract TestDelegateController is Layer2Test {
         });
 
         vm.expectRevert(Controller.DryrunSucceeded.selector);
-        tDelegateController.dryrun(actions);
+        tDelegateController.dryrun(abi.encode(actions));
 
         IBridge.Message memory message;
         message.from = daoController;
@@ -48,7 +48,8 @@ contract TestDelegateController is Layer2Test {
         message.srcChainId = ethereumChainId;
         message.destOwner = Bob;
         message.data = abi.encodeCall(
-            DelegateController.onMessageInvocation, (abi.encode(uint256(1), actions))
+            DelegateController.onMessageInvocation,
+            (abi.encodePacked(uint64(1), abi.encode(actions)))
         );
         message.to = address(tDelegateController);
 
@@ -102,15 +103,17 @@ contract TestDelegateController is Layer2Test {
         vm.deal(address(tDelegateController), 0.1 ether);
 
         vm.expectRevert(Controller.DryrunSucceeded.selector);
-        tDelegateController.dryrun(actions);
+        tDelegateController.dryrun(abi.encode(actions));
 
         IBridge.Message memory message;
         message.from = daoController;
         message.destChainId = taikoChainId;
         message.srcChainId = ethereumChainId;
         message.destOwner = Bob;
-        message.data =
-            abi.encodeCall(DelegateController.onMessageInvocation, (abi.encode(uint64(1), actions)));
+        message.data = abi.encodeCall(
+            DelegateController.onMessageInvocation,
+            (abi.encodePacked(uint64(1), abi.encode(actions)))
+        );
         message.to = address(tDelegateController);
 
         vm.prank(Bob);


Reviews:
user: login: adaki2004
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T07:04:40Z
user: login: dantaik
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T07:16:11Z
user: login: davidtaikocha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T08:02:30Z
user: login: davidtaikocha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T08:15:09Z
user: login: dantaik
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T09:12:28Z

Review comments:
diff_hunk: @@ -0,0 +1,78 @@
+# Cross-Chain Proposal Test
+
+This proposal demonstrates the Taiko DAO's capability to execute multiple cross-chain actions involving both Layer 1 (L1) and Layer 2 (L2) operations.
+
+## L1 Actions
+
+The following L1 actions will be executed by the `TaikoDAOController`:
+
+- Transfer 0.001 ETH to Daniel Wang
+- Transfer 1 USDC to Daniel Wang
+
+These actions aim to validate that the `TaikoDAOController` can securely manage and transfer both Ether and ERC20 tokens.
+
+## L2 Actions
+
+The following L2 actions will be executed via the `DelegateController` contract:
+
+- Upgrade the `DelegateController` contract to a new implementation (code remains unchanged)
+- Transfer 1 TAIKO token to Daniel Wang
+- Upgrade `BarUpgradable` (a test contract) to a new implementation (code remains unchanged)
+- Transfer 1 TAIKO token from `BarUpgradable` to Daniel Wang
+- Transfer 0.001 ETH from `BarUpgradable` to Daniel Wang
+
+These actions aim to validate that the `DelegateController` can securely manage and transfer both Ether and tokens.
+
+## Action Verification
+
+### Action Generation
+
+All actions in this proposal are generated using the following script:
+
+[Proposal0002.s.sol](https://github.com/taikoxyz/taiko-mono/blob/main/packages/protocol/script/layer1/proposals/Proposal0002.s.sol)
+
+Please review the `proposalConfig()`, `buildL1Actions()`, and `buildL2Actions()` functions for logic verification.
+
+### `proposalConfig()`
+
+This function returns execution parameters for L2:
+
+- `l2ExecutionId`: Determines the execution order. Here, `l2ExecutionId = 1`, which enforces that the proposal must execute in sequence. Setting this value to `0` would skip ordering constraints, allowing execution at any time after approval.
+- `l2GasLimit`: Specifies the minimum gas required for executing L2 actions. This proposal sets the limit to `25,000,000`, allowing any L2 address to trigger execution.
+
+### `buildL1Actions()`
+
+This function generates all L1 transactions. The final action in the list sends a cross-chain message via the Taiko bridge, embedding the L2 action payload. This message targets the L2 `DelegateController` contract to execute the L2 actions sequentially.
+
+### `buildL2Actions()`
+
+This function defines the atomic set of L2 transactions. If any single action fails, the entire batch is reverted. Nevertheless, the proposal is still marked as executed from the DAO's perspective.
+
+L2 execution is permissionless and may be initiated by any address that supplies a gas limit of at least `25,000,000`.
+
+### Action Bytecode Verification
+
+To confirm the action data matches this proposal, clone the Taiko [monorepo](https://github.com/taikoxyz/taiko-mono), navigate to `packages/protocol`, run `pnpm install`, then:
+
+```bash
+P=0002 pnpm proposal
+```
+
+This will print the generated calldata and write it to `Proposal0002.action.md` for comparison. Byte-by-byte validation can then be performed.
+
+### Dryrun Execution
+
+To test the executability of the actions, use:
+
+```bash
+P=0002 pnpm proposal:dryrun:l1
+P=0002 pnpm proposal:dryrun:l2
path: packages/protocol/script/layer1/proposals/Proposal0002.md
user: login: adaki2004
body: When i run these `P=0002 pnpm proposal:dryrun:l1 ` compiles (reverts tho due to ?), but at least compiles.

For L2, so this one: `P=0002 pnpm proposal:dryrun:l2` it gives me the following compile time issue of stack too deep:

![image](https://github.com/user-attachments/assets/19786f70-4197-4a72-a3ed-6c312c803a55)



created_at: 2025-05-22T07:04:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,78 @@
+# Cross-Chain Proposal Test
+
+This proposal demonstrates the Taiko DAO's capability to execute multiple cross-chain actions involving both Layer 1 (L1) and Layer 2 (L2) operations.
+
+## L1 Actions
+
+The following L1 actions will be executed by the `TaikoDAOController`:
+
+- Transfer 0.001 ETH to Daniel Wang
+- Transfer 1 USDC to Daniel Wang
+
+These actions aim to validate that the `TaikoDAOController` can securely manage and transfer both Ether and ERC20 tokens.
+
+## L2 Actions
+
+The following L2 actions will be executed via the `DelegateController` contract:
+
+- Upgrade the `DelegateController` contract to a new implementation (code remains unchanged)
+- Transfer 1 TAIKO token to Daniel Wang
+- Upgrade `BarUpgradable` (a test contract) to a new implementation (code remains unchanged)
+- Transfer 1 TAIKO token from `BarUpgradable` to Daniel Wang
+- Transfer 0.001 ETH from `BarUpgradable` to Daniel Wang
+
+These actions aim to validate that the `DelegateController` can securely manage and transfer both Ether and tokens.
+
+## Action Verification
+
+### Action Generation
+
+All actions in this proposal are generated using the following script:
+
+[Proposal0002.s.sol](https://github.com/taikoxyz/taiko-mono/blob/main/packages/protocol/script/layer1/proposals/Proposal0002.s.sol)
+
+Please review the `proposalConfig()`, `buildL1Actions()`, and `buildL2Actions()` functions for logic verification.
+
+### `proposalConfig()`
+
+This function returns execution parameters for L2:
+
+- `l2ExecutionId`: Determines the execution order. Here, `l2ExecutionId = 1`, which enforces that the proposal must execute in sequence. Setting this value to `0` would skip ordering constraints, allowing execution at any time after approval.
+- `l2GasLimit`: Specifies the minimum gas required for executing L2 actions. This proposal sets the limit to `25,000,000`, allowing any L2 address to trigger execution.
+
+### `buildL1Actions()`
+
+This function generates all L1 transactions. The final action in the list sends a cross-chain message via the Taiko bridge, embedding the L2 action payload. This message targets the L2 `DelegateController` contract to execute the L2 actions sequentially.
+
+### `buildL2Actions()`
+
+This function defines the atomic set of L2 transactions. If any single action fails, the entire batch is reverted. Nevertheless, the proposal is still marked as executed from the DAO's perspective.
+
+L2 execution is permissionless and may be initiated by any address that supplies a gas limit of at least `25,000,000`.
+
+### Action Bytecode Verification
+
+To confirm the action data matches this proposal, clone the Taiko [monorepo](https://github.com/taikoxyz/taiko-mono), navigate to `packages/protocol`, run `pnpm install`, then:
+
+```bash
+P=0002 pnpm proposal
+```
+
+This will print the generated calldata and write it to `Proposal0002.action.md` for comparison. Byte-by-byte validation can then be performed.
+
+### Dryrun Execution
+
+To test the executability of the actions, use:
+
+```bash
+P=0002 pnpm proposal:dryrun:l1
+P=0002 pnpm proposal:dryrun:l2
path: packages/protocol/script/layer1/proposals/Proposal0002.md
user: login: dantaik
body: interesting, no such error on my end.

@davidtaikocha can you also try
created_at: 2025-05-22T07:16:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,78 @@
+# Cross-Chain Proposal Test
+
+This proposal demonstrates the Taiko DAO's capability to execute multiple cross-chain actions involving both Layer 1 (L1) and Layer 2 (L2) operations.
+
+## L1 Actions
+
+The following L1 actions will be executed by the `TaikoDAOController`:
+
+- Transfer 0.001 ETH to Daniel Wang
+- Transfer 1 USDC to Daniel Wang
+
+These actions aim to validate that the `TaikoDAOController` can securely manage and transfer both Ether and ERC20 tokens.
+
+## L2 Actions
+
+The following L2 actions will be executed via the `DelegateController` contract:
+
+- Upgrade the `DelegateController` contract to a new implementation (code remains unchanged)
+- Transfer 1 TAIKO token to Daniel Wang
+- Upgrade `BarUpgradable` (a test contract) to a new implementation (code remains unchanged)
+- Transfer 1 TAIKO token from `BarUpgradable` to Daniel Wang
+- Transfer 0.001 ETH from `BarUpgradable` to Daniel Wang
+
+These actions aim to validate that the `DelegateController` can securely manage and transfer both Ether and tokens.
+
+## Action Verification
+
+### Action Generation
+
+All actions in this proposal are generated using the following script:
+
+[Proposal0002.s.sol](https://github.com/taikoxyz/taiko-mono/blob/main/packages/protocol/script/layer1/proposals/Proposal0002.s.sol)
+
+Please review the `proposalConfig()`, `buildL1Actions()`, and `buildL2Actions()` functions for logic verification.
+
+### `proposalConfig()`
+
+This function returns execution parameters for L2:
+
+- `l2ExecutionId`: Determines the execution order. Here, `l2ExecutionId = 1`, which enforces that the proposal must execute in sequence. Setting this value to `0` would skip ordering constraints, allowing execution at any time after approval.
+- `l2GasLimit`: Specifies the minimum gas required for executing L2 actions. This proposal sets the limit to `25,000,000`, allowing any L2 address to trigger execution.
+
+### `buildL1Actions()`
+
+This function generates all L1 transactions. The final action in the list sends a cross-chain message via the Taiko bridge, embedding the L2 action payload. This message targets the L2 `DelegateController` contract to execute the L2 actions sequentially.
+
+### `buildL2Actions()`
+
+This function defines the atomic set of L2 transactions. If any single action fails, the entire batch is reverted. Nevertheless, the proposal is still marked as executed from the DAO's perspective.
+
+L2 execution is permissionless and may be initiated by any address that supplies a gas limit of at least `25,000,000`.
+
+### Action Bytecode Verification
+
+To confirm the action data matches this proposal, clone the Taiko [monorepo](https://github.com/taikoxyz/taiko-mono), navigate to `packages/protocol`, run `pnpm install`, then:
+
+```bash
+P=0002 pnpm proposal
+```
+
+This will print the generated calldata and write it to `Proposal0002.action.md` for comparison. Byte-by-byte validation can then be performed.
+
+### Dryrun Execution
+
+To test the executability of the actions, use:
+
+```bash
+P=0002 pnpm proposal:dryrun:l1
+P=0002 pnpm proposal:dryrun:l2
path: packages/protocol/script/layer1/proposals/Proposal0002.md
user: login: davidtaikocha
body: Will try later
created_at: 2025-05-22T08:02:30Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,78 @@
+# Cross-Chain Proposal Test
+
+This proposal demonstrates the Taiko DAO's capability to execute multiple cross-chain actions involving both Layer 1 (L1) and Layer 2 (L2) operations.
+
+## L1 Actions
+
+The following L1 actions will be executed by the `TaikoDAOController`:
+
+- Transfer 0.001 ETH to Daniel Wang
+- Transfer 1 USDC to Daniel Wang
+
+These actions aim to validate that the `TaikoDAOController` can securely manage and transfer both Ether and ERC20 tokens.
+
+## L2 Actions
+
+The following L2 actions will be executed via the `DelegateController` contract:
+
+- Upgrade the `DelegateController` contract to a new implementation (code remains unchanged)
+- Transfer 1 TAIKO token to Daniel Wang
+- Upgrade `BarUpgradable` (a test contract) to a new implementation (code remains unchanged)
+- Transfer 1 TAIKO token from `BarUpgradable` to Daniel Wang
+- Transfer 0.001 ETH from `BarUpgradable` to Daniel Wang
+
+These actions aim to validate that the `DelegateController` can securely manage and transfer both Ether and tokens.
+
+## Action Verification
+
+### Action Generation
+
+All actions in this proposal are generated using the following script:
+
+[Proposal0002.s.sol](https://github.com/taikoxyz/taiko-mono/blob/main/packages/protocol/script/layer1/proposals/Proposal0002.s.sol)
+
+Please review the `proposalConfig()`, `buildL1Actions()`, and `buildL2Actions()` functions for logic verification.
+
+### `proposalConfig()`
+
+This function returns execution parameters for L2:
+
+- `l2ExecutionId`: Determines the execution order. Here, `l2ExecutionId = 1`, which enforces that the proposal must execute in sequence. Setting this value to `0` would skip ordering constraints, allowing execution at any time after approval.
+- `l2GasLimit`: Specifies the minimum gas required for executing L2 actions. This proposal sets the limit to `25,000,000`, allowing any L2 address to trigger execution.
+
+### `buildL1Actions()`
+
+This function generates all L1 transactions. The final action in the list sends a cross-chain message via the Taiko bridge, embedding the L2 action payload. This message targets the L2 `DelegateController` contract to execute the L2 actions sequentially.
+
+### `buildL2Actions()`
+
+This function defines the atomic set of L2 transactions. If any single action fails, the entire batch is reverted. Nevertheless, the proposal is still marked as executed from the DAO's perspective.
+
+L2 execution is permissionless and may be initiated by any address that supplies a gas limit of at least `25,000,000`.
+
+### Action Bytecode Verification
+
+To confirm the action data matches this proposal, clone the Taiko [monorepo](https://github.com/taikoxyz/taiko-mono), navigate to `packages/protocol`, run `pnpm install`, then:
+
+```bash
+P=0002 pnpm proposal
+```
+
+This will print the generated calldata and write it to `Proposal0002.action.md` for comparison. Byte-by-byte validation can then be performed.
+
+### Dryrun Execution
+
+To test the executability of the actions, use:
+
+```bash
+P=0002 pnpm proposal:dryrun:l1
+P=0002 pnpm proposal:dryrun:l2
path: packages/protocol/script/layer1/proposals/Proposal0002.md
user: login: davidtaikocha
body: On my laptop, for `P=0002 pnpm proposal:dryrun:l1`, it reverted, and for `P=0002 pnpm proposal:dryrun:l2`, it returned the same errors as Dani posted.
created_at: 2025-05-22T08:15:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,78 @@
+# Cross-Chain Proposal Test
+
+This proposal demonstrates the Taiko DAO's capability to execute multiple cross-chain actions involving both Layer 1 (L1) and Layer 2 (L2) operations.
+
+## L1 Actions
+
+The following L1 actions will be executed by the `TaikoDAOController`:
+
+- Transfer 0.001 ETH to Daniel Wang
+- Transfer 1 USDC to Daniel Wang
+
+These actions aim to validate that the `TaikoDAOController` can securely manage and transfer both Ether and ERC20 tokens.
+
+## L2 Actions
+
+The following L2 actions will be executed via the `DelegateController` contract:
+
+- Upgrade the `DelegateController` contract to a new implementation (code remains unchanged)
+- Transfer 1 TAIKO token to Daniel Wang
+- Upgrade `BarUpgradable` (a test contract) to a new implementation (code remains unchanged)
+- Transfer 1 TAIKO token from `BarUpgradable` to Daniel Wang
+- Transfer 0.001 ETH from `BarUpgradable` to Daniel Wang
+
+These actions aim to validate that the `DelegateController` can securely manage and transfer both Ether and tokens.
+
+## Action Verification
+
+### Action Generation
+
+All actions in this proposal are generated using the following script:
+
+[Proposal0002.s.sol](https://github.com/taikoxyz/taiko-mono/blob/main/packages/protocol/script/layer1/proposals/Proposal0002.s.sol)
+
+Please review the `proposalConfig()`, `buildL1Actions()`, and `buildL2Actions()` functions for logic verification.
+
+### `proposalConfig()`
+
+This function returns execution parameters for L2:
+
+- `l2ExecutionId`: Determines the execution order. Here, `l2ExecutionId = 1`, which enforces that the proposal must execute in sequence. Setting this value to `0` would skip ordering constraints, allowing execution at any time after approval.
+- `l2GasLimit`: Specifies the minimum gas required for executing L2 actions. This proposal sets the limit to `25,000,000`, allowing any L2 address to trigger execution.
+
+### `buildL1Actions()`
+
+This function generates all L1 transactions. The final action in the list sends a cross-chain message via the Taiko bridge, embedding the L2 action payload. This message targets the L2 `DelegateController` contract to execute the L2 actions sequentially.
+
+### `buildL2Actions()`
+
+This function defines the atomic set of L2 transactions. If any single action fails, the entire batch is reverted. Nevertheless, the proposal is still marked as executed from the DAO's perspective.
+
+L2 execution is permissionless and may be initiated by any address that supplies a gas limit of at least `25,000,000`.
+
+### Action Bytecode Verification
+
+To confirm the action data matches this proposal, clone the Taiko [monorepo](https://github.com/taikoxyz/taiko-mono), navigate to `packages/protocol`, run `pnpm install`, then:
+
+```bash
+P=0002 pnpm proposal
+```
+
+This will print the generated calldata and write it to `Proposal0002.action.md` for comparison. Byte-by-byte validation can then be performed.
+
+### Dryrun Execution
+
+To test the executability of the actions, use:
+
+```bash
+P=0002 pnpm proposal:dryrun:l1
+P=0002 pnpm proposal:dryrun:l2
path: packages/protocol/script/layer1/proposals/Proposal0002.md
user: login: dantaik
body: fixed
created_at: 2025-05-22T09:12:28Z
author_association: MEMBER

PR comments:
user: login: adaki2004
created_at: 2025-05-22T13:58:01Z
body: 1 issue remaining.
When I run it now (for L2) it works:
`P=0002 pnpm proposal:dryrun:l2`

When i run it for L1:
`P=0002 pnpm proposal:dryrun:l1`  -> It does not work and the reason is: we targetting `TaikoDAOController` (supplied in the contracts in `LibL1Addrs.sol` https://etherscan.io/address/0xd1934807041B168f383870A0d8F565aDe2DF9D7D#code ) but is not upgraded to the code which inherits from the new `Controller` contract, meaning it has no dryrun() so it obviously failed. So we need to upgrade the implementation of the proxy first.
author_association: CONTRIBUTOR
user: login: davidtaikocha
created_at: 2025-05-22T14:06:44Z
body: > 1 issue remaining. When I run it now (for L2) it works: `P=0002 pnpm proposal:dryrun:l2`
> 
> When i run it for L1: `P=0002 pnpm proposal:dryrun:l1` -> It does not work and the reason is: we targetting `TaikoDAOController` (supplied in the contracts in `LibL1Addrs.sol` https://etherscan.io/address/0xd1934807041B168f383870A0d8F565aDe2DF9D7D#code ) but is not upgraded to the code which inherits from the new `Controller` contract, meaning it has no dryrun() so it obviously failed. So we need to upgrade the implementation of the proxy first.

Me too, `P=0002 pnpm proposal:dryrun:l1` still reverts on my laptop.
author_association: COLLABORATOR
user: login: dantaik
created_at: 2025-05-22T14:07:33Z
body: > 1 issue remaining.
> 
> When I run it now (for L2) it works:
> 
> `P=0002 pnpm proposal:dryrun:l2`
> 
> 
> 
> When i run it for L1:
> 
> `P=0002 pnpm proposal:dryrun:l1`  -> It does not work and the reason is: we targetting `TaikoDAOController` (supplied in the contracts in `LibL1Addrs.sol` https://etherscan.io/address/0xd1934807041B168f383870A0d8F565aDe2DF9D7D#code ) but is not upgraded to the code which inherits from the new `Controller` contract, meaning it has no dryrun() so it obviously failed. So we need to upgrade the implementation of the proxy first.

Correct. The upgrade to the DAO controller is not done as there is a bug in the DAO config. We are working on it. 
author_association: MEMBER