Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: foundry support for missing test and deployment script #45

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
Expand Down Expand Up @@ -145,3 +144,20 @@ node_modules

# Hardhat Ignition default folder for deployments against a local node
ignition/deployments/chain-31337

# Foundry
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/**/dry-run/

# Codecov
lcov.info

# Testing
.gas-snapshot

# vscode - audit docs
.vscode/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std.git
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-include .env

# command: test the whole script without broadcasting the transaction into the chain to spot early errors
deployDry:
forge script foundry_scripts/InjectorInfraDeployment.s.sol \
--rpc-url polygon \
--slow \
-vvvv

12 changes: 12 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[profile.default]
src = 'contracts'
out = "out"
libs = ['node_modules','lib']
test = 'foundry_test'
solc = '0.8.25'

[fmt]
ignore = ['./contracts/**/*']
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@note here i'm opting to avoid linting the contracts all together. it can be something to consider tho once all issues/prs are close


[rpc_endpoints]
polygon = "${POLYGON_RPC_KEY}"
28 changes: 28 additions & 0 deletions foundry_scripts/InjectorInfraDeployment.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.25;

import {Script} from "forge-std/Script.sol";

import {ChildChainGaugeInjectorV2} from "../contracts/ChildChainGaugeInjectorV2.sol";
import {ChildChainGaugeInjectorV2Factory} from "../contracts/injectorFactoryV2.sol";

/// @notice Deploys the v2 infrastructure for the injectors in the following order:
/// 1. {ChildChainGaugeInjectorV2} -> singleton/implementation purposes (helps verifying in etherscan etc)
/// 2. {ChildChainGaugeInjectorV2Factory}
contract InjectorInfraDeployment is Script {
// injector infrastructure
ChildChainGaugeInjectorV2 injectorImpl;
ChildChainGaugeInjectorV2Factory injectorFactory;

function run() public {
// read pk from `.env`
uint256 pk = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(pk);

// 1. {ChildChainGaugeInjectorV2}
injectorImpl = new ChildChainGaugeInjectorV2();

// 2. {ChildChainGaugeInjectorV2Factory}
injectorFactory = new ChildChainGaugeInjectorV2Factory(address(injectorImpl));
}
}
72 changes: 72 additions & 0 deletions foundry_test/BaseFixture.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

import "forge-std/Test.sol";

import {IChildChainGauge} from "../contracts/interfaces/balancer/IChildChainGauge.sol";

import {ChildChainGaugeInjectorV2} from "../contracts/ChildChainGaugeInjectorV2.sol";
import {ChildChainGaugeInjectorV2Factory} from "../contracts/injectorFactoryV2.sol";

contract BaseFixture is Test {
// injector instance
ChildChainGaugeInjectorV2 injector;

// factory instance
ChildChainGaugeInjectorV2Factory factory;

// constants
address constant GAUGE = 0x3Eae4a1c2E36870A006E816930d9f55DF0a72a13;
address constant GAUGE_2 = 0xc7e5FE004416A96Cb2C7D6440c28aE92262f7695;
address constant LM_MULTISIG = 0xc38c5f97B34E175FFd35407fc91a937300E33860;
address constant AUTHORIZER_ADAPTER = 0xAB093cd16e765b5B23D34030aaFaF026558e0A19;
address constant TEST_TOKEN_WHALE = 0xF977814e90dA44bFA03b6295A0616a897441aceC;

// token address constants
address constant USDT = 0xc2132D05D31c914a87C6611C10748AEb04B58e8F;
address constant USDC = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174;

// agents
address constant KEEPER = address(5);

address[] KEEPER_ADDRESSES = new address[](1);

// dummy constants
uint256 MIN_WAIT_PERIOD_SECONDS = 1 days;
uint256 MAX_INJECTION_AMOUNT = 1_000e18;
address OWNER = address(56565);

event InjectorCreated(
address indexed injector, address[] keeperAddresses, address injectTokenAddress, address owner
);

function setUp() public {
vm.createSelectFork("polygon");

injector = new ChildChainGaugeInjectorV2();
factory = new ChildChainGaugeInjectorV2Factory(address(injector));

assert(factory.implementation() == address(injector));
}

function _deployDummyInjector() internal returns (address injectorDeployed_) {
KEEPER_ADDRESSES[0] = KEEPER;

// check: event emitted
vm.expectEmit(false, true, true, true); // @note topic0 is not checkeds
emit InjectorCreated(address(0), KEEPER_ADDRESSES, USDT, OWNER);

injectorDeployed_ =
factory.createInjector(KEEPER_ADDRESSES, MIN_WAIT_PERIOD_SECONDS, USDT, MAX_INJECTION_AMOUNT, OWNER);
}

function _enableInjectorAsDistributor(address _injector) internal {
IChildChainGauge gaugeFirst = IChildChainGauge(GAUGE);
IChildChainGauge gaugeSecond = IChildChainGauge(GAUGE_2);

vm.prank(gaugeFirst.authorizer_adaptor());
gaugeFirst.add_reward(USDT, _injector);
vm.prank(gaugeSecond.authorizer_adaptor());
gaugeSecond.add_reward(USDT, _injector);
}
}
26 changes: 26 additions & 0 deletions foundry_test/FactoryTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

