// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; import {ERC20Mock} from "../../../../shared/mocks/ERC20Mock.sol"; import {Common} from "../../../libraries/Common.sol"; import {RewardManager} from "../../../v0.3.0/RewardManager.sol"; import {IRewardManager} from "../../interfaces/IRewardManager.sol"; import {Test} from "forge-std/Test.sol"; /** * @title BaseRewardManagerTest * @author Michael Fletcher * @notice Base class for all reward manager tests * @dev This contract is intended to be inherited from and not used directly. It contains functionality to setup a * primary and secondary pool */ contract BaseRewardManagerTest is Test { //contracts ERC20Mock internal asset; ERC20Mock internal unsupported; RewardManager internal rewardManager; //default address for unregistered recipient address internal constant INVALID_ADDRESS = address(0); //contract owner address internal constant ADMIN = address(uint160(uint256(keccak256("ADMIN")))); //address to represent verifier contract address internal constant FEE_MANAGER = address(uint160(uint256(keccak256("FEE_MANAGER")))); //a general user address internal constant USER = address(uint160(uint256(keccak256("USER")))); //default recipients configured in reward manager address internal constant DEFAULT_RECIPIENT_1 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_1")))); address internal constant DEFAULT_RECIPIENT_2 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_2")))); address internal constant DEFAULT_RECIPIENT_3 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_3")))); address internal constant DEFAULT_RECIPIENT_4 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_4")))); address internal constant DEFAULT_RECIPIENT_5 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_5")))); address internal constant DEFAULT_RECIPIENT_6 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_6")))); address internal constant DEFAULT_RECIPIENT_7 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_7")))); //additional recipients not in the reward manager address internal constant DEFAULT_RECIPIENT_8 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_8")))); address internal constant DEFAULT_RECIPIENT_9 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_9")))); //two pools should be enough to test all edge cases bytes32 internal constant PRIMARY_POOL_ID = keccak256("primary_pool"); bytes32 internal constant SECONDARY_POOL_ID = keccak256("secondary_pool"); bytes32 internal constant INVALID_POOL_ID = keccak256("invalid_pool"); bytes32 internal constant ZERO_POOL_ID = bytes32(0); //convenience arrays of all pool combinations used for testing bytes32[] internal PRIMARY_POOL_ARRAY = [PRIMARY_POOL_ID]; bytes32[] internal SECONDARY_POOL_ARRAY = [SECONDARY_POOL_ID]; bytes32[] internal ALL_POOLS = [PRIMARY_POOL_ID, SECONDARY_POOL_ID]; //erc20 config uint256 internal constant DEFAULT_MINT_QUANTITY = 100 ether; //reward scalar (this should match the const in the contract) uint64 internal constant POOL_SCALAR = 1e18; uint64 internal constant ONE_PERCENT = POOL_SCALAR / 100; uint64 internal constant FIFTY_PERCENT = POOL_SCALAR / 2; uint64 internal constant TEN_PERCENT = POOL_SCALAR / 10; //the selector for each error bytes4 internal immutable UNAUTHORIZED_ERROR_SELECTOR = RewardManager.Unauthorized.selector; bytes4 internal immutable INVALID_ADDRESS_ERROR_SELECTOR = RewardManager.InvalidAddress.selector; bytes4 internal immutable INVALID_WEIGHT_ERROR_SELECTOR = RewardManager.InvalidWeights.selector; bytes4 internal immutable INVALID_POOL_ID_ERROR_SELECTOR = RewardManager.InvalidPoolId.selector; bytes internal constant ONLY_CALLABLE_BY_OWNER_ERROR = "Only callable by owner"; bytes4 internal immutable INVALID_POOL_LENGTH_SELECTOR = RewardManager.InvalidPoolLength.selector; // Events emitted within the reward manager event RewardRecipientsUpdated(bytes32 indexed poolId, Common.AddressAndWeight[] newRewardRecipients); event RewardsClaimed(bytes32 indexed poolId, address indexed recipient, uint192 quantity); event FeeManagerUpdated(address newProxyAddress); event FeePaid(IRewardManager.FeePayment[] payments, address payee); function setUp() public virtual { //change to admin user vm.startPrank(ADMIN); //init required contracts _initializeERC20Contracts(); _initializeRewardManager(); } function _initializeERC20Contracts() internal { //create the contracts asset = new ERC20Mock(18); unsupported = new ERC20Mock(18); //mint some tokens to the admin asset.mint(ADMIN, DEFAULT_MINT_QUANTITY); unsupported.mint(ADMIN, DEFAULT_MINT_QUANTITY); //mint some tokens to the user asset.mint(FEE_MANAGER, DEFAULT_MINT_QUANTITY); unsupported.mint(FEE_MANAGER, DEFAULT_MINT_QUANTITY); } function _initializeRewardManager() internal { //create the contract rewardManager = new RewardManager(address(asset)); rewardManager.setFeeManager(FEE_MANAGER); } function createPrimaryPool() public { rewardManager.setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients()); } function createSecondaryPool() public { rewardManager.setRewardRecipients(SECONDARY_POOL_ID, getSecondaryRecipients()); } //override this to test variations of different recipients. changing this function will require existing tests to be // updated as constants are hardcoded to be explicit function getPrimaryRecipients() public virtual returns (Common.AddressAndWeight[] memory) { //array of recipients Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); //init each recipient with even weights. 2500 = 25% of pool recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, POOL_SCALAR / 4); recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, POOL_SCALAR / 4); recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, POOL_SCALAR / 4); recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, POOL_SCALAR / 4); return recipients; } function getPrimaryRecipientAddresses() public pure returns (address[] memory) { //array of recipients address[] memory recipients = new address[](4); recipients[0] = DEFAULT_RECIPIENT_1; recipients[1] = DEFAULT_RECIPIENT_2; recipients[2] = DEFAULT_RECIPIENT_3; recipients[3] = DEFAULT_RECIPIENT_4; return recipients; } //override this to test variations of different recipients. function getSecondaryRecipients() public virtual returns (Common.AddressAndWeight[] memory) { //array of recipients Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); //init each recipient with even weights. 2500 = 25% of pool recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, POOL_SCALAR / 4); recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, POOL_SCALAR / 4); recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, POOL_SCALAR / 4); recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, POOL_SCALAR / 4); return recipients; } function getSecondaryRecipientAddresses() public pure returns (address[] memory) { //array of recipients address[] memory recipients = new address[](4); recipients[0] = DEFAULT_RECIPIENT_1; recipients[1] = DEFAULT_RECIPIENT_5; recipients[2] = DEFAULT_RECIPIENT_6; recipients[3] = DEFAULT_RECIPIENT_7; return recipients; } function addFundsToPool(bytes32 poolId, Common.Asset memory amount, address sender) public { IRewardManager.FeePayment[] memory payments = new IRewardManager.FeePayment[](1); payments[0] = IRewardManager.FeePayment(poolId, uint192(amount.amount)); addFundsToPool(payments, sender); } function addFundsToPool(IRewardManager.FeePayment[] memory payments, address sender) public { //record the current address and switch to the sender address originalAddr = msg.sender; changePrank(sender); uint256 totalPayment; for (uint256 i; i < payments.length; ++i) { totalPayment += payments[i].amount; } //approve the amount being paid into the pool ERC20Mock(address(asset)).approve(address(rewardManager), totalPayment); //this represents the verifier adding some funds to the pool rewardManager.onFeePaid(payments, sender); //change back to the original address changePrank(originalAddr); } function getAsset( uint256 quantity ) public view returns (Common.Asset memory) { return Common.Asset(address(asset), quantity); } function getAssetBalance( address addr ) public view returns (uint256) { return asset.balanceOf(addr); } function claimRewards(bytes32[] memory poolIds, address sender) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(sender); //claim the rewards rewardManager.claimRewards(poolIds); //change back to the original address changePrank(originalAddr); } function payRecipients(bytes32 poolId, address[] memory recipients, address sender) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(sender); //pay the recipients rewardManager.payRecipients(poolId, recipients); //change back to the original address changePrank(originalAddr); } function setRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] memory recipients, address sender) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(sender); //pay the recipients rewardManager.setRewardRecipients(poolId, recipients); //change back to the original address changePrank(originalAddr); } function setFeeManager(address feeManager, address sender) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(sender); //update the proxy rewardManager.setFeeManager(feeManager); //change back to the original address changePrank(originalAddr); } function updateRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] memory recipients, address sender) public { //record the current address and switch to the recipient address originalAddr = msg.sender; changePrank(sender); //pay the recipients rewardManager.updateRewardRecipients(poolId, recipients); //change back to the original address changePrank(originalAddr); } }