Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fee pricing to 0 for old instances #9296

Merged
merged 2 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 33 additions & 23 deletions l1-contracts/src/core/FeeJuicePortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,24 @@ import {Errors} from "@aztec/core/libraries/Errors.sol";
import {Hash} from "@aztec/core/libraries/crypto/Hash.sol";
import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol";

import {Ownable} from "@oz/access/Ownable.sol";

contract FeeJuicePortal is IFeeJuicePortal, Ownable {
contract FeeJuicePortal is IFeeJuicePortal {
using SafeERC20 for IERC20;

IRegistry public registry;
IERC20 public underlying;
bytes32 public l2TokenAddress;
IRegistry public immutable REGISTRY;
IERC20 public immutable UNDERLYING;
bytes32 public immutable L2_TOKEN_ADDRESS;

constructor(address _owner, address _registry, address _underlying, bytes32 _l2TokenAddress)
Ownable(_owner)
{
bool public initialized;

constructor(address _registry, address _underlying, bytes32 _l2TokenAddress) {
require(
_registry != address(0) && _underlying != address(0) && _l2TokenAddress != 0,
Errors.FeeJuicePortal__InvalidInitialization()
);

registry = IRegistry(_registry);
underlying = IERC20(_underlying);
l2TokenAddress = _l2TokenAddress;
REGISTRY = IRegistry(_registry);
UNDERLYING = IERC20(_underlying);
L2_TOKEN_ADDRESS = _l2TokenAddress;
}

/**
Expand All @@ -44,16 +42,16 @@ contract FeeJuicePortal is IFeeJuicePortal, Ownable {
* @dev Must be funded with FEE_JUICE_INITIAL_MINT tokens before initialization to
* ensure that the L2 contract is funded and able to pay for its deployment.
*/
function initialize() external override(IFeeJuicePortal) onlyOwner {
require(owner() != address(0), Errors.FeeJuicePortal__AlreadyInitialized());
function initialize() external override(IFeeJuicePortal) {
require(!initialized, Errors.FeeJuicePortal__AlreadyInitialized());

uint256 balance = underlying.balanceOf(address(this));
uint256 balance = UNDERLYING.balanceOf(address(this));
if (balance < Constants.FEE_JUICE_INITIAL_MINT) {
underlying.safeTransferFrom(
UNDERLYING.safeTransferFrom(
msg.sender, address(this), Constants.FEE_JUICE_INITIAL_MINT - balance
);
}
_transferOwnership(address(0));
initialized = true;
}

/**
Expand All @@ -69,18 +67,24 @@ contract FeeJuicePortal is IFeeJuicePortal, Ownable {
returns (bytes32)
{
// Preamble
IInbox inbox = IRollup(registry.getRollup()).INBOX();
DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2TokenAddress, 1);
address rollup = canonicalRollup();
uint256 version = REGISTRY.getVersionFor(rollup);
IInbox inbox = IRollup(rollup).INBOX();
DataStructures.L2Actor memory actor = DataStructures.L2Actor(L2_TOKEN_ADDRESS, version);

// Hash the message content to be reconstructed in the receiving contract
bytes32 contentHash =
Hash.sha256ToField(abi.encodeWithSignature("claim(bytes32,uint256)", _to, _amount));

// Hold the tokens in the portal
underlying.safeTransferFrom(msg.sender, address(this), _amount);
UNDERLYING.safeTransferFrom(msg.sender, address(this), _amount);

// Send message to rollup
return inbox.sendL2Message(actor, contentHash, _secretHash);
bytes32 key = inbox.sendL2Message(actor, contentHash, _secretHash);

emit DepositToAztecPublic(_to, _amount, _secretHash, key);

return key;
}

/**
Expand All @@ -94,7 +98,13 @@ contract FeeJuicePortal is IFeeJuicePortal, Ownable {
* @param _amount - The amount to pay them
*/
function distributeFees(address _to, uint256 _amount) external override(IFeeJuicePortal) {
require(msg.sender == registry.getRollup(), Errors.FeeJuicePortal__Unauthorized());
underlying.safeTransfer(_to, _amount);
require(msg.sender == canonicalRollup(), Errors.FeeJuicePortal__Unauthorized());
UNDERLYING.safeTransfer(_to, _amount);

emit FeesDistributed(_to, _amount);
}

function canonicalRollup() public view returns (address) {
return REGISTRY.getRollup();
}
}
26 changes: 16 additions & 10 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
) Leonidas(_ares) {
epochProofVerifier = new MockVerifier();
FEE_JUICE_PORTAL = _fpcJuicePortal;
PROOF_COMMITMENT_ESCROW = new ProofCommitmentEscrow(_fpcJuicePortal.underlying(), address(this));
PROOF_COMMITMENT_ESCROW = new ProofCommitmentEscrow(_fpcJuicePortal.UNDERLYING(), address(this));
INBOX = IInbox(address(new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT)));
OUTBOX = IOutbox(address(new Outbox(address(this))));
vkTreeRoot = _vkTreeRoot;
Expand Down Expand Up @@ -260,15 +260,15 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {

tips.provenBlockNumber = endBlockNumber;

for (uint256 i = 0; i < Constants.AZTEC_EPOCH_DURATION; i++) {
address coinbase = address(uint160(uint256(publicInputs[9 + i * 2])));
uint256 fees = uint256(publicInputs[10 + i * 2]);

if (coinbase != address(0) && fees > 0) {
// @note This will currently fail if there are insufficient funds in the bridge
// which WILL happen for the old version after an upgrade where the bridge follow.
// Consider allowing a failure. See #7938.
FEE_JUICE_PORTAL.distributeFees(coinbase, fees);
// @note Only if the rollup is the canonical will it be able to meaningfully claim fees
// Otherwise, the fees are unbacked #7938.
if (address(this) == FEE_JUICE_PORTAL.canonicalRollup()) {
for (uint256 i = 0; i < Constants.AZTEC_EPOCH_DURATION; i++) {
address coinbase = address(uint160(uint256(publicInputs[9 + i * 2])));
uint256 fees = uint256(publicInputs[10 + i * 2]);
if (coinbase != address(0) && fees > 0) {
FEE_JUICE_PORTAL.distributeFees(coinbase, fees);
}
}
}

Expand Down Expand Up @@ -921,5 +921,11 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
_flags.ignoreDA || _header.contentCommitment.txsEffectsHash == _txsEffectsHash,
Errors.Rollup__UnavailableTxs(_header.contentCommitment.txsEffectsHash)
);

// If not canonical rollup, require that the fees are zero
if (address(this) != FEE_JUICE_PORTAL.canonicalRollup()) {
require(_header.globalVariables.gasFees.feePerDaGas == 0, Errors.Rollup__NonZeroDaFee());
require(_header.globalVariables.gasFees.feePerL2Gas == 0, Errors.Rollup__NonZeroL2Fee());
}
}
}
10 changes: 9 additions & 1 deletion l1-contracts/src/core/interfaces/IFeeJuicePortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@
pragma solidity >=0.8.27;

import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol";

interface IFeeJuicePortal {
event DepositToAztecPublic(bytes32 indexed to, uint256 amount, bytes32 secretHash, bytes32 key);
event FeesDistributed(address indexed to, uint256 amount);

function initialize() external;
function distributeFees(address _to, uint256 _amount) external;
function depositToAztecPublic(bytes32 _to, uint256 _amount, bytes32 _secretHash)
external
returns (bytes32);
function underlying() external view returns (IERC20);
function canonicalRollup() external view returns (address);

function UNDERLYING() external view returns (IERC20);
function L2_TOKEN_ADDRESS() external view returns (bytes32);
function REGISTRY() external view returns (IRegistry);
}
2 changes: 2 additions & 0 deletions l1-contracts/src/core/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ library Errors {
error Rollup__TimestampTooOld(); // 0x72ed9c81
error Rollup__TryingToProveNonExistingBlock(); // 0x34ef4954
error Rollup__UnavailableTxs(bytes32 txsHash); // 0x414906c3
error Rollup__NonZeroDaFee();
error Rollup__NonZeroL2Fee();

//TxsDecoder
error TxsDecoder__InvalidLogsLength(uint256 expected, uint256 actual); // 0x829ca981
Expand Down
11 changes: 9 additions & 2 deletions l1-contracts/src/mock/MockFeeJuicePortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ pragma solidity >=0.8.27;
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol";
import {TestERC20} from "@aztec/mock/TestERC20.sol";
import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol";

contract MockFeeJuicePortal is IFeeJuicePortal {
IERC20 public underlying;
IERC20 public immutable UNDERLYING;
bytes32 public constant L2_TOKEN_ADDRESS = bytes32(0);
IRegistry public constant REGISTRY = IRegistry(address(0));

constructor() {
underlying = new TestERC20();
UNDERLYING = new TestERC20();
}

function initialize() external override {}
Expand All @@ -20,4 +23,8 @@ contract MockFeeJuicePortal is IFeeJuicePortal {
function depositToAztecPublic(bytes32, uint256, bytes32) external pure override returns (bytes32) {
return bytes32(0);
}

function canonicalRollup() external pure override returns (address) {
return address(0);
}
}
39 changes: 38 additions & 1 deletion l1-contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,10 @@ contract RollupTest is DecoderBase {
registry = new Registry(address(this));
testERC20 = new TestERC20();
feeJuicePortal = new FeeJuicePortal(
address(this), address(registry), address(testERC20), bytes32(Constants.FEE_JUICE_ADDRESS)
address(registry), address(testERC20), bytes32(Constants.FEE_JUICE_ADDRESS)
);
testERC20.mint(address(feeJuicePortal), Constants.FEE_JUICE_INITIAL_MINT);
feeJuicePortal.initialize();
rollup = new Rollup(feeJuicePortal, bytes32(0), bytes32(0), address(this), new address[](0));
inbox = Inbox(address(rollup.INBOX()));
outbox = Outbox(address(rollup.OUTBOX()));
Expand Down Expand Up @@ -500,6 +501,42 @@ contract RollupTest is DecoderBase {
assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number");
}

function testNonZeroDaFee() public setUpFor("mixed_block_1") {
registry.upgrade(address(0xbeef));

DecoderBase.Full memory full = load("mixed_block_1");
DecoderBase.Data memory data = full.block;
bytes memory header = data.header;
assembly {
mstore(add(header, add(0x20, 0x0208)), 1)
}
bytes32[] memory txHashes = new bytes32[](0);

// We jump to the time of the block. (unless it is in the past)
vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp));

vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NonZeroDaFee.selector));
rollup.propose(header, data.archive, data.blockHash, txHashes, signatures, data.body);
}

function testNonZeroL2Fee() public setUpFor("mixed_block_1") {
registry.upgrade(address(0xbeef));

DecoderBase.Full memory full = load("mixed_block_1");
DecoderBase.Data memory data = full.block;
bytes memory header = data.header;
assembly {
mstore(add(header, add(0x20, 0x0228)), 1)
}
bytes32[] memory txHashes = new bytes32[](0);

// We jump to the time of the block. (unless it is in the past)
vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp));

vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NonZeroL2Fee.selector));
rollup.propose(header, data.archive, data.blockHash, txHashes, signatures, data.body);
}