import {BaseFixture} from "./BaseFixture.sol";

import {ChildChainGaugeInjectorV2} from "../contracts/ChildChainGaugeInjectorV2.sol";

contract FactoryTest is BaseFixture {
function testCreateInjector() public {
// 1. create a new injector via factory
address injectorDeployed = _deployDummyInjector();
ChildChainGaugeInjectorV2 injectorFactoryDeployed = ChildChainGaugeInjectorV2(injectorDeployed);

// 2. asserts:
// 2.1. check `getDeployedInjectors` returns the correct number of injectors
address[] memory injectorsDeployed = factory.getDeployedInjectors();
assertEq(injectorsDeployed.length, 1);
assertEq(injectorsDeployed[0], injectorDeployed);

// 2.2. check params of the injector correctness at deployment time
assertEq(injectorFactoryDeployed.owner(), OWNER);
assertEq(injectorFactoryDeployed.getKeeperAddresses()[0], KEEPER);
assertEq(injectorFactoryDeployed.MinWaitPeriodSeconds(), MIN_WAIT_PERIOD_SECONDS);
assertEq(injectorFactoryDeployed.InjectTokenAddress(), USDT);
}
}
154 changes: 154 additions & 0 deletions foundry_test/UncoveredLinesTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

import {BaseFixture} from "./BaseFixture.sol";

import "../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {IChildChainGauge} from "../contracts/interfaces/balancer/IChildChainGauge.sol";

import {ChildChainGaugeInjectorV2} from "../contracts/ChildChainGaugeInjectorV2.sol";

