From 2f1d19ac3baa35800ac941f0941461addad7ab66 Mon Sep 17 00:00:00 2001 From: just-mitch <68168980+just-mitch@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:32:49 -0400 Subject: [PATCH] feat: prover escrow and 712-signed quotes (#8877) Fix https://github.com/AztecProtocol/aztec-packages/issues/8573 Introduces an escrow contract that is able to store test tokens from provers. The implementation allows the rollup contract to stake deposited bonds, and unstake them. Provers may initiate withdraws which are not executable until 3 epochs after their initiation. Proposers will use the `minBalanceAtTime` function to ensure that a prover has sufficient funds at the slot they would "cash in" the quote. Additionally, the signatures used on the epoch proof quotes have been updated to use EIP 712 style signing and verification as part of the proof claim process. Other cleanup includes: - Consolidating the erc20 token used in tests to TestERC20 - Fixing a bug in the publisher - Enabling the e2e prover coordination test in ci Future work includes: 1. Deploying the escrow along with the rollup. 2. Configuring provers to deposit funds. 3. Having the rollup actually use the escrow. --- .vscode/settings.json | 4 +- .../token_bridge/1_depositing_to_aztec.md | 4 +- l1-contracts/.solhint.json | 47 +-- .../src/core/ProofCommitmentEscrow.sol | 146 ++++++++ l1-contracts/src/core/Rollup.sol | 28 +- .../interfaces/IProofCommitmentEscrow.sol | 16 +- l1-contracts/src/core/interfaces/IRollup.sol | 12 +- .../src/core/libraries/DataStructures.sol | 32 +- .../src/core/libraries/EpochProofQuoteLib.sol | 51 +++ l1-contracts/src/core/libraries/Errors.sol | 5 + .../src/core/libraries/SignatureLib.sol | 27 -- .../core/libraries/crypto/SignatureLib.sol | 4 + .../src/mock/MockProofCommitmentEscrow.sol | 15 +- l1-contracts/test/Rollup.t.sol | 144 ++++---- .../PortalERC20.sol => TestERC20.sol} | 2 +- l1-contracts/test/TestERC20.t.sol | 17 + l1-contracts/test/portals/PortalERC20.t.sol | 17 - l1-contracts/test/portals/TokenPortal.t.sol | 26 +- l1-contracts/test/portals/UniswapPortal.t.sol | 2 +- .../ProofCommitmentEscrow.t.sol | 313 ++++++++++++++++++ l1-contracts/test/sparta/Sparta.t.sol | 6 +- yarn-project/aztec-faucet/src/bin/index.ts | 4 +- yarn-project/aztec.js/src/index.ts | 1 + yarn-project/aztec/src/sandbox.ts | 8 +- .../epoch_proof_quote.test.ts | 9 +- .../prover_coordination/epoch_proof_quote.ts | 44 +-- .../epoch_proof_quote_payload.ts | 34 +- .../cli/src/cmds/devnet/bootstrap_network.ts | 8 +- .../cli/src/cmds/l1/get_l1_balance.ts | 4 +- yarn-project/cli/src/utils/aztec.ts | 8 +- yarn-project/cli/src/utils/portal_manager.ts | 6 +- yarn-project/end-to-end/Earthfile | 4 + .../cross_chain_messaging_test.ts | 4 +- .../end-to-end/src/e2e_fees/fees_test.ts | 4 +- .../src/fixtures/setup_l1_contracts.ts | 8 +- yarn-project/end-to-end/src/fixtures/utils.ts | 8 +- .../e2e_json_coordination.test.ts | 155 ++++++--- .../src/shared/cross_chain_test_harness.ts | 15 +- .../src/shared/gas_portal_test_harness.ts | 6 +- .../foundation/src/crypto/keccak/index.ts | 7 +- .../scripts/generate-artifacts.sh | 2 +- .../src/epoch_proof_quote_pool/test_utils.ts | 3 +- .../src/publisher/l1-publisher.ts | 51 +-- .../src/sequencer/sequencer.ts | 7 + 44 files changed, 923 insertions(+), 395 deletions(-) create mode 100644 l1-contracts/src/core/ProofCommitmentEscrow.sol create mode 100644 l1-contracts/src/core/libraries/EpochProofQuoteLib.sol delete mode 100644 l1-contracts/src/core/libraries/SignatureLib.sol rename l1-contracts/test/{portals/PortalERC20.sol => TestERC20.sol} (89%) create mode 100644 l1-contracts/test/TestERC20.t.sol delete mode 100644 l1-contracts/test/portals/PortalERC20.t.sol create mode 100644 l1-contracts/test/prover-coordination/ProofCommitmentEscrow.t.sol diff --git a/.vscode/settings.json b/.vscode/settings.json index 3790d618e42..2e0e5227291 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -107,7 +107,7 @@ "*.macros": "cpp", "*.tpp": "cpp" }, - "solidity.compileUsingRemoteVersion": "v0.8.18", + "solidity.compileUsingRemoteVersion": "v0.8.27", "solidity.formatter": "forge", "search.exclude": { "**/.yarn": true, @@ -171,5 +171,5 @@ }, "files.trimTrailingWhitespace": true, "cmake.sourceDirectory": "${workspaceFolder}/barretenberg/cpp", - "typescript.tsserver.maxTsServerMemory": 4096, + "typescript.tsserver.maxTsServerMemory": 4096 } diff --git a/docs/docs/tutorials/codealong/contract_tutorials/advanced/token_bridge/1_depositing_to_aztec.md b/docs/docs/tutorials/codealong/contract_tutorials/advanced/token_bridge/1_depositing_to_aztec.md index 0b05d3e92a6..ba35449ba13 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/advanced/token_bridge/1_depositing_to_aztec.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/advanced/token_bridge/1_depositing_to_aztec.md @@ -32,9 +32,9 @@ This imports relevant files including the interfaces used by the Aztec rollup. A Create a basic ERC20 contract that can mint tokens to anyone. We will use this to test. -Create a file `PortalERC20.sol` in the same folder and add: +Create a file `TestERC20.sol` in the same folder and add: -#include_code contract /l1-contracts/test/portals/PortalERC20.sol solidity +#include_code contract /l1-contracts/test/TestERC20.sol solidity Replace the openzeppelin import with this: diff --git a/l1-contracts/.solhint.json b/l1-contracts/.solhint.json index d4a30c78520..2acdeeb2a60 100644 --- a/l1-contracts/.solhint.json +++ b/l1-contracts/.solhint.json @@ -1,10 +1,7 @@ { "extends": "solhint:recommended", "rules": { - "compiler-version": [ - "error", - ">=0.8.27" - ], + "compiler-version": ["error", ">=0.8.27"], "no-inline-assembly": "off", "gas-custom-errors": "off", "func-visibility": [ @@ -14,12 +11,8 @@ } ], "no-empty-blocks": "off", - "no-unused-vars": [ - "error" - ], - "state-visibility": [ - "error" - ], + "no-unused-vars": ["error"], + "state-visibility": ["error"], "not-rely-on-time": "off", "const-name-snakecase": [ "error", @@ -39,29 +32,13 @@ "allowPrefix": true } ], - "private-func-leading-underscore": [ - "error" - ], - "private-vars-no-leading-underscore": [ - "error" - ], - "func-param-name-leading-underscore": [ - "error" - ], - "func-param-name-mixedcase": [ - "error" - ], - "strict-override": [ - "error" - ], - "strict-import": [ - "error" - ], - "ordering": [ - "error" - ], - "comprehensive-interface": [ - "error" - ] + "private-func-leading-underscore": ["error"], + "private-vars-no-leading-underscore": ["error"], + "func-param-name-leading-underscore": ["error"], + "func-param-name-mixedcase": ["error"], + "strict-override": ["error"], + "strict-import": ["error"], + "ordering": ["error"], + "comprehensive-interface": ["error"] } -} \ No newline at end of file +} diff --git a/l1-contracts/src/core/ProofCommitmentEscrow.sol b/l1-contracts/src/core/ProofCommitmentEscrow.sol new file mode 100644 index 00000000000..a29f92ac0c4 --- /dev/null +++ b/l1-contracts/src/core/ProofCommitmentEscrow.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; + +import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; +import {Timestamp} from "@aztec/core/libraries/TimeMath.sol"; + +contract ProofCommitmentEscrow is IProofCommitmentEscrow { + using SafeERC20 for IERC20; + + struct WithdrawRequest { + uint256 amount; + Timestamp executableAt; + } + + address public immutable ROLLUP; + uint256 public constant WITHDRAW_DELAY = + Constants.ETHEREUM_SLOT_DURATION * Constants.AZTEC_EPOCH_DURATION * 3; + mapping(address => uint256) public deposits; + mapping(address => WithdrawRequest) public withdrawRequests; + IERC20 public immutable token; + + modifier onlyRollup() { + require(msg.sender == ROLLUP, Errors.ProofCommitmentEscrow__NotOwner(msg.sender)); + _; + } + + constructor(IERC20 _token, address _owner) { + token = _token; + ROLLUP = _owner; + } + + /** + * @notice Deposit tokens into the escrow + * + * @dev The caller must have approved the token transfer + * + * @param _amount The amount of tokens to deposit + */ + function deposit(uint256 _amount) external override { + token.safeTransferFrom(msg.sender, address(this), _amount); + + deposits[msg.sender] += _amount; + + emit Deposit(msg.sender, _amount); + } + + /** + * @notice Start a withdrawal request + * + * @dev The caller must have sufficient balance + * The withdrawal request will be executable after a delay + * Subsequent calls to this function will overwrite the previous request + * + * @param _amount - The amount of tokens to withdraw + */ + function startWithdraw(uint256 _amount) external override { + require( + deposits[msg.sender] >= _amount, + Errors.ProofCommitmentEscrow__InsufficientBalance(deposits[msg.sender], _amount) + ); + + withdrawRequests[msg.sender] = WithdrawRequest({ + amount: _amount, + executableAt: Timestamp.wrap(block.timestamp + WITHDRAW_DELAY) + }); + + emit StartWithdraw(msg.sender, _amount, withdrawRequests[msg.sender].executableAt); + } + + /** + * @notice Execute a mature withdrawal request + */ + function executeWithdraw() external override { + WithdrawRequest memory request = withdrawRequests[msg.sender]; + require( + request.executableAt <= Timestamp.wrap(block.timestamp), + Errors.ProofCommitmentEscrow__WithdrawRequestNotReady(block.timestamp, request.executableAt) + ); + + delete withdrawRequests[msg.sender]; + deposits[msg.sender] -= request.amount; + token.safeTransfer(msg.sender, request.amount); + + emit ExecuteWithdraw(msg.sender, request.amount); + } + + /** + * @notice Stake an amount of previously deposited tokens + * + * @dev Only callable by the owner + * The prover must have sufficient balance + * The prover's balance will be reduced by the bond amount + */ + function stakeBond(address _prover, uint256 _amount) external override onlyRollup { + deposits[_prover] -= _amount; + + emit StakeBond(_prover, _amount); + } + + /** + * @notice Unstake the bonded tokens, returning them to the prover + * + * @dev Only callable by the owner + */ + function unstakeBond(address _prover, uint256 _amount) external override onlyRollup { + deposits[_prover] += _amount; + + emit UnstakeBond(_prover, _amount); + } + + /** + * @notice Get the minimum balance of a prover at a given timestamp. + * + * @dev Returns 0 if the timestamp is beyond the WITHDRAW_DELAY from the current block timestamp + * + * @param _timestamp The timestamp at which to check the balance + * @param _prover The address of the prover + * + * @return The balance of the prover at the given timestamp, compensating for withdrawal requests that have matured by that time + */ + function minBalanceAtTime(Timestamp _timestamp, address _prover) + external + view + override + returns (uint256) + { + // If the timestamp is beyond the WITHDRAW_DELAY, the minimum possible balance is 0; + // the prover could issue a withdraw request in this block for the full amount, + // and execute it exactly WITHDRAW_DELAY later. + if (_timestamp >= Timestamp.wrap(block.timestamp + WITHDRAW_DELAY)) { + return 0; + } + + uint256 balance = deposits[_prover]; + if (withdrawRequests[_prover].executableAt <= _timestamp) { + balance -= withdrawRequests[_prover].amount; + } + return balance; + } +} diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 497e8f7fcc1..de9e5c88ea7 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -1,7 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 -// Copyright 2023 Aztec Labs. +// Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; +import {EIP712} from "@oz/utils/cryptography/EIP712.sol"; +import {ECDSA} from "@oz/utils/cryptography/ECDSA.sol"; + import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; @@ -11,6 +14,7 @@ import {IVerifier} from "@aztec/core/interfaces/IVerifier.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; +import {EpochProofQuoteLib} from "@aztec/core/libraries/EpochProofQuoteLib.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {HeaderLib} from "@aztec/core/libraries/HeaderLib.sol"; import {TxsDecoder} from "@aztec/core/libraries/TxsDecoder.sol"; @@ -32,9 +36,8 @@ import {Timestamp, Slot, Epoch, SlotLib, EpochLib} from "@aztec/core/libraries/T * @notice Rollup contract that is concerned about readability and velocity of development * not giving a damn about gas costs. */ -contract Rollup is Leonidas, IRollup, ITestRollup { +contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { using SafeCast for uint256; - using SlotLib for Slot; using EpochLib for Epoch; @@ -105,6 +108,15 @@ contract Rollup is Leonidas, IRollup, ITestRollup { setupEpoch(); } + function quoteToDigest(EpochProofQuoteLib.EpochProofQuote memory quote) + public + view + override(IRollup) + returns (bytes32) + { + return _hashTypedDataV4(EpochProofQuoteLib.hash(quote)); + } + /** * @notice Prune the pending chain up to the last proven block * @@ -169,7 +181,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { bytes32[] memory _txHashes, SignatureLib.Signature[] memory _signatures, bytes calldata _body, - DataStructures.SignedEpochProofQuote calldata _quote + EpochProofQuoteLib.SignedEpochProofQuote calldata _quote ) external override(IRollup) { propose(_header, _archive, _blockHash, _txHashes, _signatures, _body); claimEpochProofRight(_quote); @@ -324,7 +336,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return TxsDecoder.decode(_body); } - function claimEpochProofRight(DataStructures.SignedEpochProofQuote calldata _quote) + function claimEpochProofRight(EpochProofQuoteLib.SignedEpochProofQuote calldata _quote) public override(IRollup) { @@ -336,7 +348,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { // We don't currently unstake, // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. // Blocked on submitting epoch proofs to this contract. - PROOF_COMMITMENT_ESCROW.stakeBond(_quote.quote.bondAmount, _quote.quote.prover); + PROOF_COMMITMENT_ESCROW.stakeBond(_quote.quote.prover, _quote.quote.bondAmount); proofClaim = DataStructures.EpochProofClaim({ epochToProve: epochToProve, @@ -559,11 +571,13 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return publicInputs; } - function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) + function validateEpochProofRightClaim(EpochProofQuoteLib.SignedEpochProofQuote calldata _quote) public view override(IRollup) { + SignatureLib.verify(_quote.signature, _quote.quote.prover, quoteToDigest(_quote.quote)); + Slot currentSlot = getCurrentSlot(); address currentProposer = getCurrentProposer(); Epoch epochToProve = getEpochToProve(); diff --git a/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol b/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol index f6c0d55f7f8..e844400fba5 100644 --- a/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol +++ b/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol @@ -2,9 +2,19 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; +import {Timestamp} from "@aztec/core/libraries/TimeMath.sol"; + interface IProofCommitmentEscrow { + event Deposit(address indexed depositor, uint256 amount); + event StartWithdraw(address indexed withdrawer, uint256 amount, Timestamp executableAt); + event ExecuteWithdraw(address indexed withdrawer, uint256 amount); + event StakeBond(address indexed prover, uint256 amount); + event UnstakeBond(address indexed prover, uint256 amount); + function deposit(uint256 _amount) external; - function withdraw(uint256 _amount) external; - function stakeBond(uint256 _bondAmount, address _prover) external; - function unstakeBond(uint256 _bondAmount, address _prover) external; + function startWithdraw(uint256 _amount) external; + function executeWithdraw() external; + function stakeBond(address _prover, uint256 _amount) external; + function unstakeBond(address _prover, uint256 _amount) external; + function minBalanceAtTime(Timestamp _timestamp, address _prover) external view returns (uint256); } diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 62974893e69..6aa9f616938 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -7,6 +7,7 @@ import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; +import {EpochProofQuoteLib} from "@aztec/core/libraries/EpochProofQuoteLib.sol"; import {Timestamp, Slot, Epoch} from "@aztec/core/libraries/TimeMath.sol"; @@ -30,7 +31,7 @@ interface IRollup { function prune() external; - function claimEpochProofRight(DataStructures.SignedEpochProofQuote calldata _quote) external; + function claimEpochProofRight(EpochProofQuoteLib.SignedEpochProofQuote calldata _quote) external; function propose( bytes calldata _header, @@ -48,7 +49,7 @@ interface IRollup { bytes32[] memory _txHashes, SignatureLib.Signature[] memory _signatures, bytes calldata _body, - DataStructures.SignedEpochProofQuote calldata _quote + EpochProofQuoteLib.SignedEpochProofQuote calldata _quote ) external; function submitEpochRootProof( @@ -91,6 +92,11 @@ interface IRollup { Epoch provenEpochNumber ); + function quoteToDigest(EpochProofQuoteLib.EpochProofQuote memory quote) + external + view + returns (bytes32); + function archive() external view returns (bytes32); function archiveAt(uint256 _blockNumber) external view returns (bytes32); function getProvenBlockNumber() external view returns (uint256); @@ -98,7 +104,7 @@ interface IRollup { function getEpochToProve() external view returns (Epoch); function nextEpochToClaim() external view returns (Epoch); function getEpochForBlock(uint256 blockNumber) external view returns (Epoch); - function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) + function validateEpochProofRightClaim(EpochProofQuoteLib.SignedEpochProofQuote calldata _quote) external view; function getEpochProofPublicInputs( diff --git a/l1-contracts/src/core/libraries/DataStructures.sol b/l1-contracts/src/core/libraries/DataStructures.sol index 8827cededee..537f3d6e7b2 100644 --- a/l1-contracts/src/core/libraries/DataStructures.sol +++ b/l1-contracts/src/core/libraries/DataStructures.sol @@ -1,10 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 -// Copyright 2023 Aztec Labs. +// Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; - -import {Slot, Epoch} from "@aztec/core/libraries/TimeMath.sol"; +import {Epoch} from "@aztec/core/libraries/TimeMath.sol"; /** * @title Data Structures Library @@ -77,32 +75,6 @@ library DataStructures { bool ignoreSignatures; } - /** - * @notice Struct encompassing an epoch proof quote - * @param epochToProve - The epoch number to prove - * @param validUntilSlot - The deadline of the quote, denoted in L2 slots - * @param bondAmount - The size of the bond - * @param prover - The address of the prover - * @param basisPointFee - The fee measured in basis points - */ - struct EpochProofQuote { - Epoch epochToProve; - Slot validUntilSlot; - uint256 bondAmount; - address prover; - uint32 basisPointFee; - } - - /** - * @notice A signed quote for the epoch proof - * @param quote - The Epoch Proof Quote - * @param signature - A signature on the quote - */ - struct SignedEpochProofQuote { - EpochProofQuote quote; - SignatureLib.Signature signature; - } - /** * @notice Struct containing the Epoch Proof Claim * @param epochToProve - the epoch that the bond provider is claiming to prove diff --git a/l1-contracts/src/core/libraries/EpochProofQuoteLib.sol b/l1-contracts/src/core/libraries/EpochProofQuoteLib.sol new file mode 100644 index 00000000000..1aba031a64d --- /dev/null +++ b/l1-contracts/src/core/libraries/EpochProofQuoteLib.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {Slot, Epoch} from "@aztec/core/libraries/TimeMath.sol"; +import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; + +library EpochProofQuoteLib { + /** + * @notice Struct encompassing an epoch proof quote + * @param epochToProve - The epoch number to prove + * @param validUntilSlot - The deadline of the quote, denoted in L2 slots + * @param bondAmount - The size of the bond + * @param prover - The address of the prover + * @param basisPointFee - The fee measured in basis points + */ + struct EpochProofQuote { + Epoch epochToProve; + Slot validUntilSlot; + uint256 bondAmount; + address prover; + uint32 basisPointFee; + } + + /** + * @notice A signed quote for the epoch proof + * @param quote - The Epoch Proof Quote + * @param signature - A signature on the quote + */ + struct SignedEpochProofQuote { + EpochProofQuote quote; + SignatureLib.Signature signature; + } + + bytes32 public constant EPOCH_PROOF_QUOTE_TYPEHASH = keccak256( + "EpochProofQuote(uint256 epochToProve,uint256 validUntilSlot,uint256 bondAmount,address prover,uint32 basisPointFee)" + ); + + function hash(EpochProofQuote memory quote) internal pure returns (bytes32) { + return keccak256( + abi.encode( + EPOCH_PROOF_QUOTE_TYPEHASH, + quote.epochToProve, + quote.validUntilSlot, + quote.bondAmount, + quote.prover, + quote.basisPointFee + ) + ); + } +} diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index a6b3d8a68af..8df16131fd1 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -100,4 +100,9 @@ library Errors { error FeeJuicePortal__AlreadyInitialized(); // 0xc7a172fe error FeeJuicePortal__InvalidInitialization(); // 0xfd9b3208 error FeeJuicePortal__Unauthorized(); // 0x67e3691e + + // Proof Commitment Escrow + error ProofCommitmentEscrow__InsufficientBalance(uint256 balance, uint256 requested); // 0x09b8b789 + error ProofCommitmentEscrow__NotOwner(address caller); // 0x2ac332c1 + error ProofCommitmentEscrow__WithdrawRequestNotReady(uint256 current, Timestamp readyAt); // 0xb32ab8a7 } diff --git a/l1-contracts/src/core/libraries/SignatureLib.sol b/l1-contracts/src/core/libraries/SignatureLib.sol deleted file mode 100644 index 8d28e2f2796..00000000000 --- a/l1-contracts/src/core/libraries/SignatureLib.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {Errors} from "@aztec/core/libraries/Errors.sol"; - -library SignatureLib { - struct Signature { - bool isEmpty; - uint8 v; - bytes32 r; - bytes32 s; - } - - /** - * @notice Verified a signature, throws if the signature is invalid or empty - * - * @param _signature - The signature to verify - * @param _signer - The expected signer of the signature - * @param _digest - The digest that was signed - */ - function verify(Signature memory _signature, address _signer, bytes32 _digest) internal pure { - require(!_signature.isEmpty, Errors.SignatureLib__CannotVerifyEmpty()); - address recovered = ecrecover(_digest, _signature.v, _signature.r, _signature.s); - require(_signer == recovered, Errors.SignatureLib__InvalidSignature(_signer, recovered)); - } -} diff --git a/l1-contracts/src/core/libraries/crypto/SignatureLib.sol b/l1-contracts/src/core/libraries/crypto/SignatureLib.sol index 98f37d0cc1c..bcae500df8c 100644 --- a/l1-contracts/src/core/libraries/crypto/SignatureLib.sol +++ b/l1-contracts/src/core/libraries/crypto/SignatureLib.sol @@ -24,4 +24,8 @@ library SignatureLib { address recovered = ecrecover(_digest, _signature.v, _signature.r, _signature.s); require(_signer == recovered, Errors.SignatureLib__InvalidSignature(_signer, recovered)); } + + function toBytes(Signature memory _signature) internal pure returns (bytes memory) { + return abi.encodePacked(_signature.r, _signature.s, _signature.v); + } } diff --git a/l1-contracts/src/mock/MockProofCommitmentEscrow.sol b/l1-contracts/src/mock/MockProofCommitmentEscrow.sol index 4fdd0ca1306..4568f3392eb 100644 --- a/l1-contracts/src/mock/MockProofCommitmentEscrow.sol +++ b/l1-contracts/src/mock/MockProofCommitmentEscrow.sol @@ -3,21 +3,30 @@ pragma solidity >=0.8.27; import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; +import {Timestamp} from "@aztec/core/libraries/TimeMath.sol"; contract MockProofCommitmentEscrow is IProofCommitmentEscrow { function deposit(uint256 _amount) external override { // do nothing } - function withdraw(uint256 _amount) external override { + function startWithdraw(uint256 _amount) external override { // do nothing } - function unstakeBond(uint256 _amount, address _prover) external override { + function executeWithdraw() external override { // do nothing } - function stakeBond(uint256 _amount, address _prover) external override { + function unstakeBond(address _prover, uint256 _amount) external override { // do nothing } + + function stakeBond(address _prover, uint256 _amount) external override { + // do nothing + } + + function minBalanceAtTime(Timestamp, address) external pure override returns (uint256) { + return 0; + } } diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index bd581a883b2..cc481648bfa 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -// Copyright 2023 Aztec Labs. +// Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; import {DecoderBase} from "./decoders/Base.sol"; @@ -7,6 +7,7 @@ import {DecoderBase} from "./decoders/Base.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import {EpochProofQuoteLib} from "@aztec/core/libraries/EpochProofQuoteLib.sol"; import {Registry} from "@aztec/governance/Registry.sol"; import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; @@ -18,7 +19,7 @@ import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol"; import {Leonidas} from "@aztec/core/Leonidas.sol"; import {NaiveMerkle} from "./merkle/Naive.sol"; import {MerkleTestUtil} from "./merkle/TestUtil.sol"; -import {PortalERC20} from "./portals/PortalERC20.sol"; +import {TestERC20} from "./TestERC20.sol"; import {TxsDecoderHelper} from "./decoders/helpers/TxsDecoderHelper.sol"; import {IERC20Errors} from "@oz/interfaces/draft-IERC6093.sol"; @@ -41,12 +42,13 @@ contract RollupTest is DecoderBase { Rollup internal rollup; MerkleTestUtil internal merkleTestUtil; TxsDecoderHelper internal txsHelper; - PortalERC20 internal portalERC20; + TestERC20 internal testERC20; FeeJuicePortal internal feeJuicePortal; SignatureLib.Signature[] internal signatures; - DataStructures.SignedEpochProofQuote internal quote; + EpochProofQuoteLib.EpochProofQuote internal quote; + EpochProofQuoteLib.SignedEpochProofQuote internal signedQuote; /** * @notice Set up the contracts needed for the tests with time aligned to the provided block name @@ -62,11 +64,11 @@ contract RollupTest is DecoderBase { } registry = new Registry(address(this)); - portalERC20 = new PortalERC20(); + testERC20 = new TestERC20(); feeJuicePortal = new FeeJuicePortal(address(this)); - portalERC20.mint(address(feeJuicePortal), Constants.FEE_JUICE_INITIAL_MINT); + testERC20.mint(address(feeJuicePortal), Constants.FEE_JUICE_INITIAL_MINT); feeJuicePortal.initialize( - address(registry), address(portalERC20), bytes32(Constants.FEE_JUICE_ADDRESS) + address(registry), address(testERC20), bytes32(Constants.FEE_JUICE_ADDRESS) ); rollup = new Rollup(feeJuicePortal, bytes32(0), address(this), new address[](0)); inbox = Inbox(address(rollup.INBOX())); @@ -77,16 +79,16 @@ contract RollupTest is DecoderBase { merkleTestUtil = new MerkleTestUtil(); txsHelper = new TxsDecoderHelper(); - quote = DataStructures.SignedEpochProofQuote({ - quote: DataStructures.EpochProofQuote({ - epochToProve: Epoch.wrap(0), - validUntilSlot: Slot.wrap(1), - bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), - prover: address(0), - basisPointFee: 0 - }), - signature: SignatureLib.Signature({isEmpty: false, v: 27, r: bytes32(0), s: bytes32(0)}) + uint256 privateKey = 0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234; + address signer = vm.addr(privateKey); + quote = EpochProofQuoteLib.EpochProofQuote({ + epochToProve: Epoch.wrap(0), + validUntilSlot: Slot.wrap(1), + bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), + prover: signer, + basisPointFee: 0 }); + signedQuote = _quoteToSignedQuote(quote); _; } @@ -99,7 +101,7 @@ contract RollupTest is DecoderBase { // sanity check that proven/pending tip are at genesis vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); warpToL2Slot(1); assertEq(rollup.getCurrentSlot(), 1, "warp to slot 1 failed"); @@ -107,46 +109,50 @@ contract RollupTest is DecoderBase { // empty slots do not move pending chain vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); } function testClaimWithWrongEpoch() public setUpFor("mixed_block_1") { _testBlock("mixed_block_1", false, 1); - - quote.quote.epochToProve = Epoch.wrap(1); + quote.epochToProve = Epoch.wrap(1); + signedQuote = _quoteToSignedQuote(quote); vm.expectRevert( abi.encodeWithSelector( - Errors.Rollup__NotClaimingCorrectEpoch.selector, 0, quote.quote.epochToProve + Errors.Rollup__NotClaimingCorrectEpoch.selector, 0, signedQuote.quote.epochToProve ) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); } function testClaimWithInsufficientBond() public setUpFor("mixed_block_1") { _testBlock("mixed_block_1", false, 1); - quote.quote.bondAmount = 0; + quote.bondAmount = 0; + signedQuote = _quoteToSignedQuote(quote); vm.expectRevert( abi.encodeWithSelector( Errors.Rollup__InsufficientBondAmount.selector, rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), - quote.quote.bondAmount + signedQuote.quote.bondAmount ) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); } function testClaimPastValidUntil() public setUpFor("mixed_block_1") { _testBlock("mixed_block_1", false, 1); - quote.quote.validUntilSlot = Slot.wrap(0); + quote.validUntilSlot = Slot.wrap(0); + signedQuote = _quoteToSignedQuote(quote); vm.expectRevert( - abi.encodeWithSelector(Errors.Rollup__QuoteExpired.selector, 1, quote.quote.validUntilSlot) + abi.encodeWithSelector( + Errors.Rollup__QuoteExpired.selector, 1, signedQuote.quote.validUntilSlot + ) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); } function testClaimSimple() public setUpFor("mixed_block_1") { @@ -154,9 +160,9 @@ contract RollupTest is DecoderBase { vm.expectEmit(true, true, true, true); emit IRollup.ProofRightClaimed( - quote.quote.epochToProve, address(0), address(this), quote.quote.bondAmount, Slot.wrap(1) + quote.epochToProve, quote.prover, address(this), quote.bondAmount, Slot.wrap(1) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); ( Epoch epochToProve, @@ -165,26 +171,26 @@ contract RollupTest is DecoderBase { address bondProvider, address proposerClaimant ) = rollup.proofClaim(); - assertEq(epochToProve, quote.quote.epochToProve, "Invalid epoch to prove"); - assertEq(basisPointFee, quote.quote.basisPointFee, "Invalid basis point fee"); - assertEq(bondAmount, quote.quote.bondAmount, "Invalid bond amount"); + assertEq(epochToProve, signedQuote.quote.epochToProve, "Invalid epoch to prove"); + assertEq(basisPointFee, signedQuote.quote.basisPointFee, "Invalid basis point fee"); + assertEq(bondAmount, signedQuote.quote.bondAmount, "Invalid bond amount"); // TODO #8573 // This will be fixed with proper escrow - assertEq(bondProvider, address(0), "Invalid bond provider"); + assertEq(bondProvider, quote.prover, "Invalid bond provider"); assertEq(proposerClaimant, address(this), "Invalid proposer claimant"); } function testClaimTwice() public setUpFor("mixed_block_1") { _testBlock("mixed_block_1", false, 1); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); warpToL2Slot(2); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); // warp to epoch 1 warpToL2Slot(Constants.AZTEC_EPOCH_DURATION); @@ -192,7 +198,7 @@ contract RollupTest is DecoderBase { // We should still be trying to prove epoch 0 in epoch 1 vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); // still nothing to prune vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NothingToPrune.selector)); @@ -211,17 +217,18 @@ contract RollupTest is DecoderBase { rollup.CLAIM_DURATION_IN_L2_SLOTS() ) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); } function testNoPruneWhenClaimExists() public setUpFor("mixed_block_1") { _testBlock("mixed_block_1", false, 1); - quote.quote.validUntilSlot = Epoch.wrap(2).toSlots(); + quote.validUntilSlot = Epoch.wrap(2).toSlots(); + signedQuote = _quoteToSignedQuote(quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS()); @@ -232,50 +239,49 @@ contract RollupTest is DecoderBase { function testPruneWhenClaimExpires() public setUpFor("mixed_block_1") { _testBlock("mixed_block_1", false, 1); - quote.quote.validUntilSlot = Epoch.wrap(2).toSlots(); + quote.validUntilSlot = Epoch.wrap(2).toSlots(); + signedQuote = _quoteToSignedQuote(quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION * 2); // We should still be trying to prove epoch 0 in epoch 2 vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); rollup.prune(); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); } function testClaimAfterPrune() public setUpFor("mixed_block_1") { _testBlock("mixed_block_1", false, 1); - quote.quote.validUntilSlot = Epoch.wrap(3).toSlots(); - quote.quote.prover = address(this); + quote.validUntilSlot = Epoch.wrap(3).toSlots(); + signedQuote = _quoteToSignedQuote(quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION * 3); rollup.prune(); _testBlock("mixed_block_1", false, Epoch.wrap(3).toSlots().unwrap()); - quote.quote.epochToProve = Epoch.wrap(3); + + quote.epochToProve = Epoch.wrap(3); + signedQuote = _quoteToSignedQuote(quote); vm.expectEmit(true, true, true, true); emit IRollup.ProofRightClaimed( - quote.quote.epochToProve, - address(this), - address(this), - quote.quote.bondAmount, - Epoch.wrap(3).toSlots() + quote.epochToProve, quote.prover, address(this), quote.bondAmount, Epoch.wrap(3).toSlots() ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(signedQuote); } function testPruneWhenNoProofClaim() public setUpFor("mixed_block_1") { @@ -424,7 +430,7 @@ contract RollupTest is DecoderBase { DecoderBase.Data memory data = load("mixed_block_1").block; bytes32[] memory txHashes = new bytes32[](0); - uint256 portalBalance = portalERC20.balanceOf(address(feeJuicePortal)); + uint256 portalBalance = testERC20.balanceOf(address(feeJuicePortal)); address coinbase = data.decodedHeader.globalVariables.coinbase; // Progress time as necessary @@ -436,17 +442,17 @@ contract RollupTest is DecoderBase { mstore(add(header, add(0x20, 0x0248)), feeAmount) } - assertEq(portalERC20.balanceOf(address(rollup)), 0, "invalid rollup balance"); + assertEq(testERC20.balanceOf(address(rollup)), 0, "invalid rollup balance"); // We jump to the time of the block. (unless it is in the past) vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp)); - uint256 coinbaseBalance = portalERC20.balanceOf(coinbase); + uint256 coinbaseBalance = testERC20.balanceOf(coinbase); assertEq(coinbaseBalance, 0, "invalid initial coinbase balance"); // Assert that balance have NOT been increased by proposing the block rollup.propose(header, data.archive, data.blockHash, txHashes, signatures, data.body); - assertEq(portalERC20.balanceOf(coinbase), 0, "invalid coinbase balance"); + assertEq(testERC20.balanceOf(coinbase), 0, "invalid coinbase balance"); } (bytes32 preArchive, bytes32 preBlockHash,) = rollup.blocks(0); @@ -471,11 +477,11 @@ contract RollupTest is DecoderBase { coinbase, feeAmount ); - assertEq(portalERC20.balanceOf(coinbase), 0, "invalid coinbase balance"); + assertEq(testERC20.balanceOf(coinbase), 0, "invalid coinbase balance"); } { - portalERC20.mint(address(feeJuicePortal), feeAmount - portalBalance); + testERC20.mint(address(feeJuicePortal), feeAmount - portalBalance); // When the block is proven we should have received the funds _submitEpochProofWithFee( @@ -489,7 +495,7 @@ contract RollupTest is DecoderBase { coinbase, feeAmount ); - assertEq(portalERC20.balanceOf(coinbase), feeAmount, "invalid coinbase balance"); + assertEq(testERC20.balanceOf(coinbase), feeAmount, "invalid coinbase balance"); } } @@ -700,6 +706,20 @@ contract RollupTest is DecoderBase { _submitEpochProof(rollup, 1, preArchive, data.archive, preBlockHash, wrongBlockHash, bytes32(0)); } + function _quoteToSignedQuote(EpochProofQuoteLib.EpochProofQuote memory _quote) + internal + view + returns (EpochProofQuoteLib.SignedEpochProofQuote memory) + { + bytes32 digest = rollup.quoteToDigest(_quote); + (uint8 v, bytes32 r, bytes32 s) = + vm.sign(0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234, digest); + return EpochProofQuoteLib.SignedEpochProofQuote({ + quote: _quote, + signature: SignatureLib.Signature({isEmpty: false, v: v, r: r, s: s}) + }); + } + function _testBlock(string memory name, bool _submitProof) public { _testBlock(name, _submitProof, 0); } diff --git a/l1-contracts/test/portals/PortalERC20.sol b/l1-contracts/test/TestERC20.sol similarity index 89% rename from l1-contracts/test/portals/PortalERC20.sol rename to l1-contracts/test/TestERC20.sol index 4c1dc5372be..3f0e54dc5f6 100644 --- a/l1-contracts/test/portals/PortalERC20.sol +++ b/l1-contracts/test/TestERC20.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import "@oz/token/ERC20/ERC20.sol"; -contract PortalERC20 is ERC20 { +contract TestERC20 is ERC20 { constructor() ERC20("Portal", "PORTAL") {} function mint(address to, uint256 amount) external { diff --git a/l1-contracts/test/TestERC20.t.sol b/l1-contracts/test/TestERC20.t.sol new file mode 100644 index 00000000000..4f50cb73f21 --- /dev/null +++ b/l1-contracts/test/TestERC20.t.sol @@ -0,0 +1,17 @@ +pragma solidity ^0.8.18; + +import "forge-std/Test.sol"; +import {TestERC20} from "./TestERC20.sol"; + +contract TestERC20Test is Test { + TestERC20 testERC20; + + function setUp() public { + testERC20 = new TestERC20(); + } + + function test_mint() public { + testERC20.mint(address(this), 100); + assertEq(testERC20.balanceOf(address(this)), 100); + } +} diff --git a/l1-contracts/test/portals/PortalERC20.t.sol b/l1-contracts/test/portals/PortalERC20.t.sol deleted file mode 100644 index cf69d9a6882..00000000000 --- a/l1-contracts/test/portals/PortalERC20.t.sol +++ /dev/null @@ -1,17 +0,0 @@ -pragma solidity ^0.8.18; - -import "forge-std/Test.sol"; -import {PortalERC20} from "./PortalERC20.sol"; - -contract PortalERC20Test is Test { - PortalERC20 portalERC20; - - function setUp() public { - portalERC20 = new PortalERC20(); - } - - function test_mint() public { - portalERC20.mint(address(this), 100); - assertEq(portalERC20.balanceOf(address(this)), 100); - } -} diff --git a/l1-contracts/test/portals/TokenPortal.t.sol b/l1-contracts/test/portals/TokenPortal.t.sol index d6c47232cff..dff8eca55b1 100644 --- a/l1-contracts/test/portals/TokenPortal.t.sol +++ b/l1-contracts/test/portals/TokenPortal.t.sol @@ -17,7 +17,7 @@ import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; // Portal tokens import {TokenPortal} from "./TokenPortal.sol"; -import {PortalERC20} from "./PortalERC20.sol"; +import {TestERC20} from "../TestERC20.sol"; import {NaiveMerkle} from "../merkle/Naive.sol"; @@ -37,7 +37,7 @@ contract TokenPortalTest is Test { bytes32 internal l2TokenAddress = bytes32(uint256(0x42)); TokenPortal internal tokenPortal; - PortalERC20 internal portalERC20; + TestERC20 internal testERC20; // input params uint32 internal deadline = uint32(block.timestamp + 1 days); @@ -59,7 +59,7 @@ contract TokenPortalTest is Test { function setUp() public { registry = new Registry(address(this)); - portalERC20 = new PortalERC20(); + testERC20 = new TestERC20(); rollup = new Rollup(IFeeJuicePortal(address(0)), bytes32(0), address(this), new address[](0)); inbox = rollup.INBOX(); outbox = rollup.OUTBOX(); @@ -67,10 +67,10 @@ contract TokenPortalTest is Test { registry.upgrade(address(rollup)); tokenPortal = new TokenPortal(); - tokenPortal.initialize(address(registry), address(portalERC20), l2TokenAddress); + tokenPortal.initialize(address(registry), address(testERC20), l2TokenAddress); // Modify the proven block count - vm.store(address(rollup), bytes32(uint256(7)), bytes32(l2BlockNumber)); + vm.store(address(rollup), bytes32(uint256(9)), bytes32(l2BlockNumber)); assertEq(rollup.getProvenBlockNumber(), l2BlockNumber); vm.deal(address(this), 100 ether); @@ -108,8 +108,8 @@ contract TokenPortalTest is Test { function testDepositPrivate() public returns (bytes32) { // mint token and approve to the portal - portalERC20.mint(address(this), mintAmount); - portalERC20.approve(address(tokenPortal), mintAmount); + testERC20.mint(address(this), mintAmount); + testERC20.approve(address(tokenPortal), mintAmount); // Check for the expected message DataStructures.L1ToL2Msg memory expectedMessage = _createExpectedMintPrivateL1ToL2Message(); @@ -134,8 +134,8 @@ contract TokenPortalTest is Test { function testDepositPublic() public returns (bytes32) { // mint token and approve to the portal - portalERC20.mint(address(this), mintAmount); - portalERC20.approve(address(tokenPortal), mintAmount); + testERC20.mint(address(this), mintAmount); + testERC20.approve(address(tokenPortal), mintAmount); // Check for the expected message DataStructures.L1ToL2Msg memory expectedMessage = _createExpectedMintPublicL1ToL2Message(); @@ -183,7 +183,7 @@ contract TokenPortalTest is Test { returns (bytes32, bytes32[] memory, bytes32) { // send assets to the portal - portalERC20.mint(address(tokenPortal), withdrawAmount); + testERC20.mint(address(tokenPortal), withdrawAmount); // Create the message (bytes32 l2ToL1Message,) = _createWithdrawMessageForOutbox(_designatedCaller); @@ -208,7 +208,7 @@ contract TokenPortalTest is Test { // add message with caller as this address (bytes32 l2ToL1Message, bytes32[] memory siblingPath, bytes32 treeRoot) = _addWithdrawMessageInOutbox(address(0), l2BlockNumber); - assertEq(portalERC20.balanceOf(recipient), 0); + assertEq(testERC20.balanceOf(recipient), 0); vm.startPrank(_caller); vm.expectEmit(true, true, true, true); @@ -216,7 +216,7 @@ contract TokenPortalTest is Test { tokenPortal.withdraw(recipient, withdrawAmount, false, l2BlockNumber, 0, siblingPath); // Should have received 654 RNA tokens - assertEq(portalERC20.balanceOf(recipient), withdrawAmount); + assertEq(testERC20.balanceOf(recipient), withdrawAmount); // Should not be able to withdraw again vm.expectRevert( @@ -261,6 +261,6 @@ contract TokenPortalTest is Test { tokenPortal.withdraw(recipient, withdrawAmount, true, l2BlockNumber, 0, siblingPath); // Should have received 654 RNA tokens - assertEq(portalERC20.balanceOf(recipient), withdrawAmount); + assertEq(testERC20.balanceOf(recipient), withdrawAmount); } } diff --git a/l1-contracts/test/portals/UniswapPortal.t.sol b/l1-contracts/test/portals/UniswapPortal.t.sol index aa3ee982ad0..afb986f9046 100644 --- a/l1-contracts/test/portals/UniswapPortal.t.sol +++ b/l1-contracts/test/portals/UniswapPortal.t.sol @@ -65,7 +65,7 @@ contract UniswapPortalTest is Test { uniswapPortal.initialize(address(registry), l2UniswapAddress); // Modify the proven block count - vm.store(address(rollup), bytes32(uint256(7)), bytes32(l2BlockNumber + 1)); + vm.store(address(rollup), bytes32(uint256(9)), bytes32(l2BlockNumber + 1)); assertEq(rollup.getProvenBlockNumber(), l2BlockNumber + 1); // have DAI locked in portal that can be moved when funds are withdrawn diff --git a/l1-contracts/test/prover-coordination/ProofCommitmentEscrow.t.sol b/l1-contracts/test/prover-coordination/ProofCommitmentEscrow.t.sol new file mode 100644 index 00000000000..401fd67a2bc --- /dev/null +++ b/l1-contracts/test/prover-coordination/ProofCommitmentEscrow.t.sol @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {Test} from "forge-std/Test.sol"; + +import {ProofCommitmentEscrow} from "@aztec/core/ProofCommitmentEscrow.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {Timestamp} from "@aztec/core/libraries/TimeMath.sol"; + +import {TestERC20} from "../TestERC20.sol"; + +// solhint-disable comprehensive-interface + +contract TestProofCommitmentEscrow is Test { + // solhint-disable-next-line var-name-mixedcase + ProofCommitmentEscrow internal ESCROW; + // solhint-disable-next-line var-name-mixedcase + TestERC20 internal TOKEN; + address internal prover; + uint256 internal depositAmount; + + modifier setupWithApproval(address _prover, uint256 _depositAmount) { + TOKEN.mint(_prover, _depositAmount); + vm.prank(_prover); + TOKEN.approve(address(ESCROW), _depositAmount); + + prover = _prover; + depositAmount = _depositAmount; + _; + } + + function setUp() public { + TOKEN = new TestERC20(); + ESCROW = new ProofCommitmentEscrow(TOKEN, address(this)); + } + + function testDeposit() public setupWithApproval(address(42), 100) { + vm.prank(prover); + ESCROW.deposit(depositAmount); + + assertEq( + TOKEN.balanceOf(address(ESCROW)), depositAmount, "Escrow balance should match deposit amount" + ); + assertEq(TOKEN.balanceOf(prover), 0, "Prover balance should be 0 after deposit"); + } + + function testCannotWithdrawWithoutMatureRequest() public setupWithApproval(address(42), 100) { + vm.prank(prover); + ESCROW.deposit(depositAmount); + uint256 withdrawReadyAt = block.timestamp + ESCROW.WITHDRAW_DELAY(); + + vm.prank(prover); + ESCROW.startWithdraw(depositAmount); + + vm.prank(prover); + vm.expectRevert( + abi.encodeWithSelector( + Errors.ProofCommitmentEscrow__WithdrawRequestNotReady.selector, + block.timestamp, + withdrawReadyAt + ) + ); + ESCROW.executeWithdraw(); + + vm.warp(block.timestamp + ESCROW.WITHDRAW_DELAY() - 1); + vm.prank(prover); + vm.expectRevert( + abi.encodeWithSelector( + Errors.ProofCommitmentEscrow__WithdrawRequestNotReady.selector, + block.timestamp, + withdrawReadyAt + ) + ); + ESCROW.executeWithdraw(); + } + + function testWithdrawAfterDelay() public setupWithApproval(address(42), 100) { + vm.prank(prover); + ESCROW.deposit(depositAmount); + uint256 withdrawAmount = 50; + uint256 withdrawReadyAt = block.timestamp + ESCROW.WITHDRAW_DELAY(); + + vm.prank(prover); + ESCROW.startWithdraw(withdrawAmount); + + vm.warp(withdrawReadyAt); + + vm.prank(prover); + ESCROW.executeWithdraw(); + + assertEq( + TOKEN.balanceOf(address(ESCROW)), + depositAmount - withdrawAmount, + "Escrow balance should be reduced after withdrawal" + ); + assertEq(TOKEN.balanceOf(prover), withdrawAmount, "Prover balance should match deposit amount"); + } + + function testCannotReplayWithdrawRequest(uint256 _withdrawAmount) + public + setupWithApproval(address(42), 100) + { + vm.prank(prover); + ESCROW.deposit(depositAmount); + uint256 withdrawAmount = bound(_withdrawAmount, 1, depositAmount); + uint256 withdrawReadyAt = block.timestamp + ESCROW.WITHDRAW_DELAY(); + + vm.prank(prover); + ESCROW.startWithdraw(withdrawAmount); + vm.warp(withdrawReadyAt); + + vm.prank(prover); + ESCROW.executeWithdraw(); + + vm.prank(prover); + ESCROW.executeWithdraw(); + + assertEq( + TOKEN.balanceOf(address(ESCROW)), + depositAmount - withdrawAmount, + "Escrow balance should be reduced after withdrawal" + ); + } + + function testOnlyOwnerCanStake(address nonOwner) public { + vm.assume(nonOwner != address(this)); + vm.prank(nonOwner); + vm.expectRevert( + abi.encodeWithSelector(Errors.ProofCommitmentEscrow__NotOwner.selector, nonOwner) + ); + ESCROW.stakeBond(address(0), 0); + } + + function testCannotStakeMoreThanProverBalance() public setupWithApproval(address(42), 100) { + vm.prank(prover); + ESCROW.deposit(depositAmount); + uint256 stakeAmount = depositAmount + 1; + + vm.expectRevert(); + ESCROW.stakeBond(prover, stakeAmount); + + assertEq( + TOKEN.balanceOf(address(ESCROW)), depositAmount, "Escrow balance should match deposit amount" + ); + assertEq(ESCROW.deposits(prover), depositAmount, "Prover balance should match deposit amount"); + } + + function testOnlyOwnerCanUnstake(address nonOwner) public { + vm.assume(nonOwner != address(this)); + vm.prank(nonOwner); + vm.expectRevert( + abi.encodeWithSelector(Errors.ProofCommitmentEscrow__NotOwner.selector, nonOwner) + ); + ESCROW.unstakeBond(address(0), 0); + } + + function testStakeAndUnstake() public setupWithApproval(address(42), 100) { + vm.prank(prover); + ESCROW.deposit(depositAmount); + uint256 stakeAmount = 50; + + ESCROW.stakeBond(prover, stakeAmount); + + assertEq( + ESCROW.deposits(prover), depositAmount - stakeAmount, "Prover balance should be reduced" + ); + + ESCROW.unstakeBond(prover, stakeAmount); + + assertEq( + ESCROW.deposits(prover), depositAmount, "Prover balance should be restored after unstake" + ); + } + + function testOverwritingStakeSlashesPreviousProver() public { + address proverA = address(42); + address proverB = address(43); + uint256 depositAmountA = 100; + uint256 stakeAmountA = 50; + uint256 depositAmountB = 200; + uint256 stakeAmountB = 100; + + TOKEN.mint(proverA, depositAmountA); + vm.prank(proverA); + TOKEN.approve(address(ESCROW), depositAmountA); + vm.prank(proverA); + ESCROW.deposit(depositAmountA); + + TOKEN.mint(proverB, depositAmountB); + vm.prank(proverB); + TOKEN.approve(address(ESCROW), depositAmountB); + vm.prank(proverB); + ESCROW.deposit(depositAmountB); + + // Prover A is staked + ESCROW.stakeBond(proverA, stakeAmountA); + + // Prover B is staked + ESCROW.stakeBond(proverB, stakeAmountB); + + // Prover A is missing the stake + uint256 expectedDepositA = depositAmountA - stakeAmountA; + assertEq( + ESCROW.deposits(proverA), + expectedDepositA, + "Prover A's deposit should reflect the slashed stake" + ); + + // Prover B gets unstaked + ESCROW.unstakeBond(proverB, stakeAmountB); + assertEq( + ESCROW.deposits(proverB), + depositAmountB, + "Prover B's deposit should be restored after unstake" + ); + assertEq( + ESCROW.deposits(proverA), expectedDepositA, "Prover A's deposit remains slashed after unstake" + ); + } + + function testWithdrawRequestOverwriting() public setupWithApproval(address(42), 100) { + uint256 withdrawAmountA = 40; + uint256 withdrawAmountB = 60; + uint256 withdrawReadyAtA = block.timestamp + ESCROW.WITHDRAW_DELAY(); + uint256 withdrawReadyAtB = block.timestamp + 2 * ESCROW.WITHDRAW_DELAY(); + + vm.prank(prover); + ESCROW.deposit(depositAmount); + + // Prover starts first withdraw request + vm.prank(prover); + ESCROW.startWithdraw(withdrawAmountA); + + // Prover starts second withdraw request before executing first + vm.warp(withdrawReadyAtA); + + vm.prank(prover); + ESCROW.startWithdraw(withdrawAmountB); + + // Attempt to execute first withdraw request after its delay + vm.prank(prover); + vm.expectRevert( + abi.encodeWithSelector( + Errors.ProofCommitmentEscrow__WithdrawRequestNotReady.selector, + withdrawReadyAtA, + withdrawReadyAtB + ) + ); + ESCROW.executeWithdraw(); + + // Execute second withdraw request after its delay + vm.warp(withdrawReadyAtB); + vm.prank(prover); + ESCROW.executeWithdraw(); + + // Assert + assertEq( + ESCROW.deposits(prover), + depositAmount - withdrawAmountB, + "Prover's deposit should be reduced by the withdrawn amount" + ); + } + + function testMinBalanceAtTime() public setupWithApproval(address(42), 100) { + uint256 withdrawAmount = 25; + Timestamp withdrawReadyAt = Timestamp.wrap(block.timestamp + ESCROW.WITHDRAW_DELAY()); + + vm.prank(prover); + ESCROW.deposit(depositAmount); + + assertEq( + ESCROW.minBalanceAtTime(Timestamp.wrap(block.timestamp), prover), + depositAmount, + "Min balance should match deposit amount before any withdraw request" + ); + + assertEq( + ESCROW.minBalanceAtTime(withdrawReadyAt - Timestamp.wrap(1), prover), + depositAmount, + "Min balance should match deposit amount before withdraw request matures" + ); + + vm.prank(prover); + ESCROW.startWithdraw(withdrawAmount); + + assertEq( + ESCROW.minBalanceAtTime(Timestamp.wrap(block.timestamp), prover), + depositAmount, + "Min balance should be unaffected by pending withdraw request before maturity" + ); + + assertEq( + ESCROW.minBalanceAtTime(Timestamp.wrap(block.timestamp + ESCROW.WITHDRAW_DELAY()), prover), + 0, + "Min balance should be 0 at or beyond the delay window" + ); + + vm.warp(block.timestamp + 1); + + assertEq( + ESCROW.minBalanceAtTime(withdrawReadyAt, prover), + depositAmount - withdrawAmount, + "Min balance should be 75 after withdraw request matures" + ); + + assertEq( + ESCROW.minBalanceAtTime(withdrawReadyAt + Timestamp.wrap(1), prover), + 0, + "Min balance should be 0 at or beyond the delay window" + ); + } +} diff --git a/l1-contracts/test/sparta/Sparta.t.sol b/l1-contracts/test/sparta/Sparta.t.sol index 0eb15f5772f..a6e290c2fc1 100644 --- a/l1-contracts/test/sparta/Sparta.t.sol +++ b/l1-contracts/test/sparta/Sparta.t.sol @@ -15,7 +15,7 @@ import {Rollup} from "@aztec/core/Rollup.sol"; import {Leonidas} from "@aztec/core/Leonidas.sol"; import {NaiveMerkle} from "../merkle/Naive.sol"; import {MerkleTestUtil} from "../merkle/TestUtil.sol"; -import {PortalERC20} from "../portals/PortalERC20.sol"; +import {TestERC20} from "../TestERC20.sol"; import {TxsDecoderHelper} from "../decoders/helpers/TxsDecoderHelper.sol"; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol"; @@ -44,7 +44,7 @@ contract SpartaTest is DecoderBase { Rollup internal rollup; MerkleTestUtil internal merkleTestUtil; TxsDecoderHelper internal txsHelper; - PortalERC20 internal portalERC20; + TestERC20 internal testERC20; SignatureLib.Signature internal emptySignature; mapping(address validator => uint256 privateKey) internal privateKeys; @@ -73,7 +73,7 @@ contract SpartaTest is DecoderBase { initialValidators[i - 1] = validator; } - portalERC20 = new PortalERC20(); + testERC20 = new TestERC20(); rollup = new Rollup(IFeeJuicePortal(address(0)), bytes32(0), address(this), initialValidators); inbox = Inbox(address(rollup.INBOX())); outbox = Outbox(address(rollup.OUTBOX())); diff --git a/yarn-project/aztec-faucet/src/bin/index.ts b/yarn-project/aztec-faucet/src/bin/index.ts index 15058725657..456c067e4ed 100644 --- a/yarn-project/aztec-faucet/src/bin/index.ts +++ b/yarn-project/aztec-faucet/src/bin/index.ts @@ -2,7 +2,7 @@ import { NULL_KEY, createEthereumChain } from '@aztec/ethereum'; import { EthAddress } from '@aztec/foundation/eth-address'; import { createDebugLogger } from '@aztec/foundation/log'; -import { PortalERC20Abi } from '@aztec/l1-artifacts'; +import { TestERC20Abi } from '@aztec/l1-artifacts'; import http from 'http'; import Koa from 'koa'; @@ -182,7 +182,7 @@ async function transferAsset(assetName: AssetName, address: string) { try { const contract = getContract({ - abi: PortalERC20Abi, + abi: TestERC20Abi, address: assetAddress, client: walletClient, }); diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index d1eec6cabc4..b7505e7058a 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -135,6 +135,7 @@ export { L1NotePayload, L1EventPayload, EpochProofQuote, + EpochProofQuotePayload, } from '@aztec/circuit-types'; export { NodeInfo } from '@aztec/types/interfaces'; diff --git a/yarn-project/aztec/src/sandbox.ts b/yarn-project/aztec/src/sandbox.ts index 0a9f6158c72..9e508647a4b 100644 --- a/yarn-project/aztec/src/sandbox.ts +++ b/yarn-project/aztec/src/sandbox.ts @@ -20,12 +20,12 @@ import { InboxBytecode, OutboxAbi, OutboxBytecode, - PortalERC20Abi, - PortalERC20Bytecode, RegistryAbi, RegistryBytecode, RollupAbi, RollupBytecode, + TestERC20Abi, + TestERC20Bytecode, } from '@aztec/l1-artifacts'; import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types'; import { FeeJuiceAddress } from '@aztec/protocol-contracts/fee-juice'; @@ -108,8 +108,8 @@ export async function deployContractsToL1( contractBytecode: RollupBytecode, }, feeJuice: { - contractAbi: PortalERC20Abi, - contractBytecode: PortalERC20Bytecode, + contractAbi: TestERC20Abi, + contractBytecode: TestERC20Bytecode, }, feeJuicePortal: { contractAbi: FeeJuicePortalAbi, diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts index 049845921e1..9ab6d7172c4 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts @@ -1,12 +1,9 @@ import { EthAddress } from '@aztec/circuits.js'; -import { Secp256k1Signer } from '@aztec/foundation/crypto'; -import { EpochProofQuote } from './epoch_proof_quote.js'; import { EpochProofQuotePayload } from './epoch_proof_quote_payload.js'; describe('epoch proof quote', () => { it('should serialize / deserialize', () => { - const signer = Secp256k1Signer.random(); const payload = EpochProofQuotePayload.fromFields({ basisPointFee: 5000, bondAmount: 1000000000000000000n, @@ -15,10 +12,6 @@ describe('epoch proof quote', () => { validUntilSlot: 100n, }); - const quote = EpochProofQuote.new(payload, signer); - - expect(EpochProofQuote.fromBuffer(quote.toBuffer())).toEqual(quote); - - expect(quote.senderAddress).toEqual(signer.address); + expect(EpochProofQuotePayload.fromBuffer(payload.toBuffer())).toEqual(payload); }); }); diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts index 8839b257ff7..7bfd6070bd2 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts @@ -1,20 +1,16 @@ -import { type EthAddress } from '@aztec/circuits.js'; import { Buffer32 } from '@aztec/foundation/buffer'; -import { type Secp256k1Signer, recoverAddress } from '@aztec/foundation/crypto'; +import { type Secp256k1Signer } from '@aztec/foundation/crypto'; import { Signature } from '@aztec/foundation/eth-signature'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { type FieldsOf } from '@aztec/foundation/types'; import { Gossipable } from '../p2p/gossipable.js'; -import { getHashedSignaturePayloadEthSignedMessage } from '../p2p/signature_utils.js'; import { TopicType, createTopicString } from '../p2p/topic_type.js'; import { EpochProofQuotePayload } from './epoch_proof_quote_payload.js'; export class EpochProofQuote extends Gossipable { static override p2pTopic: string = createTopicString(TopicType.epoch_proof_quote); - private sender: EthAddress | undefined; - constructor(public readonly payload: EpochProofQuotePayload, public readonly signature: Signature) { super(); } @@ -36,32 +32,28 @@ export class EpochProofQuote extends Gossipable { return new EpochProofQuote(reader.readObject(EpochProofQuotePayload), reader.readObject(Signature)); } - static new(payload: EpochProofQuotePayload, signer: Secp256k1Signer): EpochProofQuote { - const digest = getHashedSignaturePayloadEthSignedMessage(payload); - const signature = signer.sign(digest); - return new EpochProofQuote(payload, signature); - } - - get senderAddress(): EthAddress { - if (!this.sender) { - const hashed = getHashedSignaturePayloadEthSignedMessage(this.payload); - - // Cache the sender for later use - this.sender = recoverAddress(hashed, this.signature); + // TODO: https://github.com/AztecProtocol/aztec-packages/issues/8911 + /** + * Creates a new quote with a signature. + * The digest provided must match what the rollup contract will produce i.e. `_hashTypedDataV4(EpochProofQuoteLib.hash(quote))` + * + * @param digest the digest of the payload that should be signed + * @param payload the actual quote + * @param signer the signer + * @returns a quote with an accompanying signature + */ + static new(digest: Buffer32, payload: EpochProofQuotePayload, signer: Secp256k1Signer): EpochProofQuote { + if (!payload.prover.equals(signer.address)) { + throw new Error(`Quote prover does not match signer. Prover [${payload.prover}], Signer [${signer.address}]`); } - - return this.sender; + const signature = signer.sign(digest); + const quote = new EpochProofQuote(payload, signature); + return quote; } toViemArgs() { return { - quote: { - epochToProve: this.payload.epochToProve, - validUntilSlot: this.payload.validUntilSlot, - bondAmount: this.payload.bondAmount, - prover: this.payload.prover.toString(), - basisPointFee: this.payload.basisPointFee, - }, + quote: this.payload.toViemArgs(), signature: this.signature.toViemSignature(), }; } diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts index be70356f7d8..e7b62c707f9 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts @@ -2,11 +2,9 @@ import { EthAddress } from '@aztec/circuits.js'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { type FieldsOf } from '@aztec/foundation/types'; -import { encodeAbiParameters, parseAbiParameters } from 'viem'; +import { inspect } from 'util'; -import { type Signable } from '../p2p/signature_utils.js'; - -export class EpochProofQuotePayload implements Signable { +export class EpochProofQuotePayload { constructor( public readonly epochToProve: bigint, public readonly validUntilSlot: bigint, @@ -50,17 +48,23 @@ export class EpochProofQuotePayload implements Signable { ); } - getPayloadToSign(): Buffer { - const abi = parseAbiParameters('uint256, uint256, uint256, address, uint32'); - const encodedData = encodeAbiParameters(abi, [ - this.epochToProve, - this.validUntilSlot, - this.bondAmount, - this.prover.toString(), - this.basisPointFee, - ] as const); + toViemArgs(): { + epochToProve: bigint; + validUntilSlot: bigint; + bondAmount: bigint; + prover: `0x${string}`; + basisPointFee: number; + } { + return { + epochToProve: this.epochToProve, + validUntilSlot: this.validUntilSlot, + bondAmount: this.bondAmount, + prover: this.prover.toString(), + basisPointFee: this.basisPointFee, + }; + } - // NOTE: trim the first two bytes to get rid of the `0x` prefix - return Buffer.from(encodedData.slice(2), 'hex'); + [inspect.custom](): string { + return `EpochProofQuotePayload { epochToProve: ${this.epochToProve}, validUntilSlot: ${this.validUntilSlot}, bondAmount: ${this.bondAmount}, prover: ${this.prover}, basisPointFee: ${this.basisPointFee} }`; } } diff --git a/yarn-project/cli/src/cmds/devnet/bootstrap_network.ts b/yarn-project/cli/src/cmds/devnet/bootstrap_network.ts index 5d5ef4eff6b..55b3d945513 100644 --- a/yarn-project/cli/src/cmds/devnet/bootstrap_network.ts +++ b/yarn-project/cli/src/cmds/devnet/bootstrap_network.ts @@ -100,13 +100,11 @@ export async function bootstrapNetwork( * Step 1. Deploy the L1 contracts, but don't initialize */ async function deployERC20({ walletClient, publicClient }: L1Clients) { - const { PortalERC20Abi, PortalERC20Bytecode, TokenPortalAbi, TokenPortalBytecode } = await import( - '@aztec/l1-artifacts' - ); + const { TestERC20Abi, TestERC20Bytecode, TokenPortalAbi, TokenPortalBytecode } = await import('@aztec/l1-artifacts'); const erc20: ContractArtifacts = { - contractAbi: PortalERC20Abi, - contractBytecode: PortalERC20Bytecode, + contractAbi: TestERC20Abi, + contractBytecode: TestERC20Bytecode, }; const portal: ContractArtifacts = { contractAbi: TokenPortalAbi, diff --git a/yarn-project/cli/src/cmds/l1/get_l1_balance.ts b/yarn-project/cli/src/cmds/l1/get_l1_balance.ts index 43f75ae5cde..c89cbb95954 100644 --- a/yarn-project/cli/src/cmds/l1/get_l1_balance.ts +++ b/yarn-project/cli/src/cmds/l1/get_l1_balance.ts @@ -1,7 +1,7 @@ import { type EthAddress } from '@aztec/circuits.js'; import { createEthereumChain } from '@aztec/ethereum'; import { type LogFn } from '@aztec/foundation/log'; -import { PortalERC20Abi } from '@aztec/l1-artifacts'; +import { TestERC20Abi } from '@aztec/l1-artifacts'; import { createPublicClient, getContract, http } from 'viem'; @@ -22,7 +22,7 @@ export async function getL1Balance( if (token) { const gasL1 = getContract({ address: token.toString(), - abi: PortalERC20Abi, + abi: TestERC20Abi, client: publicClient, }); diff --git a/yarn-project/cli/src/utils/aztec.ts b/yarn-project/cli/src/utils/aztec.ts index 71919cd8ec7..2c6f4316ee2 100644 --- a/yarn-project/cli/src/utils/aztec.ts +++ b/yarn-project/cli/src/utils/aztec.ts @@ -72,8 +72,8 @@ export async function deployAztecContracts( FeeJuicePortalAbi, FeeJuicePortalBytecode, - PortalERC20Abi, - PortalERC20Bytecode, + TestERC20Abi, + TestERC20Bytecode, } = await import('@aztec/l1-artifacts'); const { createEthereumChain, deployL1Contracts } = await import('@aztec/ethereum'); const { mnemonicToAccount, privateKeyToAccount } = await import('viem/accounts'); @@ -100,8 +100,8 @@ export async function deployAztecContracts( contractBytecode: RollupBytecode, }, feeJuice: { - contractAbi: PortalERC20Abi, - contractBytecode: PortalERC20Bytecode, + contractAbi: TestERC20Abi, + contractBytecode: TestERC20Bytecode, }, feeJuicePortal: { contractAbi: FeeJuicePortalAbi, diff --git a/yarn-project/cli/src/utils/portal_manager.ts b/yarn-project/cli/src/utils/portal_manager.ts index a30040f42c9..265ca2f5f95 100644 --- a/yarn-project/cli/src/utils/portal_manager.ts +++ b/yarn-project/cli/src/utils/portal_manager.ts @@ -1,7 +1,7 @@ // REFACTOR: This file has been shamelessly copied from yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts // We should make this a shared utility in the aztec.js package. import { type AztecAddress, type DebugLogger, type EthAddress, Fr, type PXE, computeSecretHash } from '@aztec/aztec.js'; -import { FeeJuicePortalAbi, PortalERC20Abi, TokenPortalAbi } from '@aztec/l1-artifacts'; +import { FeeJuicePortalAbi, TestERC20Abi, TokenPortalAbi } from '@aztec/l1-artifacts'; import { type Account, @@ -31,7 +31,7 @@ function generateClaimSecret(): [Fr, Fr] { } class L1TokenManager { - private contract: GetContractReturnType>; + private contract: GetContractReturnType>; public constructor( public readonly address: EthAddress, @@ -41,7 +41,7 @@ class L1TokenManager { ) { this.contract = getContract({ address: this.address.toString(), - abi: PortalERC20Abi, + abi: TestERC20Abi, client: this.walletClient, }); } diff --git a/yarn-project/end-to-end/Earthfile b/yarn-project/end-to-end/Earthfile index c349b153efb..f11e262c86e 100644 --- a/yarn-project/end-to-end/Earthfile +++ b/yarn-project/end-to-end/Earthfile @@ -172,6 +172,10 @@ e2e-token-contract: LOCALLY RUN ./scripts/e2e_test.sh ./src/e2e_token_contract +e2e-prover-coordination: + LOCALLY + RUN ./scripts/e2e_test.sh ./src/prover-coordination/e2e_json_coordination.test.ts + e2e-public-testnet: ARG L1_CHAIN_ID="31337" # automatically exported as ENV variables for below diff --git a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts index 6f0f889e4eb..168aa7fe72c 100644 --- a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +++ b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts @@ -11,7 +11,7 @@ import { createDebugLogger, } from '@aztec/aztec.js'; import { createL1Clients } from '@aztec/ethereum'; -import { InboxAbi, OutboxAbi, PortalERC20Abi, RollupAbi, TokenPortalAbi } from '@aztec/l1-artifacts'; +import { InboxAbi, OutboxAbi, RollupAbi, TestERC20Abi, TokenPortalAbi } from '@aztec/l1-artifacts'; import { TokenBridgeContract, TokenContract } from '@aztec/noir-contracts.js'; import { type Chain, type HttpTransport, type PublicClient, getContract } from 'viem'; @@ -158,7 +158,7 @@ export class CrossChainMessagingTest { }); const underlyingERC20 = getContract({ address: crossChainContext.underlying.toString(), - abi: PortalERC20Abi, + abi: TestERC20Abi, client: walletClient, }); diff --git a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts index aa5761aa80f..1157a4f3c7b 100644 --- a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts @@ -17,7 +17,7 @@ import { import { DefaultMultiCallEntrypoint } from '@aztec/aztec.js/entrypoint'; import { EthAddress, GasSettings, computePartialAddress } from '@aztec/circuits.js'; import { createL1Clients } from '@aztec/ethereum'; -import { PortalERC20Abi } from '@aztec/l1-artifacts'; +import { TestERC20Abi } from '@aztec/l1-artifacts'; import { AppSubscriptionContract, TokenContract as BananaCoin, @@ -340,7 +340,7 @@ export class FeesTest { const { walletClient } = createL1Clients(context.aztecNodeConfig.l1RpcUrl, MNEMONIC); const gasL1 = getContract({ address: data.l1FeeJuiceAddress.toString(), - abi: PortalERC20Abi, + abi: TestERC20Abi, client: walletClient, }); return await gasL1.read.balanceOf([this.coinbase.toString()]); diff --git a/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.ts b/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.ts index 6f622272b8f..c62950d85cb 100644 --- a/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.ts +++ b/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.ts @@ -7,12 +7,12 @@ import { InboxBytecode, OutboxAbi, OutboxBytecode, - PortalERC20Abi, - PortalERC20Bytecode, RegistryAbi, RegistryBytecode, RollupAbi, RollupBytecode, + TestERC20Abi, + TestERC20Bytecode, } from '@aztec/l1-artifacts'; import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types'; import { FeeJuiceAddress } from '@aztec/protocol-contracts/fee-juice'; @@ -46,8 +46,8 @@ export const setupL1Contracts = async ( contractBytecode: RollupBytecode, }, feeJuice: { - contractAbi: PortalERC20Abi, - contractBytecode: PortalERC20Bytecode, + contractAbi: TestERC20Abi, + contractBytecode: TestERC20Bytecode, }, feeJuicePortal: { contractAbi: FeeJuicePortalAbi, diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 7d456587d34..5534791b2ed 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -49,12 +49,12 @@ import { InboxBytecode, OutboxAbi, OutboxBytecode, - PortalERC20Abi, - PortalERC20Bytecode, RegistryAbi, RegistryBytecode, RollupAbi, RollupBytecode, + TestERC20Abi, + TestERC20Bytecode, } from '@aztec/l1-artifacts'; import { AuthRegistryContract, RouterContract } from '@aztec/noir-contracts.js'; import { FeeJuiceContract } from '@aztec/noir-contracts.js/FeeJuice'; @@ -136,8 +136,8 @@ export const setupL1Contracts = async ( contractBytecode: RollupBytecode, }, feeJuice: { - contractAbi: PortalERC20Abi, - contractBytecode: PortalERC20Bytecode, + contractAbi: TestERC20Abi, + contractBytecode: TestERC20Bytecode, }, feeJuicePortal: { contractAbi: FeeJuicePortalAbi, diff --git a/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts index 8ebe827b0bc..1a3daa2a931 100644 --- a/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts +++ b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts @@ -2,18 +2,29 @@ import { getSchnorrAccount } from '@aztec/accounts/schnorr'; import { type AccountWalletWithSecretKey, type DebugLogger, - type EpochProofQuote, + EpochProofQuote, + EpochProofQuotePayload, EthCheatCodes, createDebugLogger, - mockEpochProofQuote, } from '@aztec/aztec.js'; import { AZTEC_EPOCH_DURATION, AZTEC_SLOT_DURATION, type AztecAddress, EthAddress } from '@aztec/circuits.js'; +import { Buffer32 } from '@aztec/foundation/buffer'; import { times } from '@aztec/foundation/collection'; +import { Secp256k1Signer, keccak256, randomBigInt, randomInt } from '@aztec/foundation/crypto'; import { RollupAbi } from '@aztec/l1-artifacts'; import { StatefulTestContract } from '@aztec/noir-contracts.js'; import { beforeAll } from '@jest/globals'; -import { type PublicClient, getAddress, getContract } from 'viem'; +import { + type Account, + type Chain, + type GetContractReturnType, + type HttpTransport, + type PublicClient, + type WalletClient, + getAddress, + getContract, +} from 'viem'; import { type ISnapshotManager, @@ -22,16 +33,12 @@ import { createSnapshotManager, } from '../fixtures/snapshot_manager.js'; -// Tests simple block building with a sequencer that does not upload proofs to L1, -// and then follows with a prover node run (with real proofs disabled, but -// still simulating all circuits via a prover-client), in order to test -// the coordination through L1 between the sequencer and the prover node. -describe('e2e_prover_node', () => { +describe('e2e_json_coordination', () => { let ctx: SubsystemsContext; let wallet: AccountWalletWithSecretKey; let recipient: AztecAddress; let contract: StatefulTestContract; - let rollupContract: any; + let rollupContract: GetContractReturnType>; let publicClient: PublicClient; let cc: EthCheatCodes; let publisherAddress: EthAddress; @@ -41,7 +48,14 @@ describe('e2e_prover_node', () => { beforeAll(async () => { logger = createDebugLogger('aztec:prover_coordination:e2e_json_coordination'); - snapshotManager = createSnapshotManager(`prover_coordination/e2e_json_coordination`, process.env.E2E_DATA_PATH); + snapshotManager = createSnapshotManager( + `prover_coordination/e2e_json_coordination`, + process.env.E2E_DATA_PATH, + {}, + { + assumeProvenThrough: undefined, + }, + ); await snapshotManager.snapshot('setup', addAccounts(2, logger), async ({ accountKeys }, ctx) => { const accountManagers = accountKeys.map(ak => getSchnorrAccount(ctx.pxe, ak[0], ak[1], 1)); @@ -79,12 +93,19 @@ describe('e2e_prover_node', () => { }); }); - const expectProofClaimOnL1 = async (quote: EpochProofQuote, proposerAddress: EthAddress) => { - const claimFromContract = await rollupContract.read.proofClaim(); - expect(claimFromContract[0]).toEqual(quote.payload.epochToProve); - expect(claimFromContract[1]).toEqual(BigInt(quote.payload.basisPointFee)); - expect(claimFromContract[2]).toEqual(quote.payload.bondAmount); - expect(claimFromContract[4]).toEqual(proposerAddress.toChecksumString()); + const expectProofClaimOnL1 = async (expected: { + epochToProve: bigint; + basisPointFee: number; + bondAmount: bigint; + proposer: EthAddress; + prover: EthAddress; + }) => { + const [epochToProve, basisPointFee, bondAmount, prover, proposer] = await rollupContract.read.proofClaim(); + expect(epochToProve).toEqual(expected.epochToProve); + expect(basisPointFee).toEqual(BigInt(expected.basisPointFee)); + expect(bondAmount).toEqual(expected.bondAmount); + expect(prover).toEqual(expected.prover.toChecksumString()); + expect(proposer).toEqual(expected.proposer.toChecksumString()); }; const getL1Timestamp = async () => { @@ -110,7 +131,11 @@ describe('e2e_prover_node', () => { }; const getEpochToProve = async () => { - return await rollupContract.read.getEpochToProve(); + return await rollupContract.read.getEpochToProve().catch(e => { + if (e instanceof Error && e.message.includes('NoEpochToProve')) { + return undefined; + } + }); }; const logState = async () => { @@ -130,18 +155,44 @@ describe('e2e_prover_node', () => { await logState(); }; + const makeEpochProofQuote = async ({ + epochToProve, + validUntilSlot, + bondAmount, + prover, + basisPointFee, + signer, + }: { + epochToProve: bigint; + validUntilSlot?: bigint; + bondAmount?: bigint; + prover?: EthAddress; + basisPointFee?: number; + signer?: Secp256k1Signer; + }) => { + signer ??= new Secp256k1Signer(Buffer32.fromBuffer(keccak256(Buffer.from('cow')))); + const quotePayload: EpochProofQuotePayload = new EpochProofQuotePayload( + epochToProve, + validUntilSlot ?? randomBigInt(10000n), + bondAmount ?? randomBigInt(10000n) + 1000n, + prover ?? EthAddress.fromString('0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826'), + basisPointFee ?? randomInt(100), + ); + const digest = await rollupContract.read.quoteToDigest([quotePayload.toViemArgs()]); + return EpochProofQuote.new(Buffer32.fromString(digest), quotePayload, signer); + }; + it('Sequencer selects best valid proving quote for each block', async () => { // We want to create a set of proving quotes, some valid and some invalid // The sequencer should select the cheapest valid quote when it proposes the block // Here we are creating a proof quote for epoch 0, this will NOT get used yet - const quoteForEpoch0 = mockEpochProofQuote( - 0n, // epoch 0 - BigInt(AZTEC_EPOCH_DURATION + 10), // valid until slot 10 into epoch 1 - 10000n, - EthAddress.random(), - 1, - ); + const quoteForEpoch0 = await makeEpochProofQuote({ + epochToProve: 0n, + validUntilSlot: BigInt(AZTEC_EPOCH_DURATION + 10), + bondAmount: 10000n, + basisPointFee: 1, + }); // Send in the quote await ctx.proverNode.sendEpochProofQuote(quoteForEpoch0); @@ -154,16 +205,14 @@ describe('e2e_prover_node', () => { const epoch0BlockNumber = await getPendingBlockNumber(); // Verify that the claim state on L1 is unitialised - const uninitialisedProofClaim = mockEpochProofQuote( - 0n, // epoch 0 - BigInt(0), - 0n, - EthAddress.random(), - 0, - ); - // The rollup contract should have an uninitialised proof claim struct - await expectProofClaimOnL1(uninitialisedProofClaim, EthAddress.ZERO); + await expectProofClaimOnL1({ + epochToProve: 0n, + basisPointFee: 0, + bondAmount: 0n, + prover: EthAddress.ZERO, + proposer: EthAddress.ZERO, + }); // Now go to epoch 1 await advanceToNextEpoch(); @@ -176,7 +225,7 @@ describe('e2e_prover_node', () => { const epoch1BlockNumber = await getPendingBlockNumber(); // Check it was published - await expectProofClaimOnL1(quoteForEpoch0, publisherAddress); + await expectProofClaimOnL1({ ...quoteForEpoch0.payload, proposer: publisherAddress }); // now 'prove' epoch 0 await rollupContract.write.setAssumeProvenThroughBlockNumber([BigInt(epoch0BlockNumber)]); @@ -189,15 +238,37 @@ describe('e2e_prover_node', () => { const currentSlot = await getSlot(); // Now create a number of quotes, some valid some invalid for epoch 1, the lowest priced valid quote should be chosen - const validQuotes = times(3, (i: number) => - mockEpochProofQuote(1n, currentSlot + 2n, 10000n, EthAddress.random(), 10 + i), + const validQuotes = await Promise.all( + times(3, (i: number) => + makeEpochProofQuote({ + epochToProve: 1n, + validUntilSlot: currentSlot + 2n, + bondAmount: 10000n, + basisPointFee: 10 + i, + }), + ), ); - const proofQuoteInvalidSlot = mockEpochProofQuote(1n, 3n, 10000n, EthAddress.random(), 1); + const proofQuoteInvalidSlot = await makeEpochProofQuote({ + epochToProve: 1n, + validUntilSlot: 3n, + bondAmount: 10000n, + basisPointFee: 1, + }); - const proofQuoteInvalidEpoch = mockEpochProofQuote(2n, currentSlot + 4n, 10000n, EthAddress.random(), 2); + const proofQuoteInvalidEpoch = await makeEpochProofQuote({ + epochToProve: 2n, + validUntilSlot: currentSlot + 4n, + bondAmount: 10000n, + basisPointFee: 2, + }); - const proofQuoteInsufficientBond = mockEpochProofQuote(1n, currentSlot + 4n, 0n, EthAddress.random(), 3); + const proofQuoteInsufficientBond = await makeEpochProofQuote({ + epochToProve: 1n, + validUntilSlot: currentSlot + 4n, + bondAmount: 0n, + basisPointFee: 3, + }); const allQuotes = [proofQuoteInvalidSlot, proofQuoteInvalidEpoch, ...validQuotes, proofQuoteInsufficientBond]; @@ -208,12 +279,12 @@ describe('e2e_prover_node', () => { const expectedQuote = validQuotes[0]; - await expectProofClaimOnL1(expectedQuote, publisherAddress); + await expectProofClaimOnL1({ ...expectedQuote.payload, proposer: publisherAddress }); // building another block should succeed, we should not try and submit another quote await contract.methods.create_note(recipient, recipient, 10).send().wait(); - await expectProofClaimOnL1(expectedQuote, publisherAddress); + await expectProofClaimOnL1({ ...expectedQuote.payload, proposer: publisherAddress }); // now 'prove' epoch 1 await rollupContract.write.setAssumeProvenThroughBlockNumber([BigInt(epoch1BlockNumber)]); @@ -225,6 +296,6 @@ describe('e2e_prover_node', () => { await contract.methods.create_note(recipient, recipient, 10).send().wait(); // The quote state on L1 is the same as before - await expectProofClaimOnL1(expectedQuote, publisherAddress); + await expectProofClaimOnL1({ ...expectedQuote.payload, proposer: publisherAddress }); }); }); diff --git a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts index 3f14d4b34c5..bbb2b206b2d 100644 --- a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts +++ b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts @@ -22,8 +22,8 @@ import { sha256ToField } from '@aztec/foundation/crypto'; import { InboxAbi, OutboxAbi, - PortalERC20Abi, - PortalERC20Bytecode, + TestERC20Abi, + TestERC20Bytecode, TokenPortalAbi, TokenPortalBytecode, } from '@aztec/l1-artifacts'; @@ -82,16 +82,13 @@ export async function deployAndInitializeTokenAndBridgeContracts( underlyingERC20: any; }> { if (!underlyingERC20Address) { - underlyingERC20Address = await deployL1Contract( - walletClient, - publicClient, - PortalERC20Abi, - PortalERC20Bytecode, - ).then(({ address }) => address); + underlyingERC20Address = await deployL1Contract(walletClient, publicClient, TestERC20Abi, TestERC20Bytecode).then( + ({ address }) => address, + ); } const underlyingERC20 = getContract({ address: underlyingERC20Address!.toString(), - abi: PortalERC20Abi, + abi: TestERC20Abi, client: walletClient, }); diff --git a/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts b/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts index df75a856c5e..0d61fefecfe 100644 --- a/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts +++ b/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts @@ -8,7 +8,7 @@ import { type Wallet, computeSecretHash, } from '@aztec/aztec.js'; -import { FeeJuicePortalAbi, OutboxAbi, PortalERC20Abi } from '@aztec/l1-artifacts'; +import { FeeJuicePortalAbi, OutboxAbi, TestERC20Abi } from '@aztec/l1-artifacts'; import { FeeJuiceContract } from '@aztec/noir-contracts.js'; import { FeeJuiceAddress } from '@aztec/protocol-contracts/fee-juice'; @@ -68,7 +68,7 @@ export class FeeJuicePortalTestingHarnessFactory { const gasL1 = getContract({ address: feeJuiceAddress.toString(), - abi: PortalERC20Abi, + abi: TestERC20Abi, client: walletClient, }); @@ -125,7 +125,7 @@ export class GasBridgingTestHarness implements IGasBridgingTestHarness { /** Token portal instance. */ public tokenPortal: GetContractReturnType>, /** Underlying token for portal tests. */ - public underlyingERC20: GetContractReturnType>, + public underlyingERC20: GetContractReturnType>, /** Message Bridge Outbox. */ public outbox: GetContractReturnType>, /** Viem Public client instance. */ diff --git a/yarn-project/foundation/src/crypto/keccak/index.ts b/yarn-project/foundation/src/crypto/keccak/index.ts index 940e9d26ff2..4e8ed8d3473 100644 --- a/yarn-project/foundation/src/crypto/keccak/index.ts +++ b/yarn-project/foundation/src/crypto/keccak/index.ts @@ -1,12 +1,17 @@ import { Keccak } from 'sha3'; +import { Buffer32 } from '../../buffer/buffer32.js'; + /** * Computes the Keccak-256 hash of the given input buffer. * * @param input - The input buffer to be hashed. * @returns The computed Keccak-256 hash as a Buffer. */ -export function keccak256(input: Buffer) { +export function keccak256(input: Buffer | Buffer32) { + if (input instanceof Buffer32) { + input = input.buffer; + } const hash = new Keccak(256); return hash.update(input).digest(); } diff --git a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh index ef9c640c9fa..2903b4ca429 100755 --- a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh +++ b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh @@ -15,7 +15,7 @@ CONTRACTS=( "l1-contracts:Outbox" "l1-contracts:Rollup" "l1-contracts:TokenPortal" - "l1-contracts:PortalERC20" + "l1-contracts:TestERC20" "l1-contracts:UniswapPortal" "l1-contracts:IERC20" "l1-contracts:FeeJuicePortal" diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts index 0847e254014..baa5f9e2425 100644 --- a/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts @@ -1,5 +1,6 @@ import { EpochProofQuote, EpochProofQuotePayload } from '@aztec/circuit-types'; import { EthAddress } from '@aztec/circuits.js'; +import { Buffer32 } from '@aztec/foundation/buffer'; import { Secp256k1Signer, randomBigInt, randomInt } from '@aztec/foundation/crypto'; export function makeRandomEpochProofQuotePayload(): EpochProofQuotePayload { @@ -19,7 +20,7 @@ export function makeRandomEpochProofQuote(payload?: EpochProofQuotePayload): { const signer = Secp256k1Signer.random(); return { - quote: EpochProofQuote.new(payload ?? makeRandomEpochProofQuotePayload(), signer), + quote: EpochProofQuote.new(Buffer32.random(), payload ?? makeRandomEpochProofQuotePayload(), signer), signer, }; } diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index c074423b057..99df20363d1 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -28,6 +28,7 @@ import { RollupAbi } from '@aztec/l1-artifacts'; import { type TelemetryClient } from '@aztec/telemetry-client'; import pick from 'lodash.pick'; +import { inspect } from 'util'; import { ContractFunctionRevertedError, type GetContractReturnType, @@ -294,54 +295,6 @@ export class L1Publisher { return false; } - { - const timer = new Timer(); - - // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available - // This means that we can avoid the simulation issues in later checks. - // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which - // make time consistency checks break. - await this.validateBlockForSubmission(block.header, { - digest: digest.toBuffer(), - signatures: attestations ?? [], - }); - - const txHash = await this.sendProposeTx(proposeTxArgs); - - if (!txHash) { - this.log.info(`Failed to publish block ${block.number} to L1`, ctx); - return false; - } - - const receipt = await this.getTransactionReceipt(txHash); - if (!receipt) { - this.log.info(`Failed to get receipt for tx ${txHash}`, ctx); - return false; - } - - // Tx was mined successfully - if (receipt.status) { - const tx = await this.getTransactionStats(txHash); - const stats: L1PublishBlockStats = { - ...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'), - ...pick(tx!, 'calldataGas', 'calldataSize'), - ...block.getStats(), - eventName: 'rollup-published-to-l1', - }; - this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx }); - this.metrics.recordProcessBlockTx(timer.ms(), stats); - - return true; - } - - this.metrics.recordFailedTx('process'); - this.log.error(`Rollup.process tx status failed ${receipt.transactionHash}`, { - ...ctx, - ...receipt, - }); - await this.sleepOrInterrupted(); - } - const timer = new Timer(); // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available @@ -617,6 +570,8 @@ export class L1Publisher { encodedData, L1Publisher.PROPOSE_AND_CLAIM_GAS_GUESS, ); + this.log.info(`ProposeAndClaim`); + this.log.info(inspect(quote.payload)); return await this.rollupContract.write.proposeAndClaim([...args, quote.toViemArgs()], { account: this.account, diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 0195d1afc3a..3e83d8a40c0 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -32,6 +32,8 @@ import { type PublicProcessorFactory } from '@aztec/simulator'; import { Attributes, type TelemetryClient, type Tracer, trackSpan } from '@aztec/telemetry-client'; import { type ValidatorClient } from '@aztec/validator-client'; +import { inspect } from 'util'; + import { type BlockBuilderFactory } from '../block_builder/index.js'; import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js'; import { type L1Publisher } from '../publisher/l1-publisher.js'; @@ -491,6 +493,8 @@ export class Sequencer { const proofQuote = await proofQuotePromise; + this.log.verbose(proofQuote ? `Using proof quote ${inspect(proofQuote.payload)}` : 'No proof quote available'); + try { await this.publishL2Block(block, attestations, txHashes, proofQuote); this.metrics.recordPublishedBlock(workDuration); @@ -562,6 +566,9 @@ export class Sequencer { // Get quotes for the epoch to be proven const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve); this.log.verbose(`Retrieved ${quotes.length} quotes, slot: ${slotNumber}, epoch to prove: ${epochToProve}`); + for (const quote of quotes) { + this.log.verbose(inspect(quote.payload)); + } // ensure these quotes are still valid for the slot and have the contract validate them const validQuotesPromise = Promise.all( quotes.filter(x => x.payload.validUntilSlot >= slotNumber).map(x => this.publisher.validateProofQuote(x)),