// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; import {Common} from "../../../libraries/Common.sol"; import "./BaseFeeManager.t.sol"; import {IRewardManager} from "../../interfaces/IRewardManager.sol"; /** * @title BaseFeeManagerTest * @author Michael Fletcher * @notice This contract will test the functionality of the feeManager processFee */ contract FeeManagerProcessFeeTest is BaseFeeManagerTest { function setUp() public override { super.setUp(); } function test_nonAdminProxyUserCannotProcessFee() public { //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //should revert as the user is not the owner vm.expectRevert(UNAUTHORIZED_ERROR); //process the fee ProcessFeeAsUser(payload, USER, address(link), 0, USER); } function test_processFeeAsProxy() public { //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //approve the link to be transferred from the from the subscriber to the rewardManager approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); //processing the fee will transfer the link from the user to the rewardManager processFee(payload, USER, address(link), 0); //check the link has been transferred assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); //check the user has had the link fee deducted assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); } function test_processFeeIfSubscriberIsSelf() public { //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //expect a revert due to the feeManager being the subscriber vm.expectRevert(INVALID_ADDRESS_ERROR); //process the fee will fail due to assertion processFee(payload, address(feeManager), address(native), 0); } function test_processFeeWithWithEmptyQuotePayload() public { //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //expect a revert as the quote is invalid vm.expectRevert(); //processing the fee will transfer the link by default processFee(payload, USER, address(0), 0); } function test_processFeeWithWithZeroQuotePayload() public { //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //expect a revert as the quote is invalid vm.expectRevert(INVALID_QUOTE_ERROR); //processing the fee will transfer the link by default processFee(payload, USER, INVALID_ADDRESS, 0); } function test_processFeeWithWithCorruptQuotePayload() public { //get the default payload bytes memory payload = abi.encode( [DEFAULT_CONFIG_DIGEST, 0, 0], getV3Report(DEFAULT_FEED_1_V3), new bytes32[](1), new bytes32[](1), bytes32("") ); //expect an evm revert as the quote is corrupt vm.expectRevert(); //processing the fee will not withdraw anything as there is no fee to collect processFee(payload, USER, address(link), 0); } function test_processFeeDefaultReportsStillVerifiesWithEmptyQuote() public { //get the default payload bytes memory payload = getPayload(getV1Report(DEFAULT_FEED_1_V1)); //processing the fee will transfer the link from the user to the rewardManager processFee(payload, USER, address(0), 0); } function test_processFeeWithDefaultReportPayloadAndQuoteStillVerifies() public { //get the default payload bytes memory payload = getPayload(getV1Report(DEFAULT_FEED_1_V1)); //processing the fee will not withdraw anything as there is no fee to collect processFee(payload, USER, address(link), 0); } function test_processFeeNative() public { //simulate a deposit of link for the conversion pool mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //approve the native to be transferred from the user approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); //processing the fee will transfer the native from the user to the feeManager processFee(payload, USER, address(native), 0); //check the native has been transferred assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); //check the link has been transferred to the rewardManager assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); //check the feeManager has had the link deducted, the remaining balance should be 0 assertEq(getLinkBalance(address(feeManager)), 0); //check the subscriber has had the native deducted assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); } function test_processFeeEmitsEventIfNotEnoughLink() public { //simulate a deposit of half the link required for the fee mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE / 2); //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //approve the native to be transferred from the user approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); //expect an emit as there's not enough link vm.expectEmit(); IRewardManager.FeePayment[] memory contractFees = new IRewardManager.FeePayment[](1); contractFees[0] = IRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); //emit the event that is expected to be emitted emit InsufficientLink(contractFees); //processing the fee will transfer the native from the user to the feeManager processFee(payload, USER, address(native), 0); //check the native has been transferred assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); //check no link has been transferred to the rewardManager assertEq(getLinkBalance(address(rewardManager)), 0); assertEq(getLinkBalance(address(feeManager)), DEFAULT_REPORT_LINK_FEE / 2); //check the subscriber has had the native deducted assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); } function test_processFeeWithUnwrappedNative() public { //simulate a deposit of link for the conversion pool mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //only the proxy or admin can call processFee, they will pass in the native value on the users behalf processFee(payload, USER, address(native), DEFAULT_REPORT_NATIVE_FEE); //check the native has been transferred and converted to wrapped native assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); assertEq(getNativeUnwrappedBalance(address(feeManager)), 0); //check the link has been transferred to the rewardManager assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); //check the feeManager has had the link deducted, the remaining balance should be 0 assertEq(getLinkBalance(address(feeManager)), 0); //check the subscriber has had the native deducted assertEq(getNativeUnwrappedBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); } function test_processFeeWithUnwrappedNativeShortFunds() public { //simulate a deposit of link for the conversion pool mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //expect a revert as not enough funds vm.expectRevert(INVALID_DEPOSIT_ERROR); //only the proxy or admin can call processFee, they will pass in the native value on the users behalf processFee(payload, USER, address(native), DEFAULT_REPORT_NATIVE_FEE - 1); } function test_processFeeWithUnwrappedNativeLinkAddress() public { //simulate a deposit of link for the conversion pool mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //expect a revert as not enough funds vm.expectRevert(INSUFFICIENT_ALLOWANCE_ERROR); //the change will be returned and the user will attempted to be billed in LINK processFee(payload, USER, address(link), DEFAULT_REPORT_NATIVE_FEE - 1); } function test_processFeeWithUnwrappedNativeLinkAddressExcessiveFee() public { //approve the link to be transferred from the from the subscriber to the rewardManager approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, PROXY); //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //call processFee from the proxy to test whether the funds are returned to the subscriber. In reality, the funds would be returned to the caller of the proxy. processFee(payload, PROXY, address(link), DEFAULT_REPORT_NATIVE_FEE); //check the native unwrapped is no longer in the account assertEq(getNativeBalance(address(feeManager)), 0); assertEq(getNativeUnwrappedBalance(address(feeManager)), 0); //check the link has been transferred to the rewardManager assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); //check the feeManager has had the link deducted, the remaining balance should be 0 assertEq(getLinkBalance(address(feeManager)), 0); //native should not be deducted assertEq(getNativeUnwrappedBalance(PROXY), DEFAULT_NATIVE_MINT_QUANTITY); } function test_processFeeWithUnwrappedNativeWithExcessiveFee() public { //simulate a deposit of link for the conversion pool mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //call processFee from the proxy to test whether the funds are returned to the subscriber. In reality, the funds would be returned to the caller of the proxy. processFee(payload, PROXY, address(native), DEFAULT_REPORT_NATIVE_FEE * 2); //check the native has been transferred and converted to wrapped native assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); assertEq(getNativeUnwrappedBalance(address(feeManager)), 0); //check the link has been transferred to the rewardManager assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); //check the feeManager has had the link deducted, the remaining balance should be 0 assertEq(getLinkBalance(address(feeManager)), 0); //check the subscriber has had the native deducted assertEq(getNativeUnwrappedBalance(PROXY), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); } function test_processFeeUsesCorrectDigest() public { //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //approve the link to be transferred from the from the subscriber to the rewardManager approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); //processing the fee will transfer the link from the user to the rewardManager processFee(payload, USER, address(link), 0); //check the link has been transferred assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); //check the user has had the link fee deducted assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); //check funds have been paid to the reward manager assertEq(rewardManager.s_totalRewardRecipientFees(DEFAULT_CONFIG_DIGEST), DEFAULT_REPORT_LINK_FEE); } function test_V1PayloadVerifies() public { //replicate a default payload bytes memory payload = abi.encode( [DEFAULT_CONFIG_DIGEST, 0, 0], getV2Report(DEFAULT_FEED_1_V1), new bytes32[](1), new bytes32[](1), bytes32("") ); //processing the fee will transfer the link from the user to the rewardManager processFee(payload, USER, address(0), 0); } function test_V2PayloadVerifies() public { //get the default payload bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V2)); //approve the link to be transferred from the from the subscriber to the rewardManager approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); //processing the fee will transfer the link from the user to the rewardManager processFee(payload, USER, address(link), 0); //check the link has been transferred assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); //check the user has had the link fee deducted assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); } function test_V2PayloadWithoutQuoteFails() public { //get the default payload bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V2)); //expect a revert as the quote is invalid vm.expectRevert(); //processing the fee will transfer the link from the user to the rewardManager processFee(payload, USER, address(0), 0); } function test_V2PayloadWithoutZeroFee() public { //get the default payload bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V2)); //expect a revert as the quote is invalid vm.expectRevert(); //processing the fee will transfer the link from the user to the rewardManager processFee(payload, USER, address(link), 0); } function test_processFeeWithInvalidReportVersionFailsToDecode() public { bytes memory data = abi.encode(0x0000100000000000000000000000000000000000000000000000000000000000); //get the default payload bytes memory payload = getPayload(data); //serialization will fail as there is no report to decode vm.expectRevert(); //processing the fee will not withdraw anything as there is no fee to collect processFee(payload, USER, address(link), 0); } function test_processFeeWithZeroNativeNonZeroLinkWithNativeQuote() public { //get the default payload bytes memory payload = getPayload( getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, DEFAULT_REPORT_LINK_FEE, 0) ); //call processFee should not revert as the fee is 0 processFee(payload, PROXY, address(native), 0); } function test_processFeeWithZeroNativeNonZeroLinkWithLinkQuote() public { //get the default payload bytes memory payload = getPayload( getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, DEFAULT_REPORT_LINK_FEE, 0) ); //approve the link to be transferred from the from the subscriber to the rewardManager approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); //processing the fee will transfer the link to the rewardManager from the user processFee(payload, USER, address(link), 0); //check the link has been transferred assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); //check the user has had the link fee deducted assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); } function test_processFeeWithZeroLinkNonZeroNativeWithNativeQuote() public { //simulate a deposit of link for the conversion pool mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); //get the default payload bytes memory payload = getPayload( getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, 0, DEFAULT_REPORT_NATIVE_FEE) ); //approve the native to be transferred from the user approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); //processing the fee will transfer the native from the user to the feeManager processFee(payload, USER, address(native), 0); //check the native has been transferred assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); //check no link has been transferred to the rewardManager assertEq(getLinkBalance(address(rewardManager)), 0); //check the feeManager has had no link deducted assertEq(getLinkBalance(address(feeManager)), DEFAULT_REPORT_LINK_FEE); //check the subscriber has had the native deducted assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); } function test_processFeeWithZeroLinkNonZeroNativeWithLinkQuote() public { //get the default payload bytes memory payload = getPayload( getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, 0, DEFAULT_REPORT_NATIVE_FEE) ); //call processFee should not revert as the fee is 0 processFee(payload, USER, address(link), 0); } function test_processFeeWithZeroNativeNonZeroLinkReturnsChange() public { //get the default payload bytes memory payload = getPayload( getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, 0, DEFAULT_REPORT_NATIVE_FEE) ); //call processFee should not revert as the fee is 0 processFee(payload, USER, address(link), DEFAULT_REPORT_NATIVE_FEE); //check the change has been returned assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY); } function test_V1PayloadVerifiesAndReturnsChange() public { //emulate a V1 payload with no quote bytes memory payload = getPayload(getV1Report(DEFAULT_FEED_1_V1)); processFee(payload, USER, address(0), DEFAULT_REPORT_NATIVE_FEE); //Fee manager should not contain any native assertEq(address(feeManager).balance, 0); assertEq(getNativeBalance(address(feeManager)), 0); //check the unused native passed in is returned assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY); } function test_processFeeWithDiscountEmitsEvent() public { //simulate a deposit of link for the conversion pool mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); //set the subscriber discount to 50% setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); //approve the native to be transferred from the user approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE / 2, USER); //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); uint256 appliedDiscount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); vm.expectEmit(); emit DiscountApplied(DEFAULT_CONFIG_DIGEST, USER, fee, reward, appliedDiscount); //call processFee should not revert as the fee is 0 processFee(payload, USER, address(native), 0); } function test_processFeeWithNoDiscountDoesNotEmitEvent() public { //simulate a deposit of link for the conversion pool mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); //approve the native to be transferred from the user approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); //get the default payload bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); //call processFee should not revert as the fee is 0 processFee(payload, USER, address(native), 0); //no logs should have been emitted assertEq(vm.getRecordedLogs().length, 0); } }