Skip to content

Commit

Permalink
feat: add transaction filtering (#272)
Browse files Browse the repository at this point in the history
  • Loading branch information
dimazhornyk authored Mar 22, 2024
1 parent 1e50836 commit cfc96e7
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 2 deletions.
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
8 changes: 8 additions & 0 deletions l1-contracts/test/foundry/unit/concrete/Utils/UtilsFacet.sol
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 {}
}

0 comments on commit cfc96e7

Please sign in to comment.