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: add transaction filtering #272

Merged
merged 15 commits into from
Mar 22, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.20;

import {ITransactionFilterer} from "../../state-transition/chain-interfaces/ITransactionFilterer.sol";

contract TransactionFiltererFalse is ITransactionFilterer {
// add this to be excluded from coverage report
function test() internal virtual {}

function isTransactionAllowed(
address,
address,
uint256,
uint256,
bytes memory,
address
) external view returns (bool) {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.20;

import {ITransactionFilterer} from "../../state-transition/chain-interfaces/ITransactionFilterer.sol";

contract TransactionFiltererTrue is ITransactionFilterer {
// add this to be excluded from coverage report
function test() internal virtual {}

function isTransactionAllowed(
address,
address,
uint256,
uint256,
bytes memory,
address
) external view returns (bool) {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,6 @@ struct ZkSyncStateTransitionStorage {
/// we multiply by the nominator, and divide by the denominator
uint128 baseTokenGasPriceMultiplierNominator;
uint128 baseTokenGasPriceMultiplierDenominator;
/// @dev The optional address of the contract that has to be used for transaction filtering/whitelisting
address transactionFilterer;
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ contract AdminFacet is ZkSyncStateTransitionBase, IAdmin {
emit ValidiumModeStatusUpdate(_validiumMode);
}

function setTransactionFilterer(address _transactionFilterer) external onlyAdmin {
address oldTransactionFilterer = s.transactionFilterer;
s.transactionFilterer = _transactionFilterer;
emit NewTransactionFilterer(oldTransactionFilterer, _transactionFilterer);
}

/*//////////////////////////////////////////////////////////////
UPGRADE EXECUTION
//////////////////////////////////////////////////////////////*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity 0.8.20;
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

import {IMailbox} from "../../chain-interfaces/IMailbox.sol";
import {ITransactionFilterer} from "../../chain-interfaces/ITransactionFilterer.sol";
import {Merkle} from "../../libraries/Merkle.sol";
import {PriorityQueue, PriorityOperation} from "../../libraries/PriorityQueue.sol";
import {TransactionValidator} from "../../libraries/TransactionValidator.sol";
Expand Down Expand Up @@ -227,6 +228,20 @@ contract MailboxFacet is ZkSyncStateTransitionBase, IMailbox {
function _requestL2TransactionSender(
BridgehubL2TransactionRequest memory _request
) internal nonReentrant returns (bytes32 canonicalTxHash) {
// Check that the transaction is allowed by the filterer (if the filterer is set).
if (s.transactionFilterer != address(0)) {
require(
ITransactionFilterer(s.transactionFilterer).isTransactionAllowed({
sender: _request.sender,
contractL2: _request.contractL2,
mintValue: _request.mintValue,
l2Value: _request.l2Value,
l2Calldata: _request.l2Calldata,
refundRecipient: _request.refundRecipient
}),
"tf"
);
}
// Change the sender address if it is a smart contract to prevent address collision between L1 and L2.
// Please note, currently zkSync address derivation is different from Ethereum one, but it may be changed in the future.
address l2Sender = _request.sender;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ interface IAdmin is IZkSyncStateTransitionBase {
/// @notice Used to set to validium directly after genesis
function setValidiumMode(PubdataPricingMode _validiumMode) external;

/// @notice Set the transaction filterer
function setTransactionFilterer(address _transactionFilterer) external;

function upgradeChainFromVersion(uint256 _protocolVersion, Diamond.DiamondCutData calldata _cutData) external;

/// @notice Executes a proposed governor upgrade
Expand Down Expand Up @@ -79,6 +82,9 @@ interface IAdmin is IZkSyncStateTransitionBase {
/// @notice Validium mode status changed
event ValidiumModeStatusUpdate(PubdataPricingMode validiumMode);

/// @notice The transaction filterer has been updated
event NewTransactionFilterer(address oldTransactionFilterer, address newTransactionFilterer);

/// @notice BaseToken multiplier for L1->L2 transactions changed
event NewBaseTokenMultiplier(
uint128 oldNominator,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.20;

/// @title The interface of the L1 -> L2 transaction filterer.
/// @author Matter Labs
/// @custom:security-contact [email protected]
interface ITransactionFilterer {
/// @notice Check if the transaction is allowed
/// @param sender The sender of the transaction
/// @param contractL2 The L2 receiver address
/// @param mintValue The value of the L1 transaction
/// @param l2Value The msg.value of the L2 transaction
/// @param l2Calldata The calldata of the L2 transaction
/// @param refundRecipient The address to refund the excess value
/// @return Whether the transaction is allowed
function isTransactionAllowed(
address sender,
address contractL2,
uint256 mintValue,
uint256 l2Value,
bytes memory l2Calldata,
address refundRecipient
) external view returns (bool);
}
29 changes: 28 additions & 1 deletion l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {IVerifier, VerifierParams} from "solpp/state-transition/chain-deps/ZkSyn
import {FeeParams, PubdataPricingMode} from "solpp/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol";
import {InitializeData, InitializeDataNewChain} from "solpp/state-transition/chain-interfaces/IDiamondInit.sol";
import {IExecutor, SystemLogKey} from "solpp/state-transition/chain-interfaces/IExecutor.sol";
import {L2CanonicalTransaction} from "solpp/common/Messaging.sol";

bytes32 constant DEFAULT_L2_LOGS_TREE_ROOT_HASH = 0x0000000000000000000000000000000000000000000000000000000000000000;
address constant L2_SYSTEM_CONTEXT_ADDRESS = 0x000000000000000000000000000000000000800B;
Expand Down Expand Up @@ -233,7 +234,7 @@ library Utils {
}

function getUtilsFacetSelectors() public pure returns (bytes4[] memory) {
bytes4[] memory selectors = new bytes4[](36);
bytes4[] memory selectors = new bytes4[](38);
selectors[0] = UtilsFacet.util_setChainId.selector;
selectors[1] = UtilsFacet.util_getChainId.selector;
selectors[2] = UtilsFacet.util_setBridgehub.selector;
Expand Down Expand Up @@ -270,6 +271,8 @@ library Utils {
selectors[33] = UtilsFacet.util_getProtocolVersion.selector;
selectors[34] = UtilsFacet.util_setIsFrozen.selector;
selectors[35] = UtilsFacet.util_getIsFrozen.selector;
selectors[36] = UtilsFacet.util_setTransactionFilterer.selector;
selectors[37] = UtilsFacet.util_setBaseTokenGasPriceMultiplierDenominator.selector;
return selectors;
}

Expand Down Expand Up @@ -395,6 +398,30 @@ library Utils {
return address(diamondProxy);
}

function makeEmptyL2CanonicalTransaction() public returns (L2CanonicalTransaction memory) {
uint256[4] memory reserved;
uint256[] memory factoryDeps = new uint256[](1);
return
L2CanonicalTransaction({
txType: 0,
from: 0,
to: 0,
gasLimit: 0,
gasPerPubdataByteLimit: 0,
maxFeePerGas: 0,
maxPriorityFeePerGas: 0,
paymaster: 0,
nonce: 0,
value: 0,
reserved: reserved,
data: "",
signature: "",
factoryDeps: factoryDeps,
paymasterInput: "",
reservedDynamic: ""
});
}

function createBatchCommitment(
IExecutor.CommitBatchInfo calldata _newBatchData,
bytes32 _stateDiffHash,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ contract UtilsFacet is ZkSyncStateTransitionBase {
return s.validators[_validator];
}

function util_setTransactionFilterer(address _filterer) external {
s.transactionFilterer = _filterer;
}

function util_setBaseTokenGasPriceMultiplierDenominator(uint128 _denominator) external {
s.baseTokenGasPriceMultiplierDenominator = _denominator;
}

function util_setZkPorterAvailability(bool _available) external {
s.zkPorterIsAvailable = _available;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.20;

import {AdminTest} from "./_Admin_Shared.t.sol";

contract SetTransactionFiltererTest is AdminTest {
event NewTransactionFilterer(address oldTransactionFilterer, address newTransactionFilterer);

function test_initialFilterer() public {
address admin = utilsFacet.util_getAdmin();
address transactionFilterer = makeAddr("transactionFilterer");

vm.expectEmit(true, true, true, true, address(adminFacet));
emit NewTransactionFilterer(address(0), transactionFilterer);

vm.startPrank(admin);
adminFacet.setTransactionFilterer(transactionFilterer);
}

function test_replaceFilterer() public {
address admin = utilsFacet.util_getAdmin();
address f1 = makeAddr("f1");
address f2 = makeAddr("f2");
utilsFacet.util_setTransactionFilterer(f1);

vm.expectEmit(true, true, true, true, address(adminFacet));
emit NewTransactionFilterer(f1, f2);

vm.startPrank(admin);
adminFacet.setTransactionFilterer(f2);
}

function test_revertWhen_notAdmin() public {
address transactionFilterer = makeAddr("transactionFilterer");

vm.expectRevert("StateTransition Chain: not admin");
vm.startPrank(makeAddr("nonAdmin"));
adminFacet.setTransactionFilterer(transactionFilterer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ contract AdminTest is Test {
UtilsFacet internal utilsFacet;

function getAdminSelectors() public pure returns (bytes4[] memory) {
bytes4[] memory selectors = new bytes4[](11);
bytes4[] memory selectors = new bytes4[](12);
selectors[0] = IAdmin.setPendingAdmin.selector;
selectors[1] = IAdmin.acceptAdmin.selector;
selectors[2] = IAdmin.setValidator.selector;
Expand All @@ -27,6 +27,7 @@ contract AdminTest is Test {
selectors[8] = IAdmin.executeUpgrade.selector;
selectors[9] = IAdmin.freezeDiamond.selector;
selectors[10] = IAdmin.unfreezeDiamond.selector;
selectors[11] = IAdmin.setTransactionFilterer.selector;
return selectors;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.20;

import {MailboxTest} from "./_Mailbox_Shared.t.sol";
import {BridgehubL2TransactionRequest} from "solpp/common/Messaging.sol";
import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "solpp/common/Config.sol";
import {TransactionFiltererTrue} from "solpp/dev-contracts/test/DummyTransactionFiltererTrue.sol";
import {TransactionFiltererFalse} from "solpp/dev-contracts/test/DummyTransactionFiltererFalse.sol";

contract BridgehubRequestL2TransactionTest is MailboxTest {
function test_successWithoutFilterer() public {
address bridgehub = makeAddr("bridgehub");

utilsFacet.util_setBridgehub(bridgehub);
utilsFacet.util_setBaseTokenGasPriceMultiplierDenominator(1);
utilsFacet.util_setPriorityTxMaxGasLimit(100000000);

BridgehubL2TransactionRequest memory req = getBridgehubRequestL2TransactionRequest();

vm.deal(bridgehub, 100 ether);
vm.prank(address(bridgehub));
bytes32 canonicalTxHash = mailboxFacet.bridgehubRequestL2Transaction{value: 10 ether}(req);
assertTrue(canonicalTxHash != bytes32(0), "canonicalTxHash should not be 0");
}

function test_successWithFilterer() public {
address bridgehub = makeAddr("bridgehub");
TransactionFiltererTrue tf = new TransactionFiltererTrue();

utilsFacet.util_setBridgehub(bridgehub);
utilsFacet.util_setTransactionFilterer(address(tf));
utilsFacet.util_setBaseTokenGasPriceMultiplierDenominator(1);
utilsFacet.util_setPriorityTxMaxGasLimit(100000000);

BridgehubL2TransactionRequest memory req = getBridgehubRequestL2TransactionRequest();

vm.deal(bridgehub, 100 ether);
vm.prank(address(bridgehub));
bytes32 canonicalTxHash = mailboxFacet.bridgehubRequestL2Transaction{value: 10 ether}(req);
assertTrue(canonicalTxHash != bytes32(0), "canonicalTxHash should not be 0");
}

function test_revertWhen_FalseFilterer() public {
address bridgehub = makeAddr("bridgehub");
TransactionFiltererFalse tf = new TransactionFiltererFalse();

utilsFacet.util_setBridgehub(bridgehub);
utilsFacet.util_setTransactionFilterer(address(tf));
utilsFacet.util_setBaseTokenGasPriceMultiplierDenominator(1);
utilsFacet.util_setPriorityTxMaxGasLimit(100000000);

BridgehubL2TransactionRequest memory req = getBridgehubRequestL2TransactionRequest();

vm.deal(bridgehub, 100 ether);
vm.prank(address(bridgehub));
vm.expectRevert(bytes("tf"));
mailboxFacet.bridgehubRequestL2Transaction{value: 10 ether}(req);
}

function getBridgehubRequestL2TransactionRequest() private returns (BridgehubL2TransactionRequest memory req) {
bytes[] memory factoryDeps = new bytes[](1);
factoryDeps[0] = "11111111111111111111111111111111";

req = BridgehubL2TransactionRequest({
sender: sender,
contractL2: makeAddr("contractL2"),
mintValue: 2 ether,
l2Value: 10000,
l2Calldata: "",
l2GasLimit: 10000000,
l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA,
factoryDeps: factoryDeps,
refundRecipient: sender
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import {Test} from "forge-std/Test.sol";

import {Utils} from "foundry-test/unit/concrete/Utils/Utils.sol";
import {UtilsFacet} from "foundry-test/unit/concrete/Utils/UtilsFacet.sol";

import {MailboxFacet} from "solpp/state-transition/chain-deps/facets/Mailbox.sol";
import {Diamond} from "solpp/state-transition/libraries/Diamond.sol";
import {IMailbox} from "solpp/state-transition/chain-interfaces/IMailbox.sol";

contract MailboxTest is Test {
IMailbox internal mailboxFacet;
UtilsFacet internal utilsFacet;
address sender;

function getMailboxSelectors() public pure returns (bytes4[] memory) {
bytes4[] memory selectors = new bytes4[](1);
selectors[0] = IMailbox.bridgehubRequestL2Transaction.selector;
return selectors;
}

function setUp() public virtual {
sender = makeAddr("sender");
vm.deal(sender, 100 ether);

Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](2);
facetCuts[0] = Diamond.FacetCut({
facet: address(new MailboxFacet()),
action: Diamond.Action.Add,
isFreezable: true,
selectors: getMailboxSelectors()
});
facetCuts[1] = Diamond.FacetCut({
facet: address(new UtilsFacet()),
action: Diamond.Action.Add,
isFreezable: true,
selectors: Utils.getUtilsFacetSelectors()
});

address diamondProxy = Utils.makeDiamondProxy(facetCuts);
mailboxFacet = IMailbox(diamondProxy);
utilsFacet = UtilsFacet(diamondProxy);
}

// add this to be excluded from coverage report
function test() internal virtual {}
}
Loading