Skip to content

Commit

Permalink
feat: prover escrow and 712-signed quotes (#8877)
Browse files Browse the repository at this point in the history
Fix #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.
  • Loading branch information
just-mitch authored Oct 1, 2024
1 parent 989eb08 commit 2f1d19a
Show file tree
Hide file tree
Showing 44 changed files with 923 additions and 395 deletions.
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -171,5 +171,5 @@
},
"files.trimTrailingWhitespace": true,
"cmake.sourceDirectory": "${workspaceFolder}/barretenberg/cpp",
"typescript.tsserver.maxTsServerMemory": 4096,
"typescript.tsserver.maxTsServerMemory": 4096
}
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
47 changes: 12 additions & 35 deletions l1-contracts/.solhint.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand All @@ -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",
Expand All @@ -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"]
}
}
}
146 changes: 146 additions & 0 deletions l1-contracts/src/core/ProofCommitmentEscrow.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
28 changes: 21 additions & 7 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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";
Expand All @@ -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;

Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
{
Expand All @@ -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,
Expand Down Expand Up @@ -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();
Expand Down
16 changes: 13 additions & 3 deletions l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Loading

0 comments on commit 2f1d19a

Please sign in to comment.