Skip to content

Commit

Permalink
test: unit tests for Batch
Browse files Browse the repository at this point in the history
  • Loading branch information
smol-ninja committed Dec 23, 2024
1 parent 17dc651 commit 1049ab8
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 68 deletions.
69 changes: 1 addition & 68 deletions tests/integration/concrete/batch/batch.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,9 @@ import { Integration_Test } from "../../Integration.t.sol";

contract Batch_Integration_Concrete_Test is Integration_Test {
/*//////////////////////////////////////////////////////////////////////////
REVERT
BATCH + LOCKUP
//////////////////////////////////////////////////////////////////////////*/

/// @dev The batch call includes ETH and a non-payable function.
function test_RevertWhen_ETHWithNonPayable() external {
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeCall(lockup.isCancelable, (defaultStreamId));

vm.expectRevert();
lockup.batch{ value: 1 wei }(calls);
}

/// @dev The batch call cancels a non-cancelable stream.
function test_RevertWhen_LockupThrows() external {
bytes[] memory calls = new bytes[](2);
Expand All @@ -32,29 +23,6 @@ contract Batch_Integration_Concrete_Test is Integration_Test {
lockup.batch(calls);
}

/// @dev The batch call includes a non-existent function.
function test_RevertWhen_NonExistentFunction() external {
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeWithSignature("nonExistentFunction()");

vm.expectRevert();
lockup.batch(calls);
}

/// @dev The batch call reverts with a string reason.
function test_RevertWhen_StringReason() external {
bytes[] memory calls = new bytes[](2);
calls[0] = abi.encodeCall(lockup.cancel, (defaultStreamId));
calls[1] = abi.encodeCall(lockup.cancel, (recipientRevertStreamId));

vm.expectRevert("You shall not pass");
lockup.batch(calls);
}

/*//////////////////////////////////////////////////////////////////////////
RETURN
//////////////////////////////////////////////////////////////////////////*/

/// @dev The batch call includes:
/// - Returning state changing functions
/// - Non-returning state changing functions
Expand Down Expand Up @@ -90,41 +58,6 @@ contract Batch_Integration_Concrete_Test is Integration_Test {
assertEq(results[5], hex"");
}

/// @dev The batch call includes:
/// - ETH value
/// - Returning state changing functions
/// - Non-returning state changing functions
function test_BatchPayable_StateChangingFunctions() external {
uint256 expectedNextStreamId = lockup.nextStreamId();
uint256 initialEthBalance = address(lockup).balance;
vm.warp(defaults.WARP_26_PERCENT());

bytes[] memory calls = new bytes[](4);
// It will return the withdrawn amount.
calls[0] = abi.encodeCall(lockup.withdrawMax, (notCancelableStreamId, users.recipient));
// It will not return anything.
calls[1] = abi.encodeCall(lockup.cancel, (defaultStreamId));
// It will return the stream ID created.
calls[2] = abi.encodeCall(
lockup.createWithTimestampsLL,
(defaults.createWithTimestamps(), defaults.unlockAmounts(), defaults.CLIFF_TIME())
);
// It will not return anything.
calls[3] = abi.encodeCall(lockup.renounce, (notTransferableStreamId));

bytes[] memory results = lockup.batch{ value: 1 wei }(calls);
assertEq(results.length, 4);
assertEq(abi.decode(results[0], (uint128)), defaults.WITHDRAW_AMOUNT());
assertEq(results[1], hex"");
assertEq(abi.decode(results[2], (uint256)), expectedNextStreamId);
assertEq(results[3], hex"");
assertEq(address(lockup).balance, initialEthBalance + 1 wei);
}

/*//////////////////////////////////////////////////////////////////////////
BATCH + LOCKUP
//////////////////////////////////////////////////////////////////////////*/

/// @dev The batch call includes:
/// - ETH value
/// - All create stream functions that return a value
Expand Down
48 changes: 48 additions & 0 deletions tests/mocks/BatchMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.22;

import { Batch } from "src/abstracts/Batch.sol";

contract BatchMock is Batch {
error InvalidNumber(uint256);

uint256 internal _number = 10;

// A view only function.
function getNumber() public view returns (uint256) {
return _number;
}

// A view only function that reverts.
function getNumberAndRevert() public pure returns (uint256) {
revert InvalidNumber(1);
}

// A state changing function with no return value.
function setNumber(uint256 number) public {
_number = number;
}

// A state changing function with payable modifier and return value.
function setNumberWithPayableAndReturn(uint256 number) public payable returns (uint256) {
_number = number;
return _number;
}

// A state changing function with payable modifier but reverts with custom error.
function setNumberWithPayableAndRevert(uint256 number) public payable {
_number = number;
revert InvalidNumber(number);
}

// A state changing function with payable modifier but reverts with string error.
function setNumberWithPayableAndRevertString(uint256 number) public payable {
_number = number;
revert("You cannot pass");
}

// A state changing function with payable modifier and no return value.
function setNumberWithPayable(uint256 number) public payable {
_number = number;
}
}
144 changes: 144 additions & 0 deletions tests/unit/concrete/batch/batch.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.22;

import { Base_Test } from "../../../Base.t.sol";
import { BatchMock } from "../../../mocks/BatchMock.sol";

contract Batch_Unit_Concrete_Test is Base_Test {
BatchMock internal batchMock;
bytes[] internal calls;
bytes[] internal results;
uint256 internal newNumber = 100;

function setUp() public virtual override {
Base_Test.setUp();

batchMock = new BatchMock();
}

function test_RevertWhen_FunctionDoesNotExist() external {
calls = new bytes[](1);
calls[0] = abi.encodeWithSignature("nonExistentFunction()");

// It should revert.
vm.expectRevert(bytes(""));
batchMock.batch(calls);
}

modifier whenFunctionExists() {
_;
}

modifier givenNonStateChangingFunction() {
_;
}

function test_RevertWhen_FunctionReverts() external whenFunctionExists givenNonStateChangingFunction {
calls = new bytes[](1);
calls[0] = abi.encodeCall(batchMock.getNumberAndRevert, ());

// It should revert.
vm.expectRevert(abi.encodeWithSelector(BatchMock.InvalidNumber.selector, 1));
batchMock.batch(calls);
}

function test_WhenFunctionNotRevert() external whenFunctionExists givenNonStateChangingFunction {
calls = new bytes[](1);
calls[0] = abi.encodeCall(batchMock.getNumber, ());
results = batchMock.batch(calls);

// It should return expected value.
assertEq(results.length, 1);
assertEq(abi.decode(results[0], (uint256)), 10);
}

modifier givenStateChangingFunction() {
_;
}

modifier whenNotPayable() {
_;
}

function test_RevertWhen_BatchIncludesETHValue()
external
whenFunctionExists
givenStateChangingFunction
whenNotPayable
{
calls = new bytes[](1);
calls[0] = abi.encodeCall(batchMock.setNumber, (newNumber));

// It should revert.
vm.expectRevert(bytes(""));
batchMock.batch{ value: 1 wei }(calls);
}

function test_WhenBatchNotIncludeETHValue() external whenFunctionExists givenStateChangingFunction whenNotPayable {
calls = new bytes[](1);
calls[0] = abi.encodeCall(batchMock.setNumber, (newNumber));

results = batchMock.batch(calls);

// It should return empty value.
assertEq(results.length, 1);
assertEq(results[0], hex"");
}

modifier whenPayable() {
_;
}

function test_RevertWhen_FunctionRevertsWithCustomError()
external
whenFunctionExists
givenStateChangingFunction
whenPayable
{
calls = new bytes[](1);
calls[0] = abi.encodeCall(batchMock.setNumberWithPayableAndRevert, (newNumber));

// It should revert.
vm.expectRevert(abi.encodeWithSelector(BatchMock.InvalidNumber.selector, newNumber));
batchMock.batch{ value: 1 wei }(calls);
}

function test_RevertWhen_FunctionRevertsWithStringError()
external
whenFunctionExists
givenStateChangingFunction
whenPayable
{
calls = new bytes[](1);
calls[0] = abi.encodeCall(batchMock.setNumberWithPayableAndRevertString, (newNumber));

// It should revert.
vm.expectRevert("You cannot pass");
batchMock.batch{ value: 1 wei }(calls);
}

function test_WhenFunctionReturnsAValue() external whenFunctionExists givenStateChangingFunction whenPayable {
calls = new bytes[](1);
calls[0] = abi.encodeCall(batchMock.setNumberWithPayableAndReturn, (newNumber));
results = batchMock.batch{ value: 1 wei }(calls);

// It should return expected value.
assertEq(results.length, 1);
assertEq(abi.decode(results[0], (uint256)), newNumber);
}

function test_WhenFunctionDoesNotReturnAValue()
external
whenFunctionExists
givenStateChangingFunction
whenPayable
{
calls = new bytes[](1);
calls[0] = abi.encodeCall(batchMock.setNumberWithPayable, (newNumber));
results = batchMock.batch{ value: 1 wei }(calls);

// It should return empty value.
assertEq(results.length, 1);
assertEq(results[0], hex"");
}
}
24 changes: 24 additions & 0 deletions tests/unit/concrete/batch/batch.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Batch_Unit_Concrete_Test
├── when function does not exist
│ └── it should revert
└── when function exists
├── given non state changing function
│ ├── when function reverts
│ │ └── it should revert
│ └── when function not revert
│ └── it should return expected value
└── given state changing function
├── when not payable
│ ├── when batch includes ETH value
│ │ └── it should revert
│ └── when batch not include ETH value
│ └── it should return empty value
└── when payable
├── when function reverts with custom error
│ └── it should revert
├── when function reverts with string error
│ └── it should revert
├── when function returns a value
│ └── it should return expected value
└── when function does not return a value
└── it should return empty value

0 comments on commit 1049ab8

Please sign in to comment.