diff --git a/l1-contracts/contracts/dev-contracts/test/TransactionValidatorTest.sol b/l1-contracts/contracts/dev-contracts/test/TransactionValidatorTest.sol deleted file mode 100644 index 3ec4ded29..000000000 --- a/l1-contracts/contracts/dev-contracts/test/TransactionValidatorTest.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import "../../zksync/libraries/TransactionValidator.sol"; -import "../../zksync/interfaces/IMailbox.sol"; - -contract TransactionValidatorTest { - function validateL1ToL2Transaction( - IMailbox.L2CanonicalTransaction memory _transaction, - uint256 _priorityTxMaxGasLimit - ) external pure { - TransactionValidator.validateL1ToL2Transaction(_transaction, abi.encode(_transaction), _priorityTxMaxGasLimit); - } - - function validateUpgradeTransaction(IMailbox.L2CanonicalTransaction memory _transaction) external pure { - TransactionValidator.validateUpgradeTransaction(_transaction); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/TransactionValidator/ValidateL1L2Tx.t.sol b/l1-contracts/test/foundry/unit/concrete/TransactionValidator/ValidateL1L2Tx.t.sol new file mode 100644 index 000000000..9d3a35523 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/TransactionValidator/ValidateL1L2Tx.t.sol @@ -0,0 +1,64 @@ +pragma solidity 0.8.20; + +import {TransactionValidatorSharedTest} from "./_TransactionValidator_Shared.t.sol"; +import {IMailbox} from "solpp/zksync/interfaces/IMailbox.sol"; +import {TransactionValidator} from "solpp/zksync/libraries/TransactionValidator.sol"; + +contract ValidateL1L2TxTest is TransactionValidatorSharedTest { + function test_BasicRequestL1L2() public pure { + IMailbox.L2CanonicalTransaction memory testTx = createTestTransaction(); + testTx.gasLimit = 500000; + validateL1ToL2Transaction(testTx, 500000); + } + + function test_RevertWhen_GasLimitDoesntCoverOverhead() public { + IMailbox.L2CanonicalTransaction memory testTx = createTestTransaction(); + // The limit is so low, that it doesn't even cover the overhead + testTx.gasLimit = 0; + vm.expectRevert(bytes("my")); + validateL1ToL2Transaction(testTx, 500000); + } + + function test_RevertWhen_GasLimitHigherThanMax() public { + IMailbox.L2CanonicalTransaction memory testTx = createTestTransaction(); + // We should fail, if user asks for too much gas. + // Notice, that we subtract the transaction overhead costs from the user's gas limit + // before checking that it is below the max gas limit. + uint256 priorityTxMaxGasLimit = 500000; + testTx.gasLimit = priorityTxMaxGasLimit + 1000000; + vm.expectRevert(bytes("ui")); + validateL1ToL2Transaction(testTx, priorityTxMaxGasLimit); + } + + function test_RevertWhen_TooMuchPubdata() public { + IMailbox.L2CanonicalTransaction memory testTx = createTestTransaction(); + // We should fail, if user's transaction could output too much pubdata. + // We can allow only 99k of pubdata (otherwise we'd exceed the ethereum calldata limits). + + uint256 priorityTxMaxGasLimit = 500000; + testTx.gasLimit = priorityTxMaxGasLimit; + // So if the pubdata costs per byte is 1 - then this transaction could produce 500k of pubdata. + // (hypothetically, assuming all the gas was spent on writing). + testTx.gasPerPubdataByteLimit = 1; + vm.expectRevert(bytes("uk")); + validateL1ToL2Transaction(testTx, priorityTxMaxGasLimit); + } + + function test_RevertWhen_BelowMinimumCost() public { + IMailbox.L2CanonicalTransaction memory testTx = createTestTransaction(); + uint256 priorityTxMaxGasLimit = 500000; + testTx.gasLimit = 200000; + vm.expectRevert(bytes("up")); + validateL1ToL2Transaction(testTx, priorityTxMaxGasLimit); + } + + function test_RevertWhen_HugePubdata() public { + IMailbox.L2CanonicalTransaction memory testTx = createTestTransaction(); + uint256 priorityTxMaxGasLimit = 500000; + testTx.gasLimit = 400000; + // Setting huge pubdata limit should cause the panic. + testTx.gasPerPubdataByteLimit = type(uint256).max; + vm.expectRevert(); + validateL1ToL2Transaction(testTx, priorityTxMaxGasLimit); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/TransactionValidator/ValidateUpgradeTransaction.t.sol b/l1-contracts/test/foundry/unit/concrete/TransactionValidator/ValidateUpgradeTransaction.t.sol new file mode 100644 index 000000000..14cd4c0eb --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/TransactionValidator/ValidateUpgradeTransaction.t.sol @@ -0,0 +1,100 @@ +pragma solidity 0.8.20; + +import {TransactionValidatorSharedTest} from "./_TransactionValidator_Shared.t.sol"; +import {IMailbox} from "solpp/zksync/interfaces/IMailbox.sol"; +import {TransactionValidator} from "solpp/zksync/libraries/TransactionValidator.sol"; + +contract ValidateUpgradeTxTest is TransactionValidatorSharedTest { + function test_BasicRequest() public pure { + IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + TransactionValidator.validateUpgradeTransaction(testTx); + } + + function test_RevertWhen_RequestNotFromSystemContract() public { + IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + // only system contracts (address < 2^16) are allowed to send upgrade transactions. + testTx.from = uint256(1000000000); + vm.expectRevert(bytes("ua")); + TransactionValidator.validateUpgradeTransaction(testTx); + } + + function test_RevertWhen_RequestNotToSystemContract() public { + IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + // Now the 'to' address it too large. + testTx.to = uint256(type(uint160).max) + 100; + vm.expectRevert(bytes("ub")); + TransactionValidator.validateUpgradeTransaction(testTx); + } + + function test_RevertWhen_PaymasterIsNotZero() public { + IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + // Paymaster must be 0 - otherwise we revert. + testTx.paymaster = 1; + vm.expectRevert(bytes("uc")); + TransactionValidator.validateUpgradeTransaction(testTx); + } + + function test_RevertWhen_ValueIsNotZero() public { + IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + // Value must be 0 - otherwise we revert. + testTx.value = 1; + vm.expectRevert(bytes("ud")); + TransactionValidator.validateUpgradeTransaction(testTx); + } + + function test_RevertWhen_Reserved0IsNonZero() public { + IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + // reserved 0 must be 0 - otherwise we revert. + testTx.reserved[0] = 1; + vm.expectRevert(bytes("ue")); + TransactionValidator.validateUpgradeTransaction(testTx); + } + + function test_RevertWhen_Reserved1IsTooLarge() public { + IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + // reserved 1 must be a valid address + testTx.reserved[1] = uint256(type(uint160).max) + 100; + vm.expectRevert(bytes("uf")); + TransactionValidator.validateUpgradeTransaction(testTx); + } + + function test_RevertWhen_Reserved2IsNonZero() public { + IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + // reserved 2 must be 0 - otherwise we revert. + testTx.reserved[2] = 1; + vm.expectRevert(bytes("ug")); + TransactionValidator.validateUpgradeTransaction(testTx); + } + + function test_RevertWhen_Reserved3IsNonZero() public { + IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + // reserved 3 be 0 - otherwise we revert. + testTx.reserved[3] = 1; + vm.expectRevert(bytes("uo")); + TransactionValidator.validateUpgradeTransaction(testTx); + } + + function test_RevertWhen_NonZeroSignature() public { + IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + // Signature must be 0 - otherwise we revert. + testTx.signature = bytes("hello"); + vm.expectRevert(bytes("uh")); + TransactionValidator.validateUpgradeTransaction(testTx); + } + + function test_RevertWhen_PaymasterInputNonZero() public { + IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + // PaymasterInput must be 0 - otherwise we revert. + testTx.paymasterInput = bytes("hi"); + vm.expectRevert(bytes("ul")); + TransactionValidator.validateUpgradeTransaction(testTx); + } + + function test_RevertWhen_ReservedDynamicIsNonZero() public { + IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + // ReservedDynamic must be 0 - otherwise we revert. + testTx.reservedDynamic = bytes("something"); + vm.expectRevert(bytes("um")); + TransactionValidator.validateUpgradeTransaction(testTx); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/TransactionValidator/_TransactionValidator_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/TransactionValidator/_TransactionValidator_Shared.t.sol new file mode 100644 index 000000000..cd728d680 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/TransactionValidator/_TransactionValidator_Shared.t.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {IMailbox} from "solpp/zksync/interfaces/IMailbox.sol"; +//import {TransactionValidator} from "solpp/zksync/libraries/TransactionValidator.sol"; +import {TransactionValidator} from "cache/solpp-generated-contracts/zksync/libraries/TransactionValidator.sol"; + +contract TransactionValidatorSharedTest is Test { + constructor() {} + + function createTestTransaction() public pure returns (IMailbox.L2CanonicalTransaction memory testTx) { + testTx = IMailbox.L2CanonicalTransaction({ + txType: 0, + from: uint256(uint160(1_000_000_000)), + to: uint256(uint160(0)), + gasLimit: 500000, + gasPerPubdataByteLimit: 800, + maxFeePerGas: uint256(0), + maxPriorityFeePerGas: uint256(0), + paymaster: uint256(0), + nonce: uint256(0), + value: 0, + reserved: [uint256(0), uint256(0), uint256(0), uint256(0)], + data: new bytes(0), + signature: new bytes(0), + factoryDeps: new uint256[](0), + paymasterInput: new bytes(0), + reservedDynamic: new bytes(0) + }); + } + + function createUpgradeTransaction() public pure returns (IMailbox.L2CanonicalTransaction memory testTx) { + testTx = createTestTransaction(); + testTx.from = uint256(0x8001); + testTx.to = uint256(0x8007); + } + + function validateL1ToL2Transaction( + IMailbox.L2CanonicalTransaction memory _transaction, + uint256 _priorityTxMaxGasLimit + ) public pure { + TransactionValidator.validateL1ToL2Transaction(_transaction, abi.encode(_transaction), _priorityTxMaxGasLimit); + } +} diff --git a/l1-contracts/test/unit_tests/transaction_validator_test.spec.ts b/l1-contracts/test/unit_tests/transaction_validator_test.spec.ts deleted file mode 100644 index a8683c368..000000000 --- a/l1-contracts/test/unit_tests/transaction_validator_test.spec.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { expect } from "chai"; -import * as hardhat from "hardhat"; -import type { TransactionValidatorTest } from "../../typechain"; -import { TransactionValidatorTestFactory } from "../../typechain"; -import { getCallRevertReason } from "./utils"; -import * as ethers from "ethers"; - -describe("TransactionValidator tests", function () { - let tester: TransactionValidatorTest; - before(async () => { - const testerFactory = await hardhat.ethers.getContractFactory("TransactionValidatorTest"); - const testerContract = await testerFactory.deploy(); - tester = TransactionValidatorTestFactory.connect(testerContract.address, testerContract.signer); - }); - - describe("validateL1ToL2Transaction", function () { - it("Should not revert when all parameters are valid", async () => { - await tester.validateL1ToL2Transaction(createTestTransaction({}), 500000); - }); - - it("Should revert when provided gas limit doesnt cover transaction overhead", async () => { - const result = await getCallRevertReason( - tester.validateL1ToL2Transaction( - createTestTransaction({ - gasLimit: 0, - }), - 500000 - ) - ); - expect(result).equal("my"); - }); - - it("Should revert when needed gas is higher than the max", async () => { - const result = await getCallRevertReason(tester.validateL1ToL2Transaction(createTestTransaction({}), 0)); - expect(result).equal("ui"); - }); - - it("Should revert when transaction can output more pubdata than processable", async () => { - const result = await getCallRevertReason( - tester.validateL1ToL2Transaction( - createTestTransaction({ - gasPerPubdataByteLimit: 1, - }), - 500000 - ) - ); - expect(result).equal("uk"); - }); - - it("Should revert when transaction gas doesnt pay the minimum costs", async () => { - const result = await getCallRevertReason( - tester.validateL1ToL2Transaction( - createTestTransaction({ - gasLimit: 200000, - }), - 500000 - ) - ); - expect(result).equal("up"); - }); - }); - - describe("validateUpgradeTransaction", function () { - it("Should not revert when all parameters are valid", async () => { - await tester.validateUpgradeTransaction(createTestTransaction({})); - }); - - it("Should revert when from is too large", async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - from: ethers.BigNumber.from(2).pow(16), - }) - ) - ); - expect(result).equal("ua"); - }); - - it("Should revert when to is too large", async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - to: ethers.BigNumber.from(2).pow(161), - }) - ) - ); - expect(result).equal("ub"); - }); - - it("Should revert when paymaster is non-zero", async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - paymaster: 1, - }) - ) - ); - expect(result).equal("uc"); - }); - - it("Should revert when value is non-zero", async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - value: 1, - }) - ) - ); - expect(result).equal("ud"); - }); - - it("Should revert when reserved[0] is non-zero", async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - reserved: [1, 0, 0, 0], - }) - ) - ); - expect(result).equal("ue"); - }); - - it("Should revert when reserved[1] is too large", async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - reserved: [0, ethers.BigNumber.from(2).pow(161), 0, 0], - }) - ) - ); - expect(result).equal("uf"); - }); - - it("Should revert when reserved[2] is non-zero", async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - reserved: [0, 0, 1, 0], - }) - ) - ); - expect(result).equal("ug"); - }); - - it("Should revert when reserved[3] is non-zero", async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - reserved: [0, 0, 0, 1], - }) - ) - ); - expect(result).equal("uo"); - }); - - it("Should revert when signature has non-zero length", async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - signature: "0xaa", - }) - ) - ); - expect(result).equal("uh"); - }); - - it("Should revert when paymaster input has non-zero length", async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - paymasterInput: "0xaa", - }) - ) - ); - expect(result).equal("ul"); - }); - - it("Should revert when reserved dynamic field has non-zero length", async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - reservedDynamic: "0xaa", - }) - ) - ); - expect(result).equal("um"); - }); - }); -}); - -function createTestTransaction(overrides) { - return Object.assign( - { - txType: 0, - from: ethers.BigNumber.from(2).pow(16).sub(1), - to: 0, - gasLimit: 500000, - gasPerPubdataByteLimit: 800, - maxFeePerGas: 0, - maxPriorityFeePerGas: 0, - paymaster: 0, - nonce: 0, - value: 0, - reserved: [0, 0, 0, 0], - data: "0x", - signature: "0x", - factoryDeps: [], - paymasterInput: "0x", - reservedDynamic: "0x", - }, - overrides - ); -}