function testBlockFee() public setUpFor("mixed_block_1") {
uint256 feeAmount = 2e18;

Expand Down
104 changes: 104 additions & 0 deletions l1-contracts/test/fee_portal/depositToAztecPublic.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.27;

import {Test} from "forge-std/Test.sol";
import {Registry} from "@aztec/governance/Registry.sol";
import {TestERC20} from "@aztec/mock/TestERC20.sol";
import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol";
import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol";
import {Constants} from "@aztec/core/libraries/ConstantsGen.sol";
import {IERC20Errors} from "@oz/interfaces/draft-IERC6093.sol";
import {Rollup} from "@aztec/core/Rollup.sol";
import {DataStructures} from "@aztec/core/libraries/DataStructures.sol";
import {Hash} from "@aztec/core/libraries/crypto/Hash.sol";
import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol";
import {Inbox} from "@aztec/core/messagebridge/Inbox.sol";

contract DepositToAztecPublic is Test {
using Hash for DataStructures.L1ToL2Msg;

address internal constant OWNER = address(0x1);
Registry internal registry;
TestERC20 internal token;
FeeJuicePortal internal feeJuicePortal;
Rollup internal rollup;

function setUp() public {
registry = new Registry(OWNER);
token = new TestERC20();
feeJuicePortal =
new FeeJuicePortal(address(registry), address(token), bytes32(Constants.FEE_JUICE_ADDRESS));

token.mint(address(feeJuicePortal), Constants.FEE_JUICE_INITIAL_MINT);
feeJuicePortal.initialize();

rollup = new Rollup(feeJuicePortal, bytes32(0), bytes32(0), address(this), new address[](0));

vm.prank(OWNER);
registry.upgrade(address(rollup));
}

function test_RevertGiven_InsufficientBalance() external {
// it should revert
vm.expectRevert(
abi.encodeWithSelector(
IERC20Errors.ERC20InsufficientAllowance.selector, address(feeJuicePortal), 0, 1
)
);
feeJuicePortal.depositToAztecPublic(bytes32(0x0), 1, bytes32(0x0));

token.approve(address(feeJuicePortal), 1);
vm.expectRevert(
abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, address(this), 0, 1)
);
feeJuicePortal.depositToAztecPublic(bytes32(0x0), 1, bytes32(0x0));
}

function test_GivenSufficientBalance(uint256 _numberOfRollups) external {
// it should create a message for the newest version
// it should transfer the tokens to the portal
// it should insert the message into the newest inbox
// it should emit a {DepositToAztecPublic} event
// it should return the key

uint256 numberOfRollups = bound(_numberOfRollups, 1, 5);
for (uint256 i = 0; i < numberOfRollups; i++) {
Rollup freshRollup =
new Rollup(feeJuicePortal, bytes32(0), bytes32(0), address(this), new address[](0));
vm.prank(OWNER);
registry.upgrade(address(freshRollup));
}

assertNotEq(registry.getRollup(), address(rollup));

bytes32 to = bytes32(0x0);
bytes32 secretHash = bytes32(uint256(0x01));
uint256 amount = 100 ether;

DataStructures.L1ToL2Msg memory message = DataStructures.L1ToL2Msg({
sender: DataStructures.L1Actor(address(feeJuicePortal), block.chainid),
recipient: DataStructures.L2Actor(feeJuicePortal.L2_TOKEN_ADDRESS(), 1 + numberOfRollups),
content: Hash.sha256ToField(abi.encodeWithSignature("claim(bytes32,uint256)", to, amount)),
secretHash: secretHash
});

bytes32 expectedKey = message.sha256ToField();

token.mint(address(this), amount);
token.approve(address(feeJuicePortal), amount);

Inbox inbox = Inbox(address(Rollup(address(registry.getRollup())).INBOX()));
assertEq(inbox.totalMessagesInserted(), 0);
uint256 index = 2 ** Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT;

vm.expectEmit(true, true, true, true, address(inbox));
emit IInbox.MessageSent(2, index, expectedKey);
vm.expectEmit(true, true, true, true, address(feeJuicePortal));
emit IFeeJuicePortal.DepositToAztecPublic(to, amount, secretHash, expectedKey);

bytes32 key = feeJuicePortal.depositToAztecPublic(to, amount, secretHash);

assertEq(inbox.totalMessagesInserted(), 1);
assertEq(key, expectedKey);
}
}
9 changes: 9 additions & 0 deletions l1-contracts/test/fee_portal/depositToAztecPublic.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
DepositToAztecPublic
├── given insufficient balance
│ └── it should revert
└── given sufficient balance
├── it should create a message for the newest version
├── it should transfer the tokens to the portal
├── it should insert the message into the newest inbox
├── it should emit a {DepositToAztecPublic} event
└── it should return the key
Loading
Loading