// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; import {ERC20Mock} from "../../../../shared/mocks/ERC20Mock.sol"; import {WERC20Mock} from "../../../../shared/mocks/WERC20Mock.sol"; import {Common} from "../../../libraries/Common.sol"; import {FeeManager} from "../../FeeManager.sol"; import {RewardManager} from "../../RewardManager.sol"; import {IRewardManager} from "../../interfaces/IRewardManager.sol"; import {FeeManagerProxy} from "../mocks/FeeManagerProxy.sol"; import {Test} from "forge-std/Test.sol"; /** * @title BaseFeeManagerTest * @author Michael Fletcher * @notice Base class for all feeManager tests * @dev This contract is intended to be inherited from and not used directly. It contains functionality to setup the * feeManager */ contract BaseFeeManagerTest is Test { //contracts FeeManager internal feeManager; RewardManager internal rewardManager; FeeManagerProxy internal feeManagerProxy; ERC20Mock internal link; WERC20Mock internal native; //erc20 config uint256 internal constant DEFAULT_LINK_MINT_QUANTITY = 100 ether; uint256 internal constant DEFAULT_NATIVE_MINT_QUANTITY = 100 ether; //contract owner address internal constant INVALID_ADDRESS = address(0); address internal constant ADMIN = address(uint160(uint256(keccak256("ADMIN")))); address internal constant USER = address(uint160(uint256(keccak256("USER")))); address internal constant PROXY = address(uint160(uint256(keccak256("PROXY")))); //version masks bytes32 internal constant V_MASK = 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; bytes32 internal constant V1_BITMASK = 0x0001000000000000000000000000000000000000000000000000000000000000; bytes32 internal constant V2_BITMASK = 0x0002000000000000000000000000000000000000000000000000000000000000; bytes32 internal constant V3_BITMASK = 0x0003000000000000000000000000000000000000000000000000000000000000; //feed ids & config digests bytes32 internal constant DEFAULT_FEED_1_V1 = (keccak256("ETH-USD") & V_MASK) | V1_BITMASK; bytes32 internal constant DEFAULT_FEED_1_V2 = (keccak256("ETH-USD") & V_MASK) | V2_BITMASK; bytes32 internal constant DEFAULT_FEED_1_V3 = (keccak256("ETH-USD") & V_MASK) | V3_BITMASK; bytes32 internal constant DEFAULT_FEED_2_V3 = (keccak256("LINK-USD") & V_MASK) | V3_BITMASK; bytes32 internal constant DEFAULT_CONFIG_DIGEST = keccak256("DEFAULT_CONFIG_DIGEST"); //report uint256 internal constant DEFAULT_REPORT_LINK_FEE = 1e10; uint256 internal constant DEFAULT_REPORT_NATIVE_FEE = 1e12; //rewards uint64 internal constant FEE_SCALAR = 1e18; address internal constant NATIVE_WITHDRAW_ADDRESS = address(0); //the selector for each error bytes4 internal immutable INVALID_DISCOUNT_ERROR = FeeManager.InvalidDiscount.selector; bytes4 internal immutable INVALID_ADDRESS_ERROR = FeeManager.InvalidAddress.selector; bytes4 internal immutable INVALID_SURCHARGE_ERROR = FeeManager.InvalidSurcharge.selector; bytes4 internal immutable EXPIRED_REPORT_ERROR = FeeManager.ExpiredReport.selector; bytes4 internal immutable INVALID_DEPOSIT_ERROR = FeeManager.InvalidDeposit.selector; bytes4 internal immutable INVALID_QUOTE_ERROR = FeeManager.InvalidQuote.selector; bytes4 internal immutable UNAUTHORIZED_ERROR = FeeManager.Unauthorized.selector; bytes internal constant ONLY_CALLABLE_BY_OWNER_ERROR = "Only callable by owner"; bytes internal constant INSUFFICIENT_ALLOWANCE_ERROR = "ERC20: insufficient allowance"; bytes4 internal immutable ZERO_DEFICIT = FeeManager.ZeroDeficit.selector; //events emitted event SubscriberDiscountUpdated(address indexed subscriber, bytes32 indexed feedId, address token, uint64 discount); event NativeSurchargeUpdated(uint64 newSurcharge); event InsufficientLink(IRewardManager.FeePayment[] feesAndRewards); event Withdraw(address adminAddress, address recipient, address assetAddress, uint192 quantity); event LinkDeficitCleared(bytes32 indexed configDigest, uint256 linkQuantity); event DiscountApplied( bytes32 indexed configDigest, address indexed subscriber, Common.Asset fee, Common.Asset reward, uint256 appliedDiscountQuantity ); function setUp() public virtual { //change to admin user vm.startPrank(ADMIN); //init required contracts _initializeContracts(); } function _initializeContracts() internal { link = new ERC20Mock(18); native = new WERC20Mock(); feeManagerProxy = new FeeManagerProxy(); rewardManager = new RewardManager(address(link)); feeManager = new FeeManager(address(link), address(native), address(feeManagerProxy), address(rewardManager)); //link the feeManager to the proxy feeManagerProxy.setFeeManager(feeManager); //link the feeManager to the reward manager rewardManager.setFeeManager(address(feeManager)); //mint some tokens to the admin link.mint(ADMIN, DEFAULT_LINK_MINT_QUANTITY); native.mint(ADMIN, DEFAULT_NATIVE_MINT_QUANTITY); vm.deal(ADMIN, DEFAULT_NATIVE_MINT_QUANTITY); //mint some tokens to the user link.mint(USER, DEFAULT_LINK_MINT_QUANTITY); native.mint(USER, DEFAULT_NATIVE_MINT_QUANTITY); vm.deal(USER, DEFAULT_NATIVE_MINT_QUANTITY); //mint some tokens to the proxy link.mint(PROXY, DEFAULT_LINK_MINT_QUANTITY); native.mint(PROXY, DEFAULT_NATIVE_MINT_QUANTITY); vm.deal(PROXY, DEFAULT_NATIVE_MINT_QUANTITY); } function setSubscriberDiscount( address subscriber, bytes32 feedId, address token, uint256 discount, address sender ) internal { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(sender); //set the discount feeManager.updateSubscriberDiscount(subscriber, feedId, token, uint64(discount)); //change back to the original address changePrank(originalAddr); } function setNativeSurcharge(uint256 surcharge, address sender) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(sender); //set the surcharge feeManager.setNativeSurcharge(uint64(surcharge)); //change back to the original address changePrank(originalAddr); } // solium-disable-next-line no-unused-vars function getFee(bytes memory report, address quote, address subscriber) public view returns (Common.Asset memory) { //get the fee (Common.Asset memory fee,,) = feeManager.getFeeAndReward(subscriber, report, quote); return fee; } function getReward(bytes memory report, address quote, address subscriber) public view returns (Common.Asset memory) { //get the reward (, Common.Asset memory reward,) = feeManager.getFeeAndReward(subscriber, report, quote); return reward; } function getAppliedDiscount(bytes memory report, address quote, address subscriber) public view returns (uint256) { //get the reward (,, uint256 appliedDiscount) = feeManager.getFeeAndReward(subscriber, report, quote); return appliedDiscount; } function getV1Report( bytes32 feedId ) public pure returns (bytes memory) { return abi.encode(feedId, uint32(0), int192(0), int192(0), int192(0), uint64(0), bytes32(0), uint64(0), uint64(0)); } function getV2Report( bytes32 feedId ) public view returns (bytes memory) { return abi.encode( feedId, uint32(0), uint32(0), uint192(DEFAULT_REPORT_NATIVE_FEE), uint192(DEFAULT_REPORT_LINK_FEE), uint32(block.timestamp), int192(0) ); } function getV3Report( bytes32 feedId ) public view returns (bytes memory) { return abi.encode( feedId, uint32(0), uint32(0), uint192(DEFAULT_REPORT_NATIVE_FEE), uint192(DEFAULT_REPORT_LINK_FEE), uint32(block.timestamp), int192(0), int192(0), int192(0) ); } function getV3ReportWithCustomExpiryAndFee( bytes32 feedId, uint256 expiry, uint256 linkFee, uint256 nativeFee ) public pure returns (bytes memory) { return abi.encode( feedId, uint32(0), uint32(0), uint192(nativeFee), uint192(linkFee), uint32(expiry), int192(0), int192(0), int192(0) ); } function getLinkQuote() public view returns (address) { return address(link); } function getNativeQuote() public view returns (address) { return address(native); } function withdraw(address assetAddress, address recipient, uint256 amount, address sender) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(sender); //set the surcharge feeManager.withdraw(assetAddress, recipient, uint192(amount)); //change back to the original address changePrank(originalAddr); } function getLinkBalance( address balanceAddress ) public view returns (uint256) { return link.balanceOf(balanceAddress); } function getNativeBalance( address balanceAddress ) public view returns (uint256) { return native.balanceOf(balanceAddress); } function getNativeUnwrappedBalance( address balanceAddress ) public view returns (uint256) { return balanceAddress.balance; } function mintLink(address recipient, uint256 amount) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(ADMIN); //mint the link to the recipient link.mint(recipient, amount); //change back to the original address changePrank(originalAddr); } function mintNative(address recipient, uint256 amount, address sender) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(sender); //mint the native to the recipient native.mint(recipient, amount); //change back to the original address changePrank(originalAddr); } function issueUnwrappedNative(address recipient, uint256 quantity) public { vm.deal(recipient, quantity); } function ProcessFeeAsUser( bytes memory payload, address subscriber, address tokenAddress, uint256 wrappedNativeValue, address sender ) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(sender); //process the fee feeManager.processFee{value: wrappedNativeValue}(payload, abi.encode(tokenAddress), subscriber); //change ProcessFeeAsUserback to the original address changePrank(originalAddr); } function processFee(bytes memory payload, address subscriber, address feeAddress, uint256 wrappedNativeValue) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(subscriber); //process the fee feeManagerProxy.processFee{value: wrappedNativeValue}(payload, abi.encode(feeAddress)); //change back to the original address changePrank(originalAddr); } function processFee( bytes[] memory payloads, address subscriber, address feeAddress, uint256 wrappedNativeValue ) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(subscriber); //process the fee feeManagerProxy.processFeeBulk{value: wrappedNativeValue}(payloads, abi.encode(feeAddress)); //change back to the original address changePrank(originalAddr); } function getPayload( bytes memory reportPayload ) public pure returns (bytes memory) { return abi.encode([DEFAULT_CONFIG_DIGEST, 0, 0], reportPayload, new bytes32[](1), new bytes32[](1), bytes32("")); } function approveLink(address spender, uint256 quantity, address sender) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(sender); //approve the link to be transferred link.approve(spender, quantity); //change back to the original address changePrank(originalAddr); } function approveNative(address spender, uint256 quantity, address sender) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(sender); //approve the link to be transferred native.approve(spender, quantity); //change back to the original address changePrank(originalAddr); } function payLinkDeficit(bytes32 configDigest, address sender) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(sender); //approve the link to be transferred feeManager.payLinkDeficit(configDigest); //change back to the original address changePrank(originalAddr); } function getLinkDeficit( bytes32 configDigest ) public view returns (uint256) { return feeManager.s_linkDeficit(configDigest); } }