From 612bb775339bcadca6d9dbb309a929d32eecf456 Mon Sep 17 00:00:00 2001 From: andreivladbrg Date: Fri, 10 Jan 2025 19:08:53 +0200 Subject: [PATCH] test: add sablier fees tests --- src/SablierFees.sol | 2 + tests/mocks/Receive.sol | 8 +++ tests/mocks/SablierFeesMock.sol | 8 +++ tests/unit/Unit.t.sol | 24 +++++++ tests/unit/sablier-fees/collectFees.t.sol | 81 +++++++++++++++++++++++ tests/unit/sablier-fees/collectFees.tree | 12 ++++ 6 files changed, 135 insertions(+) create mode 100644 tests/mocks/Receive.sol create mode 100644 tests/mocks/SablierFeesMock.sol create mode 100644 tests/unit/Unit.t.sol create mode 100644 tests/unit/sablier-fees/collectFees.t.sol create mode 100644 tests/unit/sablier-fees/collectFees.tree diff --git a/src/SablierFees.sol b/src/SablierFees.sol index b50d29c..a6967f3 100644 --- a/src/SablierFees.sol +++ b/src/SablierFees.sol @@ -9,6 +9,8 @@ import { Adminable } from "./Adminable.sol"; /// @title SablierFees /// @notice See the documentation in {ISablierFees}. abstract contract SablierFees is Adminable, ISablierFees { + constructor(address initialAdmin) Adminable(initialAdmin) { } + /// @inheritdoc ISablierFees function collectFees() external override { uint256 feeAmount = address(this).balance; diff --git a/tests/mocks/Receive.sol b/tests/mocks/Receive.sol new file mode 100644 index 0000000..31a8fa9 --- /dev/null +++ b/tests/mocks/Receive.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.22 <0.9.0; + +contract ContractWithoutReceive { } + +contract ContractWithReceive { + receive() external payable { } +} diff --git a/tests/mocks/SablierFeesMock.sol b/tests/mocks/SablierFeesMock.sol new file mode 100644 index 0000000..1c31250 --- /dev/null +++ b/tests/mocks/SablierFeesMock.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.22; + +import { SablierFees } from "src/SablierFees.sol"; + +contract SablierFeesMock is SablierFees { + constructor(address initialAdmin) SablierFees(initialAdmin) { } +} diff --git a/tests/unit/Unit.t.sol b/tests/unit/Unit.t.sol new file mode 100644 index 0000000..3d4fc14 --- /dev/null +++ b/tests/unit/Unit.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.22 <0.9.0; + +import { CommonBase } from "../Base.t.sol"; +import { ContractWithoutReceive, ContractWithReceive } from "../mocks/Receive.sol"; + +abstract contract Unit_Test is CommonBase { + address internal admin; + address internal eve; + + ContractWithoutReceive internal contractWithoutReceive; + ContractWithReceive internal contractWithReceive; + + function setUp() public virtual override { + CommonBase.setUp(); + + admin = createUser("admin"); + eve = createUser("eve"); + contractWithoutReceive = new ContractWithoutReceive(); + contractWithReceive = new ContractWithReceive(); + + resetPrank(admin); + } +} diff --git a/tests/unit/sablier-fees/collectFees.t.sol b/tests/unit/sablier-fees/collectFees.t.sol new file mode 100644 index 0000000..73b9670 --- /dev/null +++ b/tests/unit/sablier-fees/collectFees.t.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.22 <0.9.0; + +import { ISablierFees } from "src/interfaces/ISablierFees.sol"; +import { Errors } from "src/libraries/Errors.sol"; + +import { Unit_Test } from "../Unit.t.sol"; +import { SablierFeesMock } from "../../mocks/SablierFeesMock.sol"; + +contract CollectFees_Unit_Concrete_Test is Unit_Test { + SablierFeesMock internal sablierFeesMock; + + function setUp() public virtual override { + Unit_Test.setUp(); + + sablierFeesMock = new SablierFeesMock(admin); + } + + function test_GivenAdminIsNotContract() external { + _test_CollectFees(admin); + } + + modifier givenAdminIsContract() { + _; + } + + function test_RevertGiven_AdminDoesNotImplementReceiveFunction() external givenAdminIsContract { + // Transfer the admin to a contract that does not implement the receive function. + resetPrank({ msgSender: admin }); + sablierFeesMock.transferAdmin(address(contractWithoutReceive)); + + // Make the contract the caller. + resetPrank({ msgSender: address(contractWithoutReceive) }); + + // Expect a revert. + vm.expectRevert( + abi.encodeWithSelector( + Errors.SablierFees_FeeTransferFail.selector, + address(contractWithoutReceive), + address(sablierFeesMock).balance + ) + ); + + // Collect the fees. + sablierFeesMock.collectFees(); + } + + function test_GivenAdminImplementsReceiveFunction() external givenAdminIsContract { + // Transfer the admin to a contract that implements the receive function. + resetPrank({ msgSender: admin }); + sablierFeesMock.transferAdmin(address(contractWithReceive)); + + // Make the contract the caller. + resetPrank({ msgSender: address(contractWithReceive) }); + + // Run the tests. + _test_CollectFees(address(contractWithReceive)); + } + + function _test_CollectFees(address admin) private { + uint256 fee = 1 ether; + + // Set the contract balance to 1 ETH. + vm.deal({ account: address(sablierFeesMock), newBalance: 1 ether }); + + // Load the initial ETH balance of the admin. + uint256 initialAdminBalance = admin.balance; + + // It should emit a {CollectFees} event. + vm.expectEmit({ emitter: address(sablierFeesMock) }); + emit ISablierFees.CollectFees({ admin: admin, feeAmount: fee }); + + sablierFeesMock.collectFees(); + + // It should transfer the fee. + assertEq(admin.balance, initialAdminBalance + fee, "admin ETH balance"); + + // It should decrease contract balance to zero. + assertEq(address(sablierFeesMock).balance, 0, "sablierFeesMock ETH balance"); + } +} diff --git a/tests/unit/sablier-fees/collectFees.tree b/tests/unit/sablier-fees/collectFees.tree new file mode 100644 index 0000000..bbfec9f --- /dev/null +++ b/tests/unit/sablier-fees/collectFees.tree @@ -0,0 +1,12 @@ +CollectFees_Unit_Concrete_Test +├── given admin is not contract +│ ├── it should transfer fee +│ ├── it should decrease contract balance to zero +│ └── it should emit a {CollectFees} event +└── given admin is contract + ├── given admin does not implement receive function + │ └── it should revert + └── given admin implements receive function + ├── it should transfer fee + ├── it should decrease contract balance to zero + └── it should emit a {CollectFees} event