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)),