/// @notice Scope of the file is to test uncovered reverts, setters and getters:
/// 1. revert: `InjectorNotDistributor`
/// 2. revert: `ExceedsTotalInjectorProgramBudget`
/// 3. revert: `OnlyKeepers`
/// 4. getter: `getBalanceDelta` (cases-> deficit, exact balance and surplus)
/// 5. getter: `getFullSchedule` (check: expected values)
// @audit https://github.com/BalancerMaxis/ChildGaugeInjectorV2/issues/31 ?
contract UncoveredLinesTest is BaseFixture {
function test_revertWhen_InjectorNotDistributor() public {
ChildChainGaugeInjectorV2 inj = ChildChainGaugeInjectorV2(_deployDummyInjector());

address[] memory recipients = new address[](1);
recipients[0] = GAUGE;

vm.prank(inj.owner());
vm.expectRevert(abi.encodeWithSelector(ChildChainGaugeInjectorV2.InjectorNotDistributor.selector, GAUGE, USDT));
inj.addRecipients(recipients, 50e18, 4, uint56(block.timestamp + 1 days));
}

function test_revertWhen_ExceedsTotalInjectorProgramBudget() public {
ChildChainGaugeInjectorV2 inj = ChildChainGaugeInjectorV2(_deployDummyInjector());

_enableInjectorAsDistributor(address(inj));

uint256 dummyMaxTotalDue = 100e18;
vm.startPrank(inj.owner());
inj.setMaxTotalDue(dummyMaxTotalDue);
assertEq(inj.MaxTotalDue(), dummyMaxTotalDue);

address[] memory recipients = new address[](2);
recipients[0] = GAUGE;
recipients[1] = GAUGE_2;

uint256 amountPerPeriod = 250e18;
vm.expectRevert(
abi.encodeWithSelector(
ChildChainGaugeInjectorV2.ExceedsTotalInjectorProgramBudget.selector, amountPerPeriod
)
);
inj.addRecipients(recipients, 250e18, 1, uint56(block.timestamp + 1 days));
}

function test_revertWhen_NotKeepers() public {
address NOT_KEEPER_AGENT = address(543485484845);

ChildChainGaugeInjectorV2 inj = ChildChainGaugeInjectorV2(_deployDummyInjector());

address[] memory needsFunding = new address[](1);
needsFunding[0] = GAUGE;

vm.prank(NOT_KEEPER_AGENT);
vm.expectRevert(abi.encodeWithSelector(ChildChainGaugeInjectorV2.OnlyKeepers.selector, NOT_KEEPER_AGENT));
inj.performUpkeep(abi.encode(needsFunding));
}

function testGetBalance_When_Deficit() public {
ChildChainGaugeInjectorV2 inj = ChildChainGaugeInjectorV2(_deployDummyInjector());

_enableInjectorAsDistributor(address(inj));

address[] memory recipients = new address[](1);
recipients[0] = GAUGE;
uint256 amountPerPeriod = 250e18;
vm.prank(inj.owner());
inj.addRecipients(recipients, amountPerPeriod, 1, uint56(block.timestamp + 1 days));

// send partially
deal(USDT, address(inj), 50e18);

int256 expectedDeficit = -1 * int256(amountPerPeriod - IERC20(USDT).balanceOf(address(inj)));

// should encounter DEFICIT
assertEq(inj.getBalanceDelta(), expectedDeficit);
}

function testGetBalance_When_ExactBalance() public {
ChildChainGaugeInjectorV2 inj = ChildChainGaugeInjectorV2(_deployDummyInjector());

_enableInjectorAsDistributor(address(inj));

address[] memory recipients = new address[](1);
recipients[0] = GAUGE;
uint256 amountPerPeriod = 250e18;
vm.prank(inj.owner());
inj.addRecipients(recipients, amountPerPeriod, 1, uint56(block.timestamp + 1 days));

// send full `amountPerPeriod`
deal(USDT, address(inj), amountPerPeriod);

// should encounter EXACT
assertEq(inj.getBalanceDelta(), 0);
}

function testGetBalance_When_Surplus() public {
ChildChainGaugeInjectorV2 inj = ChildChainGaugeInjectorV2(_deployDummyInjector());

_enableInjectorAsDistributor(address(inj));

address[] memory recipients = new address[](1);
recipients[0] = GAUGE;
uint256 amountPerPeriod = 250e18;
vm.prank(inj.owner());
inj.addRecipients(recipients, amountPerPeriod, 1, uint56(block.timestamp + 1 days));

// send full `amountPerPeriod` * 3
deal(USDT, address(inj), amountPerPeriod * 3);

int256 expectedSurplus = int256(IERC20(USDT).balanceOf(address(inj)) - amountPerPeriod);
// should encounter SURPLUS
assertEq(inj.getBalanceDelta(), expectedSurplus);
}

function testGetFullSchedule() public {
ChildChainGaugeInjectorV2 inj = ChildChainGaugeInjectorV2(_deployDummyInjector());

_enableInjectorAsDistributor(address(inj));

address[] memory recipients = new address[](2);
recipients[0] = GAUGE;
recipients[1] = GAUGE_2;

vm.prank(inj.owner());
inj.addRecipients(recipients, 250e18, 5, uint56(block.timestamp + 1 days));

(
address[] memory gauges,
uint256[] memory amountsPerPeriod,
uint8[] memory maxPeriods,
uint8[] memory currentPeriods,
uint56[] memory lastTimestamps,
uint56[] memory doNotStartBeforeTimestamps
) = inj.getFullSchedule();

for (uint256 i = 0; i < gauges.length; i++) {
assertEq(gauges[i], recipients[i]);
assertEq(amountsPerPeriod[i], 250e18);
assertEq(maxPeriods[i], 5);
assertEq(currentPeriods[i], 0);
assertEq(lastTimestamps[i], uint56(0));
assertEq(doNotStartBeforeTimestamps[i], uint56(block.timestamp + 1 days));
}
}
}
1 change: 1 addition & 0 deletions lib/forge-std
Submodule forge-std added at 035de3
Loading