-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
160 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 159 additions & 0 deletions
159
packages/evm/test/incentives/ERC20VariableIncentive.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.24; | ||
|
||
import {Test, console} from "lib/forge-std/src/Test.sol"; | ||
import {MockERC20} from "contracts/shared/Mocks.sol"; | ||
import {LibClone} from "@solady/utils/LibClone.sol"; | ||
import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol"; | ||
|
||
import {BoostError} from "contracts/shared/BoostError.sol"; | ||
import {Incentive} from "contracts/incentives/Incentive.sol"; | ||
import {ERC20VariableIncentive} from "contracts/incentives/ERC20VariableIncentive.sol"; | ||
|
||
import {Budget} from "contracts/budgets/Budget.sol"; | ||
import {SimpleBudget} from "contracts/budgets/SimpleBudget.sol"; | ||
|
||
contract ERC20VariableIncentiveTest is Test { | ||
using SafeTransferLib for address; | ||
|
||
// Declare test accounts as constants | ||
address CLAIM_RECIPIENT = makeAddr("CLAIM_RECIPIENT"); | ||
address EXCEEDS_LIMIT_CLAIM = makeAddr("EXCEEDS_LIMIT_CLAIM"); | ||
address VARIABLE_REWARD_CLAIM = makeAddr("VARIABLE_REWARD_CLAIM"); | ||
|
||
ERC20VariableIncentive public incentive; | ||
SimpleBudget public budget = new SimpleBudget(); | ||
MockERC20 public mockAsset = new MockERC20(); | ||
|
||
function setUp() public { | ||
incentive = _newIncentiveClone(); | ||
|
||
// Preload the budget with some mock tokens | ||
mockAsset.mint(address(this), 100 ether); | ||
mockAsset.approve(address(budget), 100 ether); | ||
budget.allocate(_makeFungibleTransfer(Budget.AssetType.ERC20, address(mockAsset), address(this), 100 ether)); | ||
|
||
// Manually handle the budget disbursement | ||
budget.disburse( | ||
_makeFungibleTransfer(Budget.AssetType.ERC20, address(mockAsset), address(incentive), 100 ether) | ||
); | ||
} | ||
|
||
/////////////////////////////// | ||
// ERC20VariableIncentive.initialize // | ||
/////////////////////////////// | ||
|
||
function testInitialize() public { | ||
// Initialize the ERC20VariableIncentive | ||
_initialize(address(mockAsset), 1 ether, 5 ether); | ||
|
||
// Check the incentive parameters | ||
assertEq(incentive.asset(), address(mockAsset)); | ||
assertEq(incentive.reward(), 1 ether); | ||
assertEq(incentive.limit(), 5 ether); | ||
} | ||
|
||
function testInitialize_InsufficientFunds() public { | ||
// Attempt to initialize with a limit greater than available balance => revert | ||
vm.expectRevert( | ||
abi.encodeWithSelector(BoostError.InsufficientFunds.selector, address(mockAsset), 100 ether, 101 ether) | ||
); | ||
_initialize(address(mockAsset), 1 ether, 101 ether); | ||
} | ||
|
||
function testInitialize_InvalidInitialization() public { | ||
// Attempt to initialize with invalid parameters => revert | ||
vm.expectRevert(BoostError.InvalidInitialization.selector); | ||
_initialize(address(mockAsset), 0, 0); | ||
} | ||
|
||
//////////////////////////////// | ||
// ERC20VariableIncentive.claim // | ||
//////////////////////////////// | ||
|
||
function testClaim() public { | ||
// Initialize the ERC20VariableIncentive | ||
_initialize(address(mockAsset), 1 ether, 5 ether); | ||
|
||
vm.expectEmit(true, false, false, true); | ||
emit Incentive.Claimed(CLAIM_RECIPIENT, abi.encodePacked(address(mockAsset), CLAIM_RECIPIENT, uint256(1 ether))); | ||
|
||
// Claim the incentive | ||
bytes memory claimPayload = | ||
abi.encode(Incentive.ClaimPayload({target: CLAIM_RECIPIENT, data: abi.encode(1 ether)})); | ||
incentive.claim(claimPayload); | ||
|
||
// Check the claim status and balance | ||
assertEq(mockAsset.balanceOf(CLAIM_RECIPIENT), 1 ether); | ||
assertTrue(incentive.isClaimable(claimPayload)); | ||
} | ||
|
||
function testClaim_ClaimFailed() public { | ||
// Initialize the ERC20VariableIncentive | ||
_initialize(address(mockAsset), 1 ether, 2 ether); | ||
|
||
// Attempt to claim more than the limit => revert | ||
bytes memory claimPayload = | ||
abi.encode(Incentive.ClaimPayload({target: EXCEEDS_LIMIT_CLAIM, data: abi.encode(3 ether)})); | ||
vm.expectRevert(Incentive.ClaimFailed.selector); | ||
incentive.claim(claimPayload); | ||
} | ||
|
||
function testClaim_VariableReward() public { | ||
// Initialize the ERC20VariableIncentive with zero reward, meaning signed amount will be used directly | ||
_initialize(address(mockAsset), 0, 5 ether); | ||
|
||
// Claim with variable reward | ||
bytes memory claimPayload = | ||
abi.encode(Incentive.ClaimPayload({target: VARIABLE_REWARD_CLAIM, data: abi.encode(2 ether)})); | ||
incentive.claim(claimPayload); | ||
|
||
// Check the claim status and balance | ||
assertEq(mockAsset.balanceOf(VARIABLE_REWARD_CLAIM), 2 ether); | ||
assertTrue(incentive.isClaimable(claimPayload)); | ||
} | ||
|
||
///////////////////////////////// | ||
// ERC20VariableIncentive.supportsInterface // | ||
///////////////////////////////// | ||
|
||
function testSupportsInterface() public { | ||
// Ensure the contract supports the Incentive interface | ||
assertTrue(incentive.supportsInterface(type(Incentive).interfaceId)); | ||
} | ||
|
||
function testSupportsInterface_NotSupported() public { | ||
// Ensure the contract does not support an unsupported interface | ||
assertFalse(incentive.supportsInterface(type(Test).interfaceId)); | ||
} | ||
|
||
/////////////////////////// | ||
// Test Helper Functions // | ||
/////////////////////////// | ||
|
||
function _newIncentiveClone() internal returns (ERC20VariableIncentive) { | ||
return ERC20VariableIncentive(LibClone.clone(address(new ERC20VariableIncentive()))); | ||
} | ||
|
||
function _initialize(address asset, uint256 reward, uint256 limit) internal { | ||
incentive.initialize(_initPayload(asset, reward, limit)); | ||
} | ||
|
||
function _initPayload(address asset, uint256 reward, uint256 limit) internal pure returns (bytes memory) { | ||
return abi.encode(ERC20VariableIncentive.InitPayload({asset: asset, reward: reward, limit: limit})); | ||
} | ||
|
||
function _makeFungibleTransfer(Budget.AssetType assetType, address asset, address target, uint256 value) | ||
internal | ||
pure | ||
returns (bytes memory) | ||
{ | ||
Budget.Transfer memory transfer; | ||
transfer.assetType = assetType; | ||
transfer.asset = asset; | ||
transfer.target = target; | ||
transfer.data = abi.encode(Budget.FungiblePayload({amount: value})); | ||
|
||
return abi.encode(transfer); | ||
} | ||
} |