diff --git a/l1-contracts/src/core/FeeJuicePortal.sol b/l1-contracts/src/core/FeeJuicePortal.sol index dee322c6a814..e84c31ec0824 100644 --- a/l1-contracts/src/core/FeeJuicePortal.sol +++ b/l1-contracts/src/core/FeeJuicePortal.sol @@ -14,26 +14,24 @@ import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; -import {Ownable} from "@oz/access/Ownable.sol"; - -contract FeeJuicePortal is IFeeJuicePortal, Ownable { +contract FeeJuicePortal is IFeeJuicePortal { using SafeERC20 for IERC20; - IRegistry public registry; - IERC20 public underlying; - bytes32 public l2TokenAddress; + IRegistry public immutable REGISTRY; + IERC20 public immutable UNDERLYING; + bytes32 public immutable L2_TOKEN_ADDRESS; - constructor(address _owner, address _registry, address _underlying, bytes32 _l2TokenAddress) - Ownable(_owner) - { + bool public initialized; + + constructor(address _registry, address _underlying, bytes32 _l2TokenAddress) { require( _registry != address(0) && _underlying != address(0) && _l2TokenAddress != 0, Errors.FeeJuicePortal__InvalidInitialization() ); - registry = IRegistry(_registry); - underlying = IERC20(_underlying); - l2TokenAddress = _l2TokenAddress; + REGISTRY = IRegistry(_registry); + UNDERLYING = IERC20(_underlying); + L2_TOKEN_ADDRESS = _l2TokenAddress; } /** @@ -44,16 +42,16 @@ contract FeeJuicePortal is IFeeJuicePortal, Ownable { * @dev Must be funded with FEE_JUICE_INITIAL_MINT tokens before initialization to * ensure that the L2 contract is funded and able to pay for its deployment. */ - function initialize() external override(IFeeJuicePortal) onlyOwner { - require(owner() != address(0), Errors.FeeJuicePortal__AlreadyInitialized()); + function initialize() external override(IFeeJuicePortal) { + require(!initialized, Errors.FeeJuicePortal__AlreadyInitialized()); - uint256 balance = underlying.balanceOf(address(this)); + uint256 balance = UNDERLYING.balanceOf(address(this)); if (balance < Constants.FEE_JUICE_INITIAL_MINT) { - underlying.safeTransferFrom( + UNDERLYING.safeTransferFrom( msg.sender, address(this), Constants.FEE_JUICE_INITIAL_MINT - balance ); } - _transferOwnership(address(0)); + initialized = true; } /** @@ -69,18 +67,24 @@ contract FeeJuicePortal is IFeeJuicePortal, Ownable { returns (bytes32) { // Preamble - IInbox inbox = IRollup(registry.getRollup()).INBOX(); - DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2TokenAddress, 1); + address rollup = canonicalRollup(); + uint256 version = REGISTRY.getVersionFor(rollup); + IInbox inbox = IRollup(rollup).INBOX(); + DataStructures.L2Actor memory actor = DataStructures.L2Actor(L2_TOKEN_ADDRESS, version); // Hash the message content to be reconstructed in the receiving contract bytes32 contentHash = Hash.sha256ToField(abi.encodeWithSignature("claim(bytes32,uint256)", _to, _amount)); // Hold the tokens in the portal - underlying.safeTransferFrom(msg.sender, address(this), _amount); + UNDERLYING.safeTransferFrom(msg.sender, address(this), _amount); // Send message to rollup - return inbox.sendL2Message(actor, contentHash, _secretHash); + bytes32 key = inbox.sendL2Message(actor, contentHash, _secretHash); + + emit DepositToAztecPublic(_to, _amount, _secretHash, key); + + return key; } /** @@ -94,7 +98,13 @@ contract FeeJuicePortal is IFeeJuicePortal, Ownable { * @param _amount - The amount to pay them */ function distributeFees(address _to, uint256 _amount) external override(IFeeJuicePortal) { - require(msg.sender == registry.getRollup(), Errors.FeeJuicePortal__Unauthorized()); - underlying.safeTransfer(_to, _amount); + require(msg.sender == canonicalRollup(), Errors.FeeJuicePortal__Unauthorized()); + UNDERLYING.safeTransfer(_to, _amount); + + emit FeesDistributed(_to, _amount); + } + + function canonicalRollup() public view returns (address) { + return REGISTRY.getRollup(); } } diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 01c1c6efff39..f79bdfdd2cb3 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -92,7 +92,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { ) Leonidas(_ares) { epochProofVerifier = new MockVerifier(); FEE_JUICE_PORTAL = _fpcJuicePortal; - PROOF_COMMITMENT_ESCROW = new ProofCommitmentEscrow(_fpcJuicePortal.underlying(), address(this)); + PROOF_COMMITMENT_ESCROW = new ProofCommitmentEscrow(_fpcJuicePortal.UNDERLYING(), address(this)); INBOX = IInbox(address(new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT))); OUTBOX = IOutbox(address(new Outbox(address(this)))); vkTreeRoot = _vkTreeRoot; @@ -260,15 +260,15 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { tips.provenBlockNumber = endBlockNumber; - for (uint256 i = 0; i < Constants.AZTEC_EPOCH_DURATION; i++) { - address coinbase = address(uint160(uint256(publicInputs[9 + i * 2]))); - uint256 fees = uint256(publicInputs[10 + i * 2]); - - if (coinbase != address(0) && fees > 0) { - // @note This will currently fail if there are insufficient funds in the bridge - // which WILL happen for the old version after an upgrade where the bridge follow. - // Consider allowing a failure. See #7938. - FEE_JUICE_PORTAL.distributeFees(coinbase, fees); + // @note Only if the rollup is the canonical will it be able to meaningfully claim fees + // Otherwise, the fees are unbacked #7938. + if (address(this) == FEE_JUICE_PORTAL.canonicalRollup()) { + for (uint256 i = 0; i < Constants.AZTEC_EPOCH_DURATION; i++) { + address coinbase = address(uint160(uint256(publicInputs[9 + i * 2]))); + uint256 fees = uint256(publicInputs[10 + i * 2]); + if (coinbase != address(0) && fees > 0) { + FEE_JUICE_PORTAL.distributeFees(coinbase, fees); + } } } @@ -906,5 +906,11 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { _flags.ignoreDA || _header.contentCommitment.txsEffectsHash == _txsEffectsHash, Errors.Rollup__UnavailableTxs(_header.contentCommitment.txsEffectsHash) ); + + // If not canonical rollup, require that the fees are zero + if (address(this) != FEE_JUICE_PORTAL.canonicalRollup()) { + require(_header.globalVariables.gasFees.feePerDaGas == 0, Errors.Rollup__NonZeroDaFee()); + require(_header.globalVariables.gasFees.feePerL2Gas == 0, Errors.Rollup__NonZeroL2Fee()); + } } } diff --git a/l1-contracts/src/core/interfaces/IFeeJuicePortal.sol b/l1-contracts/src/core/interfaces/IFeeJuicePortal.sol index 5f5a7c0d440d..5537127f0957 100644 --- a/l1-contracts/src/core/interfaces/IFeeJuicePortal.sol +++ b/l1-contracts/src/core/interfaces/IFeeJuicePortal.sol @@ -3,12 +3,20 @@ pragma solidity >=0.8.27; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; +import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol"; interface IFeeJuicePortal { + event DepositToAztecPublic(bytes32 indexed to, uint256 amount, bytes32 secretHash, bytes32 key); + event FeesDistributed(address indexed to, uint256 amount); + function initialize() external; function distributeFees(address _to, uint256 _amount) external; function depositToAztecPublic(bytes32 _to, uint256 _amount, bytes32 _secretHash) external returns (bytes32); - function underlying() external view returns (IERC20); + function canonicalRollup() external view returns (address); + + function UNDERLYING() external view returns (IERC20); + function L2_TOKEN_ADDRESS() external view returns (bytes32); + function REGISTRY() external view returns (IRegistry); } diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index cdc8e113db67..5414773813b4 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -72,6 +72,8 @@ library Errors { error Rollup__TimestampTooOld(); // 0x72ed9c81 error Rollup__TryingToProveNonExistingBlock(); // 0x34ef4954 error Rollup__UnavailableTxs(bytes32 txsHash); // 0x414906c3 + error Rollup__NonZeroDaFee(); + error Rollup__NonZeroL2Fee(); //TxsDecoder error TxsDecoder__InvalidLogsLength(uint256 expected, uint256 actual); // 0x829ca981 diff --git a/l1-contracts/src/mock/MockFeeJuicePortal.sol b/l1-contracts/src/mock/MockFeeJuicePortal.sol index 75580af598e2..5227f60717d1 100644 --- a/l1-contracts/src/mock/MockFeeJuicePortal.sol +++ b/l1-contracts/src/mock/MockFeeJuicePortal.sol @@ -5,12 +5,15 @@ pragma solidity >=0.8.27; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; import {TestERC20} from "@aztec/mock/TestERC20.sol"; +import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol"; contract MockFeeJuicePortal is IFeeJuicePortal { - IERC20 public underlying; + IERC20 public immutable UNDERLYING; + bytes32 public constant L2_TOKEN_ADDRESS = bytes32(0); + IRegistry public constant REGISTRY = IRegistry(address(0)); constructor() { - underlying = new TestERC20(); + UNDERLYING = new TestERC20(); } function initialize() external override {} @@ -20,4 +23,8 @@ contract MockFeeJuicePortal is IFeeJuicePortal { function depositToAztecPublic(bytes32, uint256, bytes32) external pure override returns (bytes32) { return bytes32(0); } + + function canonicalRollup() external pure override returns (address) { + return address(0); + } } diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 3450f882baa0..101563c4a19e 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -68,9 +68,10 @@ contract RollupTest is DecoderBase { registry = new Registry(address(this)); testERC20 = new TestERC20(); feeJuicePortal = new FeeJuicePortal( - address(this), address(registry), address(testERC20), bytes32(Constants.FEE_JUICE_ADDRESS) + address(registry), address(testERC20), bytes32(Constants.FEE_JUICE_ADDRESS) ); testERC20.mint(address(feeJuicePortal), Constants.FEE_JUICE_INITIAL_MINT); + feeJuicePortal.initialize(); rollup = new Rollup(feeJuicePortal, bytes32(0), bytes32(0), address(this), new address[](0)); inbox = Inbox(address(rollup.INBOX())); outbox = Outbox(address(rollup.OUTBOX())); @@ -435,6 +436,42 @@ contract RollupTest is DecoderBase { assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); } + function testNonZeroDaFee() public setUpFor("mixed_block_1") { + registry.upgrade(address(0xbeef)); + + DecoderBase.Full memory full = load("mixed_block_1"); + DecoderBase.Data memory data = full.block; + bytes memory header = data.header; + assembly { + mstore(add(header, add(0x20, 0x0208)), 1) + } + bytes32[] memory txHashes = new bytes32[](0); + + // We jump to the time of the block. (unless it is in the past) + vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp)); + + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NonZeroDaFee.selector)); + rollup.propose(header, data.archive, data.blockHash, txHashes, signatures, data.body); + } + + function testNonZeroL2Fee() public setUpFor("mixed_block_1") { + registry.upgrade(address(0xbeef)); + + DecoderBase.Full memory full = load("mixed_block_1"); + DecoderBase.Data memory data = full.block; + bytes memory header = data.header; + assembly { + mstore(add(header, add(0x20, 0x0228)), 1) + } + bytes32[] memory txHashes = new bytes32[](0); + + // We jump to the time of the block. (unless it is in the past) + vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp)); + + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NonZeroL2Fee.selector)); + rollup.propose(header, data.archive, data.blockHash, txHashes, signatures, data.body); + } + function testBlockFee() public setUpFor("mixed_block_1") { uint256 feeAmount = 2e18; diff --git a/l1-contracts/test/fee_portal/depositToAztecPublic.t.sol b/l1-contracts/test/fee_portal/depositToAztecPublic.t.sol new file mode 100644 index 000000000000..e489db070e5a --- /dev/null +++ b/l1-contracts/test/fee_portal/depositToAztecPublic.t.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.27; + +import {Test} from "forge-std/Test.sol"; +import {Registry} from "@aztec/governance/Registry.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; +import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol"; +import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; +import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; +import {IERC20Errors} from "@oz/interfaces/draft-IERC6093.sol"; +import {Rollup} from "@aztec/core/Rollup.sol"; +import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; +import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; +import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; +import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; + +contract DepositToAztecPublic is Test { + using Hash for DataStructures.L1ToL2Msg; + + address internal constant OWNER = address(0x1); + Registry internal registry; + TestERC20 internal token; + FeeJuicePortal internal feeJuicePortal; + Rollup internal rollup; + + function setUp() public { + registry = new Registry(OWNER); + token = new TestERC20(); + feeJuicePortal = + new FeeJuicePortal(address(registry), address(token), bytes32(Constants.FEE_JUICE_ADDRESS)); + + token.mint(address(feeJuicePortal), Constants.FEE_JUICE_INITIAL_MINT); + feeJuicePortal.initialize(); + + rollup = new Rollup(feeJuicePortal, bytes32(0), bytes32(0), address(this), new address[](0)); + + vm.prank(OWNER); + registry.upgrade(address(rollup)); + } + + function test_RevertGiven_InsufficientBalance() external { + // it should revert + vm.expectRevert( + abi.encodeWithSelector( + IERC20Errors.ERC20InsufficientAllowance.selector, address(feeJuicePortal), 0, 1 + ) + ); + feeJuicePortal.depositToAztecPublic(bytes32(0x0), 1, bytes32(0x0)); + + token.approve(address(feeJuicePortal), 1); + vm.expectRevert( + abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, address(this), 0, 1) + ); + feeJuicePortal.depositToAztecPublic(bytes32(0x0), 1, bytes32(0x0)); + } + + function test_GivenSufficientBalance(uint256 _numberOfRollups) external { + // it should create a message for the newest version + // it should transfer the tokens to the portal + // it should insert the message into the newest inbox + // it should emit a {DepositToAztecPublic} event + // it should return the key + + uint256 numberOfRollups = bound(_numberOfRollups, 1, 5); + for (uint256 i = 0; i < numberOfRollups; i++) { + Rollup freshRollup = + new Rollup(feeJuicePortal, bytes32(0), bytes32(0), address(this), new address[](0)); + vm.prank(OWNER); + registry.upgrade(address(freshRollup)); + } + + assertNotEq(registry.getRollup(), address(rollup)); + + bytes32 to = bytes32(0x0); + bytes32 secretHash = bytes32(uint256(0x01)); + uint256 amount = 100 ether; + + DataStructures.L1ToL2Msg memory message = DataStructures.L1ToL2Msg({ + sender: DataStructures.L1Actor(address(feeJuicePortal), block.chainid), + recipient: DataStructures.L2Actor(feeJuicePortal.L2_TOKEN_ADDRESS(), 1 + numberOfRollups), + content: Hash.sha256ToField(abi.encodeWithSignature("claim(bytes32,uint256)", to, amount)), + secretHash: secretHash + }); + + bytes32 expectedKey = message.sha256ToField(); + + token.mint(address(this), amount); + token.approve(address(feeJuicePortal), amount); + + Inbox inbox = Inbox(address(Rollup(address(registry.getRollup())).INBOX())); + assertEq(inbox.totalMessagesInserted(), 0); + uint256 index = 2 ** Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT; + + vm.expectEmit(true, true, true, true, address(inbox)); + emit IInbox.MessageSent(2, index, expectedKey); + vm.expectEmit(true, true, true, true, address(feeJuicePortal)); + emit IFeeJuicePortal.DepositToAztecPublic(to, amount, secretHash, expectedKey); + + bytes32 key = feeJuicePortal.depositToAztecPublic(to, amount, secretHash); + + assertEq(inbox.totalMessagesInserted(), 1); + assertEq(key, expectedKey); + } +} diff --git a/l1-contracts/test/fee_portal/depositToAztecPublic.tree b/l1-contracts/test/fee_portal/depositToAztecPublic.tree new file mode 100644 index 000000000000..480ea7bbb53d --- /dev/null +++ b/l1-contracts/test/fee_portal/depositToAztecPublic.tree @@ -0,0 +1,9 @@ +DepositToAztecPublic +├── given insufficient balance +│ └── it should revert +└── given sufficient balance + ├── it should create a message for the newest version + ├── it should transfer the tokens to the portal + ├── it should insert the message into the newest inbox + ├── it should emit a {DepositToAztecPublic} event + └── it should return the key \ No newline at end of file diff --git a/l1-contracts/test/fee_portal/distributeFees.t.sol b/l1-contracts/test/fee_portal/distributeFees.t.sol new file mode 100644 index 000000000000..ed95f8996946 --- /dev/null +++ b/l1-contracts/test/fee_portal/distributeFees.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.27; + +import {Test} from "forge-std/Test.sol"; +import {Registry} from "@aztec/governance/Registry.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; +import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol"; +import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; +import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; +import {IERC20Errors} from "@oz/interfaces/draft-IERC6093.sol"; +import {Rollup} from "@aztec/core/Rollup.sol"; +import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; +import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; +import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; +import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; + +contract DistributeFees is Test { + using Hash for DataStructures.L1ToL2Msg; + + address internal constant OWNER = address(0x1); + Registry internal registry; + TestERC20 internal token; + FeeJuicePortal internal feeJuicePortal; + Rollup internal rollup; + + function setUp() public { + registry = new Registry(OWNER); + token = new TestERC20(); + feeJuicePortal = + new FeeJuicePortal(address(registry), address(token), bytes32(Constants.FEE_JUICE_ADDRESS)); + token.mint(address(feeJuicePortal), Constants.FEE_JUICE_INITIAL_MINT); + feeJuicePortal.initialize(); + + rollup = new Rollup(feeJuicePortal, bytes32(0), bytes32(0), address(this), new address[](0)); + + vm.prank(OWNER); + registry.upgrade(address(rollup)); + } + + function test_RevertGiven_TheCallerIsNotTheCanonicalRollup() external { + // it should revert + vm.expectRevert(abi.encodeWithSelector(Errors.FeeJuicePortal__Unauthorized.selector)); + feeJuicePortal.distributeFees(address(this), 1); + } + + modifier givenTheCallerIsTheCanonicalRollup() { + _; + } + + function test_RevertGiven_InsufficientBalance() external givenTheCallerIsTheCanonicalRollup { + // it should revert + vm.prank(address(rollup)); + vm.expectRevert( + abi.encodeWithSelector( + IERC20Errors.ERC20InsufficientBalance.selector, + address(feeJuicePortal), + Constants.FEE_JUICE_INITIAL_MINT, + Constants.FEE_JUICE_INITIAL_MINT + 1 + ) + ); + feeJuicePortal.distributeFees(address(this), Constants.FEE_JUICE_INITIAL_MINT + 1); + } + + function test_GivenSufficientBalance(uint256 _numberOfRollups) + external + givenTheCallerIsTheCanonicalRollup + { + // it should transfer the tokens to the recipient + // it should emit a {FeesDistributed} event + + uint256 numberOfRollups = bound(_numberOfRollups, 1, 5); + for (uint256 i = 0; i < numberOfRollups; i++) { + Rollup freshRollup = + new Rollup(feeJuicePortal, bytes32(0), bytes32(0), address(this), new address[](0)); + vm.prank(OWNER); + registry.upgrade(address(freshRollup)); + } + + assertEq(token.balanceOf(address(this)), 0); + + assertNotEq(registry.getRollup(), address(rollup)); + + vm.prank(registry.getRollup()); + vm.expectEmit(true, true, true, true, address(feeJuicePortal)); + emit IFeeJuicePortal.FeesDistributed(address(this), Constants.FEE_JUICE_INITIAL_MINT); + feeJuicePortal.distributeFees(address(this), Constants.FEE_JUICE_INITIAL_MINT); + + assertEq(token.balanceOf(address(this)), Constants.FEE_JUICE_INITIAL_MINT); + } +} diff --git a/l1-contracts/test/fee_portal/distributeFees.tree b/l1-contracts/test/fee_portal/distributeFees.tree new file mode 100644 index 000000000000..68e35f0ce16a --- /dev/null +++ b/l1-contracts/test/fee_portal/distributeFees.tree @@ -0,0 +1,9 @@ +DistributeFees +├── given the caller is not the canonical rollup +│ └── it should revert +└── given the caller is the canonical rollup + ├── given insufficient balance + │ └── it should revert + └── given sufficient balance + ├── it should transfer the tokens to the recipient + └── it should emit a {FeesDistributed} event \ No newline at end of file diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 974767fc066d..56711e90f001 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -322,7 +322,6 @@ export const deployL1Contracts = async ( const deployer = new L1Deployer(walletClient, publicClient, args.salt, logger); const feeJuicePortalAddress = await deployer.deploy(l1Artifacts.feeJuicePortal, [ - account.address.toString(), registryAddress.toString(), feeJuiceAddress.toString(), args.l2FeeJuiceAddress.toString(), @@ -374,7 +373,7 @@ export const deployL1Contracts = async ( await publicClient.waitForTransactionReceipt({ hash: mintTxHash }); logger.info(`Funding fee juice portal contract with fee juice in ${mintTxHash}`); - if ((await feeJuicePortal.read.owner([])) !== zeroAddress) { + if (!(await feeJuicePortal.read.initialized([]))) { const initPortalTxHash = await feeJuicePortal.write.initialize([]); txHashes.push(initPortalTxHash); logger.verbose(`Fee juice portal initializing in tx ${initPortalTxHash}`);