From 2662df1ea468168f3f6bee430810b9a98b59edbd Mon Sep 17 00:00:00 2001 From: katzman Date: Thu, 21 Mar 2024 12:33:46 -0700 Subject: [PATCH] Get MagicSpend test coverage to 100% (#14) * Add OwnerWithdraw test cases * Add test file for GetHash * Add test file for IsValidWithdrawalSignature * Add test for InsufficientBalance revert case * Rename getHash.t.sol to GetHash.t.sol * Remove commented lines * Format * Added coverage and coveralls to CI * Remove unecessary filter to lcov --- .github/workflows/test.yml | 56 +++++++++++++++++++++++++---- test/GetHash.t.sol | 23 ++++++++++++ test/IsValidWithdrawSignature.t.sol | 24 +++++++++++++ test/OwnerWithdraw.t.sol | 36 +++++++++++++++++++ test/ValidatePaymasterUserOp.t.sol | 6 ++++ 5 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 test/GetHash.t.sol create mode 100644 test/IsValidWithdrawSignature.t.sol create mode 100644 test/OwnerWithdraw.t.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7061b60..fa405fc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: test +name: Forge CI on: pull_request: @@ -9,11 +9,8 @@ env: FOUNDRY_PROFILE: ci jobs: - check: - strategy: - fail-fast: true - - name: Foundry project + forge-test: + name: Run Forge Tests and Checks runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -40,3 +37,50 @@ jobs: run: | forge fmt --check id: fmt + + forge-coverage: + name: Run Coverage Reporting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Install forge dependencies + run: forge install + + - name: Install lcov + run: | + sudo apt-get install lcov + id: lcov + + - name: Run coverage + run: | + forge coverage --report summary --report lcov + + - name: Prune coverage + run: | + lcov --remove ./lcov.info -o ./lcov-filtered.info 'test/*' 'script/*' + + - name: Submit coverage to Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: ./lcov-filtered.info + flag-name: foundry + parallel: true + + finish: + needs: forge-coverage + if: ${{ always() }} + runs-on: ubuntu-latest + steps: + - name: Coveralls Finished + uses: coverallsapp/github-action@v2 + with: + parallel-finished: true diff --git a/test/GetHash.t.sol b/test/GetHash.t.sol new file mode 100644 index 0000000..bd4c799 --- /dev/null +++ b/test/GetHash.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import {MagicSpendTest} from "./MagicSpend.t.sol"; +import {MagicSpend} from "../src/MagicSpend.sol"; +import {MockERC20} from "solady/test/utils/mocks/MockERC20.sol"; +import {SignatureCheckerLib} from "solady/src/utils/SignatureCheckerLib.sol"; + +contract GetHashTest is MagicSpendTest { + MockERC20 token = new MockERC20("test", "TEST", 18); + + function test_returnsValidHash() public { + asset = address(token); + MagicSpend.WithdrawRequest memory request = _getRequest(); + bytes32 expectedHash = SignatureCheckerLib.toEthSignedMessageHash( + abi.encode( + address(magic), withdrawer, block.chainid, address(token), request.amount, request.nonce, request.expiry + ) + ); + bytes32 testHash = magic.getHash(withdrawer, request); + assertEq(testHash, expectedHash); + } +} diff --git a/test/IsValidWithdrawSignature.t.sol b/test/IsValidWithdrawSignature.t.sol new file mode 100644 index 0000000..92628c1 --- /dev/null +++ b/test/IsValidWithdrawSignature.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import "./MagicSpend.t.sol"; +import {MockERC20} from "solady/test/utils/mocks/MockERC20.sol"; + +contract IsValidWithdrawalSignature is MagicSpendTest { + MockERC20 token = new MockERC20("test", "TEST", 18); + + function test_returnsTrueWithValidSignature() public { + asset = address(token); + MagicSpend.WithdrawRequest memory request = _getRequest(); + bool success = magic.isValidWithdrawSignature(withdrawer, request); + assert(success); + } + + function test_returnsFalseWithInvalidSignature() public { + asset = address(token); + address invalidSender = address(0xdead); + MagicSpend.WithdrawRequest memory request = _getRequest(); + bool success = magic.isValidWithdrawSignature(invalidSender, request); + assertFalse(success); + } +} diff --git a/test/OwnerWithdraw.t.sol b/test/OwnerWithdraw.t.sol new file mode 100644 index 0000000..32026ee --- /dev/null +++ b/test/OwnerWithdraw.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import {Ownable} from "./MagicSpend.t.sol"; +import {MagicSpendTest} from "./Validate.t.sol"; +import {MockERC20} from "solady/test/utils/mocks/MockERC20.sol"; + +contract OwnerWithdrawTest is MagicSpendTest { + MockERC20 token = new MockERC20("test", "TEST", 18); + + function test_revertsIfNotOwner() public { + vm.startPrank(withdrawer); + vm.expectRevert(Ownable.Unauthorized.selector); + magic.ownerWithdraw(address(token), withdrawer, 1); + } + + function test_transfersERC20Successfully(uint256 amount_) public { + vm.startPrank(owner); + amount = amount_; + token.mint(address(magic), amount); + asset = address(token); + assertEq(token.balanceOf(owner), 0); + magic.ownerWithdraw(asset, owner, amount); + assertEq(token.balanceOf(owner), amount); + } + + function test_transfersETHSuccessfully(uint256 amount_) public { + vm.deal(address(magic), amount_); + vm.startPrank(owner); + amount = amount_; + asset = address(0); + assertEq(owner.balance, 0); + magic.ownerWithdraw(asset, owner, amount); + assertEq(owner.balance, amount); + } +} diff --git a/test/ValidatePaymasterUserOp.t.sol b/test/ValidatePaymasterUserOp.t.sol index 5b2eefd..f6bb052 100644 --- a/test/ValidatePaymasterUserOp.t.sol +++ b/test/ValidatePaymasterUserOp.t.sol @@ -16,6 +16,12 @@ contract ValidatePaymasterUserOpTest is PaymasterMagicSpendBaseTest, ValidateTes magic.validatePaymasterUserOp(_getUserOp(), userOpHash, maxCost); } + function test_revertsIfWithdrawalExceedsBalance() public { + vm.deal(address(magic), 0); + vm.expectRevert(abi.encodeWithSelector(MagicSpend.InsufficientBalance.selector, amount, 0)); + magic.validatePaymasterUserOp(_getUserOp(), userOpHash, maxCost); + } + function test_returnsCorrectly() public { (bytes memory context, uint256 validationData) = magic.validatePaymasterUserOp(_getUserOp(), userOpHash, maxCost);