// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; import {Common} from "../../../libraries/Common.sol"; import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol"; import "./BaseDestinationFeeManager.t.sol"; /** * @title BaseFeeManagerTest * @author Michael Fletcher * @notice This contract will test the functionality of the feeManager processFee */ contract DestinationFeeManagerProcessFeeTest is BaseDestinationFeeManagerTest { 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(); IDestinationRewardManager.FeePayment[] memory contractFees = new IDestinationRewardManager.FeePayment[](1); contractFees[0] = IDestinationRewardManager.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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, 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(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0); //no logs should have been emitted assertEq(vm.getRecordedLogs().length, 0); } }