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

Test/demeter balancer #17

Merged
merged 67 commits into from
Nov 16, 2023
Merged
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
e14d16e
Initiated BaseFarm Test
bayou020 Sep 29, 2023
8f7d5af
small update
bayou020 Sep 29, 2023
f01fce3
started testing
bayou020 Oct 13, 2023
6723e0b
Merge branch 'wip/demeter-balancer' into test/demeter-balancer
bayou020 Oct 13, 2023
c507af9
created farms
bayou020 Oct 16, 2023
824c35a
added more test cases
bayou020 Oct 17, 2023
ad131e0
added coverage-minimum command
bayou020 Oct 17, 2023
4c7d346
Added more test cases
bayou020 Oct 18, 2023
0f06663
Added deposit tests
bayou020 Oct 18, 2023
e940d4a
Merge branch 'wip/demeter-balancer' into test/demeter-balancer
bayou020 Oct 18, 2023
de6ac9c
(test:Balancer E20 Farms) Coverage 80%
bayou020 Oct 18, 2023
f0d5fb9
added more BaseFarm tests
bayou020 Oct 25, 2023
aed1d66
Added Withdraw test cases
bayou020 Oct 27, 2023
4e9280a
Merge branch 'dev' into test/demeter-balancer
bayou020 Oct 31, 2023
6422afc
Merge branch 'dev' into test/demeter-balancer
bayou020 Nov 1, 2023
1e4b5f0
Update .gitignore
bayou020 Nov 1, 2023
fe62345
Resolved @parv3213 Comments
bayou020 Nov 1, 2023
68d18e5
PR Fixes
bayou020 Nov 1, 2023
1d0ce5b
Fix package.json
parv3213 Nov 1, 2023
f29735b
Format contracts
parv3213 Nov 1, 2023
9407aaf
Add husky to dev dependencies
parv3213 Nov 1, 2023
98007c2
fix(Demeter Base Farm): Updated Test Cases
bayou020 Nov 1, 2023
61a8103
fix: removed Console from imports
bayou020 Nov 1, 2023
72e1a14
fix: Removed Console import from BaseFarm test file
bayou020 Nov 1, 2023
d0790a6
Fix file paths
parv3213 Nov 2, 2023
fefa338
Format code
parv3213 Nov 2, 2023
24cf6a2
Fix CI
parv3213 Nov 2, 2023
03990e2
Fix gitignore
parv3213 Nov 2, 2023
f3437b9
forge install: forge-std
parv3213 Nov 2, 2023
4743b03
Simplify `foundry.toml`
parv3213 Nov 2, 2023
b4aa77e
Fix file import
parv3213 Nov 2, 2023
2a337e2
test(Balancer Test Cases): Added Missing test cases
bayou020 Nov 2, 2023
69fef42
Merge remote-tracking branch 'origin/dev' into test/demeter-balancer
parv3213 Nov 3, 2023
6f9dcf8
fix(BaseFarm Test Files): Fixed Repeated GetPoolAddress Method on Tes…
bayou020 Nov 3, 2023
bfd9849
Fix forge lib and removing unnecessary file
parv3213 Nov 3, 2023
57aeb9f
Fix solhint errors
parv3213 Nov 3, 2023
2ce2519
forge install: forge-std
parv3213 Nov 3, 2023
1bd337f
Merge remote-tracking branch 'origin/dev' into test/demeter-balancer
parv3213 Nov 3, 2023
8fdff76
Format code using `prettier` script
parv3213 Nov 3, 2023
d2a8a06
refactor(contracts): Make initiateCooldown and withdraw() accessible …
YashP16 Nov 3, 2023
b3b5d8f
test(utils): Add network configuration for tests
YashP16 Nov 3, 2023
75b4b73
test: Restructure test cases to make them modular
YashP16 Nov 3, 2023
cf26593
test: Remove old test cases
YashP16 Nov 3, 2023
479ed18
Fix import path in BaseE20Farm.t.sol
parv3213 Nov 4, 2023
b58db9a
test: Add setup modifier for tests
YashP16 Nov 6, 2023
356db8f
feat(BaseFarm): Added a Non Zero Amount Check
bayou020 Nov 6, 2023
d5762f9
test(BalancerFarm): Fixed not working test cases
bayou020 Nov 6, 2023
a7d24bf
Solhint Functions ordering fixes
bayou020 Nov 6, 2023
20c1b53
fix(Commit Conflicts): Fixed Commit Conflicts on test Cases
bayou020 Nov 6, 2023
e95d426
feat(Fuzzing tests): added fuzzing amounts tests
bayou020 Nov 9, 2023
3fe92aa
feat(Balancer Farm): Added Test Cases Imports
bayou020 Nov 10, 2023
fb9036b
feat(Base Farm): Added Missing test cases
bayou020 Nov 10, 2023
c897696
feat(BaseE20Farm): Added Reverting Test Cases
bayou020 Nov 10, 2023
19a3060
feat(BaseSetup): Fixed Owner= User Bug, Added More Actors , Added Fac…
bayou020 Nov 13, 2023
4023803
fix(BaseFarm): Owner actor shouldn't be user
bayou020 Nov 13, 2023
feb60b6
fix(FarmFactory): Added test cases for Farm Factory
bayou020 Nov 13, 2023
4db2fbb
fix(BaseE20Farm): Fixed test cases when User Prank is not owner
bayou020 Nov 13, 2023
dce3ff2
fix(BaseFarm): Minor Prank Fix
bayou020 Nov 13, 2023
ba1457d
Check for `_isNonZeroAmt` before `_farmNotClosed`
parv3213 Nov 14, 2023
8f5cdcd
Fix/improve tests
parv3213 Nov 14, 2023
3f388c3
fix(FarmFactory test cases): Solved Mehul's comments
bayou020 Nov 14, 2023
82f170f
fix(BaseFarm.t.sol ): Fix Parv's comments
bayou020 Nov 16, 2023
600ddcd
fix(BaseE20Farm.t.sol ): Added Maths tests
bayou020 Nov 16, 2023
cd905ee
fix(BalancerFarn.t.sol ): Fixed Parv's Comment
bayou020 Nov 16, 2023
8b8944d
fix(BaseFarm.t.sol ): Typo fix
bayou020 Nov 16, 2023
629e031
fix(BaseFarm.t.sol BaseFarm.t.sol): Fixes Parv's Comments
bayou020 Nov 16, 2023
5c5d25b
fix(BaseFarm.sol): Removed 0 amount Function check
bayou020 Nov 16, 2023
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
157 changes: 16 additions & 141 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,144 +1,19 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.vscode
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

.env
.history
.hypothesis/
build/
node_modules
deployed/*
!deployed/arbitrum-one
.vscode
.deps/
artifacts/

# Foundry
out/
forge-cache/
.env
coverage
coverage.json

# Foundry files
out
cache

# Brownie files
build
reports
deployed/arbitrum-main-fork
__pycache__
lcov.info
.DS_Store
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
7 changes: 7 additions & 0 deletions contracts/BaseFarm.sol
Original file line number Diff line number Diff line change
@@ -147,13 +147,17 @@ abstract contract BaseFarm is Ownable, ReentrancyGuard, Initializable {
error FarmIsPaused();
error NotTheTokenManager();
error InvalidAddress();
error ZeroAmount();
error InvalidCooldownPeriod();

// Disallow initialization of a implementation contract
constructor() {
_disableInitializers();
}

function initiateCooldown(uint256 _depositId) external virtual;
function withdraw(uint256 _depositId) external virtual;

/// @notice Claim rewards for the user.
/// @param _depositId The id of the deposit
function claimRewards(uint256 _depositId) external {
@@ -164,6 +168,9 @@ abstract contract BaseFarm is Ownable, ReentrancyGuard, Initializable {
/// @param _rwdToken the reward token's address.
/// @param _amount the amount of reward tokens to add.
function addRewards(address _rwdToken, uint256 _amount) external nonReentrant {
if (_amount == 0) {
revert ZeroAmount();
}
_farmNotClosed();
if (rewardData[_rwdToken].tknManager == address(0)) {
revert InvalidRewardToken();
4 changes: 2 additions & 2 deletions contracts/camelot/Demeter_CamelotFarm.sol
Original file line number Diff line number Diff line change
@@ -82,13 +82,13 @@ contract Demeter_CamelotFarm is BaseFarm, INFTHandler {
/// @notice Function to lock a staked deposit
/// @param _depositId The id of the deposit to be locked
/// @dev _depositId is corresponding to the user's deposit
function initiateCooldown(uint256 _depositId) external nonReentrant {
function initiateCooldown(uint256 _depositId) external override nonReentrant {
_initiateCooldown(_depositId);
}

/// @notice Function to withdraw a deposit from the farm.
/// @param _depositId The id of the deposit to be withdrawn
function withdraw(uint256 _depositId) external nonReentrant {
function withdraw(uint256 _depositId) external override nonReentrant {
_isValidDeposit(msg.sender, _depositId);
Deposit memory userDeposit = deposits[msg.sender][_depositId];

4 changes: 2 additions & 2 deletions contracts/e20-farms/BaseE20Farm.sol
Original file line number Diff line number Diff line change
@@ -122,13 +122,13 @@ contract BaseE20Farm is BaseFarm {
/// @notice Function to lock a staked deposit
/// @param _depositId The id of the deposit to be locked
/// @dev _depositId is corresponding to the user's deposit
function initiateCooldown(uint256 _depositId) external nonReentrant {
function initiateCooldown(uint256 _depositId) external override nonReentrant {
_initiateCooldown(_depositId);
}

/// @notice Function to withdraw a deposit from the farm.
/// @param _depositId The id of the deposit to be withdrawn
function withdraw(uint256 _depositId) external nonReentrant {
function withdraw(uint256 _depositId) external override nonReentrant {
_isValidDeposit(msg.sender, _depositId);
Deposit memory userDeposit = deposits[msg.sender][_depositId];

4 changes: 2 additions & 2 deletions contracts/uniswapV3/BaseUniV3Farm.sol
Original file line number Diff line number Diff line change
@@ -110,13 +110,13 @@ abstract contract BaseUniV3Farm is BaseFarm, IERC721Receiver {
/// @notice Function to lock a staked deposit
/// @param _depositId The id of the deposit to be locked
/// @dev _depositId is corresponding to the user's deposit
function initiateCooldown(uint256 _depositId) external nonReentrant {
function initiateCooldown(uint256 _depositId) external override nonReentrant {
_initiateCooldown(_depositId);
}

/// @notice Function to withdraw a deposit from the farm.
/// @param _depositId The id of the deposit to be withdrawn
function withdraw(uint256 _depositId) external nonReentrant {
function withdraw(uint256 _depositId) external override nonReentrant {
_isValidDeposit(msg.sender, _depositId);
Deposit memory userDeposit = deposits[msg.sender][_depositId];

2 changes: 0 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -2,7 +2,5 @@
src = "contracts"
out = "out"
libs = ["node_modules", "lib"]
test = "test"
cache_path = "forge-cache"

parv3213 marked this conversation as resolved.
Show resolved Hide resolved
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions lib/forge-std
Submodule forge-std added at f73c73
2,781 changes: 1 addition & 2,780 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions package.json
parv3213 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -14,7 +14,13 @@
"lint-test-contract": "solhint 'test/**/*.sol' -f table",
"prettier": "forge fmt",
"prettier-check": "forge fmt --check",
"slither-analyze": "slither ."
"slither-analyze": "slither .",
"test": "forge test -vvv",
"test-contract": "forge test -vvvv --match-contract",
"test-file": "forge test -vvv --match-path",
"test-function": "forge test -vvv --match-test",
"forge-coverage": "forge coverage --report lcov && lcov --remove ./lcov.info -o ./lcov.info 'test/*' && rm -rf ./coverage && genhtml lcov.info --output-dir coverage && mv lcov.info ./coverage",
"coverage-minimum": "forge coverage --ir-minimum --report lcov && lcov --remove ./lcov.info -o ./lcov.info 'test/*' && rm -rf ./coverage && genhtml lcov.info --output-dir coverage && mv lcov.info ./coverage"
},
"dependencies": {
"@openzeppelin/contracts": "^4.7.3",
@@ -23,7 +29,6 @@
"@uniswap/v3-periphery": "^1.3.0"
},
"devDependencies": {
"ethers": "^5.7.2",
"husky": "^8.0.3",
"npm-run-all": "^4.1.5",
"solhint": "^3.6.2"
2,168 changes: 0 additions & 2,168 deletions reports/coverage.json

This file was deleted.

2,055 changes: 0 additions & 2,055 deletions reports/coverage_camelot.json

This file was deleted.

2,843 changes: 0 additions & 2,843 deletions reports/coverage_uniV3.json

This file was deleted.

870 changes: 870 additions & 0 deletions test/BaseFarm.t.sol

Large diffs are not rendered by default.

185 changes: 185 additions & 0 deletions test/FarmFactory.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {BaseFarm, RewardTokenData} from "../contracts/BaseFarm.sol";
import {BaseE20Farm} from "../contracts/e20-farms/BaseE20Farm.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {TestNetworkConfig} from "./utils/TestNetworkConfig.t.sol";
import {FarmFactory} from "../contracts/FarmFactory.sol";
import {BaseFarmDeployer} from "../contracts/BaseFarmDeployer.sol";
import {UpgradeUtil} from "../test/utils/UpgradeUtil.t.sol";
import {console} from "forge-std/console.sol";

abstract contract FarmFactoryTest is TestNetworkConfig {
UpgradeUtil internal upgradeUtil;
FarmFactory public factoryImp;

event FarmRegistered(address indexed farm, address indexed creator, address indexed deployer);
event FarmDeployerRegistered(address deployer);
event FarmDeployerRemoved(address deployer);
event FeeParamsUpdated(address receiver, address token, uint256 amount);
event PrivilegeUpdated(address deployer, bool privilege);

modifier initialized() {
FarmFactory(factory).initialize(FACTORY_OWNER, USDS, 1e20);
_;
}

modifier deployerRegistered() {
FarmFactory(factory).registerFarmDeployer(owner);
_;
}

function setUp() public override {
super.setUp();
factoryImp = new FarmFactory();
upgradeUtil = new UpgradeUtil();
factory = createFactory();
}

function createFactory() public useKnownActor(FACTORY_OWNER) returns (address) {
address factoryProxy;
factoryImp = new FarmFactory();
upgradeUtil = new UpgradeUtil();
factoryProxy = upgradeUtil.deployErc1967Proxy(address(factoryImp));
return factoryProxy;
}
}

contract InitializeTest is FarmFactoryTest {
function test_revertsWhen_receiverIsZeroAddress() public useKnownActor(FACTORY_OWNER) {
vm.expectRevert(abi.encodeWithSelector(FarmFactory.InvalidAddress.selector));
FarmFactory(factory).initialize(address(0), USDS, 1e20);
}

function test_revertsWhen_tokenIsZeroAddress() public useKnownActor(FACTORY_OWNER) {
vm.expectRevert(abi.encodeWithSelector(FarmFactory.InvalidAddress.selector));
FarmFactory(factory).initialize(FACTORY_OWNER, address(0), 1e20);
}

function test_revertsWhen_feeAmountIsZero() public useKnownActor(FACTORY_OWNER) {
vm.expectRevert(abi.encodeWithSelector(FarmFactory.FeeCannotBeZero.selector));
FarmFactory(factory).initialize(FACTORY_OWNER, USDS, 0);
}

function test_init(uint256 feeAmt) public useKnownActor(FACTORY_OWNER) {
address feeReceiver = FACTORY_OWNER;
address feeToken = USDS;
feeAmt = bound(feeAmt, 1e18, 1e25);

address _feeReceiver;
address _feeToken;
uint256 _feeAmount;
vm.expectEmit(true, true, true, false);
emit FeeParamsUpdated(feeReceiver, feeToken, feeAmt);
FarmFactory(factory).initialize(feeReceiver, feeToken, feeAmt);
(_feeReceiver, _feeToken, _feeAmount) = FarmFactory(factory).getFeeParams();
assertEq(_feeReceiver, feeReceiver);
assertEq(_feeToken, feeToken);
assertEq(_feeAmount, feeAmt);
assertEq(FarmFactory(factory).owner(), currentActor);
}
}

contract RegisterFarmTest is FarmFactoryTest {
function test_revertsWhen_DeployerNotRegistered() public useKnownActor(FACTORY_OWNER) initialized {
vm.expectRevert(abi.encodeWithSelector(FarmFactory.DeployerNotRegistered.selector));
FarmFactory(factory).registerFarm(actors[6], actors[4]);
}

function test_registerFarm() public useKnownActor(FACTORY_OWNER) initialized deployerRegistered {
address farm = actors[6];
address creator = actors[5];

vm.startPrank(owner);
vm.expectEmit(true, true, false, true);
emit FarmRegistered(farm, creator, owner);
FarmFactory(factory).registerFarm(farm, creator);
assertEq(FarmFactory(factory).getFarmList()[0], farm);
}
}

contract RegisterFarmDeployerTest is FarmFactoryTest {
function test_revertsWhen_DeployerAddressIsZero() public useKnownActor(FACTORY_OWNER) initialized {
vm.expectRevert(abi.encodeWithSelector(FarmFactory.InvalidAddress.selector));
FarmFactory(factory).registerFarmDeployer(address(0));
}

function test_revertsWhen_DeployerIsAlreadyRegistered()
public
useKnownActor(FACTORY_OWNER)
initialized
deployerRegistered
{
vm.expectRevert(abi.encodeWithSelector(FarmFactory.DeployerAlreadyRegistered.selector));
FarmFactory(factory).registerFarmDeployer(owner);
}

function test_registerFarmDeployer() public useKnownActor(FACTORY_OWNER) initialized {
address deployer = actors[5];
vm.expectEmit(true, true, false, false);
emit FarmDeployerRegistered(deployer);
FarmFactory(factory).registerFarmDeployer(deployer);
assertEq(FarmFactory(factory).getFarmDeployerList()[0], deployer);
assertEq(FarmFactory(factory).deployerRegistered(deployer), true);
}
}

contract RemoveFarmDeployerTest is FarmFactoryTest {
function test_revertsWhen_invalidDeployerId() public useKnownActor(FACTORY_OWNER) initialized deployerRegistered {
uint16 deployerId = uint16(FarmFactory(factory).getFarmDeployerList().length);
vm.expectRevert(abi.encodeWithSelector(FarmFactory.InvalidDeployerId.selector));
FarmFactory(factory).removeDeployer(deployerId);
}

function test_removeLastDeployer() public useKnownActor(FACTORY_OWNER) initialized deployerRegistered {
FarmFactory(factory).registerFarmDeployer(actors[10]);
FarmFactory(factory).registerFarmDeployer(actors[11]);
uint16 deployerId = uint16(FarmFactory(factory).getFarmDeployerList().length - 1);
uint16 lengthBfr = uint16(FarmFactory(factory).getFarmDeployerList().length);
vm.expectEmit(true, true, false, false);
emit FarmDeployerRemoved(actors[11]);
FarmFactory(factory).removeDeployer(deployerId);
assertEq(FarmFactory(factory).getFarmDeployerList()[0], owner);
assertEq(FarmFactory(factory).getFarmDeployerList()[1], actors[10]);
assertEq(uint16(FarmFactory(factory).getFarmDeployerList().length), lengthBfr - 1); //check length after poping a deployer
}

function test_removeMiddleDeployer() public useKnownActor(FACTORY_OWNER) initialized deployerRegistered {
FarmFactory(factory).registerFarmDeployer(actors[10]);
FarmFactory(factory).registerFarmDeployer(actors[11]);
uint16 deployerId = uint16(FarmFactory(factory).getFarmDeployerList().length - 2);
uint16 lengthBfr = uint16(FarmFactory(factory).getFarmDeployerList().length);
vm.expectEmit(true, true, false, false);
emit FarmDeployerRemoved(actors[10]);
FarmFactory(factory).removeDeployer(deployerId);
assertEq(FarmFactory(factory).getFarmDeployerList()[0], owner);
assertEq(FarmFactory(factory).getFarmDeployerList()[1], actors[11]);
assertEq(uint16(FarmFactory(factory).getFarmDeployerList().length), lengthBfr - 1); //check length after poping a deployer
}
}

contract UpdatePrivilegeTest is FarmFactoryTest {
function test_revertsWhen_PrivilegeSameAsDesired()
public
useKnownActor(FACTORY_OWNER)
initialized
deployerRegistered
{
vm.expectRevert(abi.encodeWithSelector(FarmFactory.PrivilegeSameAsDesired.selector));
FarmFactory(factory).updatePrivilege(owner, false);
}

function test_revertsWhen_callerIsNotOwner() public useKnownActor(FACTORY_OWNER) initialized deployerRegistered {
vm.startPrank(owner);
vm.expectRevert("Ownable: caller is not the owner");
FarmFactory(factory).updatePrivilege(owner, false);
}

function test_updatePrivilege() public useKnownActor(FACTORY_OWNER) initialized deployerRegistered {
vm.expectEmit(true, true, false, false);
emit PrivilegeUpdated(owner, true);
FarmFactory(factory).updatePrivilege(owner, true);
assertEq(FarmFactory(factory).isPrivilegedDeployer(owner), true);
}
}
201 changes: 201 additions & 0 deletions test/e20-farms/BaseE20Farm.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {BaseFarmTest} from "../BaseFarm.t.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {BaseFarm} from "../../contracts/BaseFarm.sol";
import {BaseE20Farm} from "../../contracts/e20-farms/BaseE20Farm.sol";

abstract contract BaseE20FarmTest is BaseFarmTest {}

abstract contract IncreaseDepositTest is BaseE20FarmTest {
function test_revertsWhen_InvalidAmount() public depositSetup(lockupFarm, true) useKnownActor(user) {
address poolAddress = getPoolAddress();
uint256 amt = 0;

deal(poolAddress, currentActor, amt);
ERC20(poolAddress).approve(address(lockupFarm), amt);
vm.expectRevert(abi.encodeWithSelector(BaseE20Farm.InvalidAmount.selector));
BaseE20Farm(lockupFarm).increaseDeposit(0, amt);
}

function testFuzz_revertsWhen_farmIsClosed(uint256 amt) public depositSetup(lockupFarm, true) useKnownActor(user) {
address poolAddress = getPoolAddress();
vm.assume(amt > 100 * 10 ** ERC20(poolAddress).decimals() && amt <= 1000 * 10 ** ERC20(poolAddress).decimals());

deal(poolAddress, currentActor, amt);
ERC20(poolAddress).approve(address(lockupFarm), amt);
vm.startPrank(owner);
BaseE20Farm(lockupFarm).closeFarm();
vm.expectRevert(abi.encodeWithSelector(BaseFarm.FarmIsClosed.selector));
BaseE20Farm(lockupFarm).increaseDeposit(0, amt);
}

function testFuzz_revertsWhen_depositInCoolDown(uint256 amt)
public
depositSetup(lockupFarm, true)
useKnownActor(user)
{
address poolAddress = getPoolAddress();
vm.assume(amt > 100 * 10 ** ERC20(poolAddress).decimals() && amt <= 1000 * 10 ** ERC20(poolAddress).decimals());
BaseE20Farm(lockupFarm).initiateCooldown(0);
skip(86400 * 2);
deal(poolAddress, currentActor, amt);
ERC20(poolAddress).approve(address(lockupFarm), amt);
vm.expectRevert(abi.encodeWithSelector(BaseE20Farm.DepositInCooldown.selector));
BaseE20Farm(lockupFarm).increaseDeposit(0, amt);
}

function testFuzz_lockupFarm(uint256 amt) public depositSetup(lockupFarm, true) useKnownActor(user) {
address poolAddress = getPoolAddress();
vm.assume(amt > 100 * 10 ** ERC20(poolAddress).decimals() && amt <= 1000 * 10 ** ERC20(poolAddress).decimals());

deal(poolAddress, currentActor, amt);
uint256 usrBalanceBefore = ERC20(poolAddress).balanceOf(currentActor);
uint256 farmBalanceBefore = ERC20(poolAddress).balanceOf(lockupFarm);
ERC20(poolAddress).approve(address(lockupFarm), amt);
BaseE20Farm(lockupFarm).increaseDeposit(0, amt);
uint256 usrBalanceAfter = ERC20(poolAddress).balanceOf(currentActor);
uint256 farmBalanceAfter = ERC20(poolAddress).balanceOf(lockupFarm);
assertEq(usrBalanceAfter, usrBalanceBefore - amt);
assertEq(farmBalanceAfter, farmBalanceBefore + amt);
}

function testFuzz_nonLockupFarm(uint256 amt) public depositSetup(nonLockupFarm, false) useKnownActor(user) {
address poolAddress = getPoolAddress();
vm.assume(amt > 100 * 10 ** ERC20(poolAddress).decimals() && amt <= 1000 * 10 ** ERC20(poolAddress).decimals());

deal(poolAddress, currentActor, amt);
uint256 usrBalanceBefore = ERC20(poolAddress).balanceOf(currentActor);
uint256 farmBalanceBefore = ERC20(poolAddress).balanceOf(nonLockupFarm);
ERC20(poolAddress).approve(address(nonLockupFarm), amt);
BaseE20Farm(nonLockupFarm).increaseDeposit(0, amt);
uint256 usrBalanceAfter = ERC20(poolAddress).balanceOf(currentActor);
uint256 farmBalanceAfter = ERC20(poolAddress).balanceOf(nonLockupFarm);
assertEq(usrBalanceAfter, usrBalanceBefore - amt);
assertEq(farmBalanceAfter, farmBalanceBefore + amt);
}

function testMaths_updateSubscriptionForIncrease() public depositSetup(nonLockupFarm, false) useKnownActor(user) {
address[] memory farmRewardTokens = getRewardTokens(nonLockupFarm);
uint256 totalRewardClaimed = 0;
uint256 rewardRate = 1e16;
address poolAddress = getPoolAddress();
deposit(nonLockupFarm, false, 1e3);
uint256 time = 2 days;
uint256 amt = 1e3 * 10 ** ERC20(poolAddress).decimals();
uint256[][] memory rewardsForEachSubs1 = new uint256[][](1);
uint256[][] memory rewardsForEachSubs2 = new uint256[][](1);
skip(time);
vm.startPrank(user);
rewardsForEachSubs1[0] = BaseFarm(nonLockupFarm).computeRewards(currentActor, 0);
rewardsForEachSubs2[0] = BaseFarm(nonLockupFarm).computeRewards(currentActor, 1);
//since the Deposit amounts are the same, The reward amounts should be the same.

for (uint8 i = 0; i < farmRewardTokens.length; ++i) {
assertEq(rewardsForEachSubs1[0][i], rewardsForEachSubs2[0][i]);
}
deal(poolAddress, currentActor, amt);
ERC20(poolAddress).approve(address(nonLockupFarm), amt);

// We increased the first deposit by 100%
BaseE20Farm(nonLockupFarm).increaseDeposit(0, amt);
BaseFarm(nonLockupFarm).claimRewards(1);

//Check if all the rewards are distributed to the deposits
totalRewardClaimed += rewardsForEachSubs1[0][0] + rewardsForEachSubs2[0][0];
assertEq(totalRewardClaimed, time * rewardRate);

skip(time);
rewardsForEachSubs1[0] = BaseFarm(nonLockupFarm).computeRewards(currentActor, 0);
rewardsForEachSubs2[0] = BaseFarm(nonLockupFarm).computeRewards(currentActor, 1);

//The first Deposit amount is the double than the second one so the the ratio should be 2/3 and 1/3
for (uint8 i = 0; i < farmRewardTokens.length; ++i) {
assertEq(rewardsForEachSubs1[0][i], 2 * rewardsForEachSubs2[0][i]);
}
BaseFarm(nonLockupFarm).claimRewards(0);
BaseFarm(nonLockupFarm).claimRewards(1);

//Check if all the rewards are distributed to the deposits
totalRewardClaimed += rewardsForEachSubs1[0][0] + rewardsForEachSubs2[0][0];
assertEq(totalRewardClaimed, 2 * time * rewardRate);
}
}

abstract contract WithdrawPartiallyTest is BaseE20FarmTest {
function test_zeroAmount() public depositSetup(lockupFarm, true) useKnownActor(user) {
vm.expectRevert(abi.encodeWithSelector(BaseE20Farm.InvalidAmount.selector));
BaseE20Farm(lockupFarm).withdrawPartially(0, 0);
}

function test_revertsWhen_LockupFarm_PartialWithdrawNotPermitted()
public
depositSetup(lockupFarm, true)
useKnownActor(user)
{
skip(86400 * 7);
vm.expectRevert(abi.encodeWithSelector(BaseE20Farm.PartialWithdrawNotPermitted.selector));
BaseE20Farm(lockupFarm).withdrawPartially(0, 10000);
}

function test_revertsWhen_farmIsClosed() public depositSetup(nonLockupFarm, false) useKnownActor(owner) {
skip(86400 * 7);
BaseE20Farm(nonLockupFarm).closeFarm();
vm.startPrank(user);
vm.expectRevert(abi.encodeWithSelector(BaseFarm.FarmIsClosed.selector));
BaseE20Farm(nonLockupFarm).withdrawPartially(0, 10000);
}

function test_nonLockupFarm() public depositSetup(nonLockupFarm, false) useKnownActor(user) {
skip(86400 * 7);
BaseE20Farm(nonLockupFarm).computeRewards(currentActor, 0);
BaseE20Farm(nonLockupFarm).withdrawPartially(0, 10000);
}

function testMaths_updateSubscriptionForDecrease() public depositSetup(nonLockupFarm, false) useKnownActor(user) {
address[] memory farmRewardTokens = getRewardTokens(nonLockupFarm);
uint256 totalRewardClaimed = 0;
uint256 rewardRate = 1e16;
address poolAddress = getPoolAddress();
deposit(nonLockupFarm, false, 1e3);
uint256 time = 2 days;
uint256 amt = 1e3 * 10 ** ERC20(poolAddress).decimals();
uint256[][] memory rewardsForEachSubs1 = new uint256[][](1);
uint256[][] memory rewardsForEachSubs2 = new uint256[][](1);
skip(time);
vm.startPrank(user);
rewardsForEachSubs1[0] = BaseFarm(nonLockupFarm).computeRewards(currentActor, 0);
rewardsForEachSubs2[0] = BaseFarm(nonLockupFarm).computeRewards(currentActor, 1);
//since the Deposit amounts are the same, The reward amounts should be the same.

for (uint8 i = 0; i < farmRewardTokens.length; ++i) {
assertEq(rewardsForEachSubs1[0][i], rewardsForEachSubs2[0][i]);
}
deal(poolAddress, currentActor, amt);
ERC20(poolAddress).approve(address(nonLockupFarm), amt);

// We withdrew 50% of the deposit
BaseE20Farm(nonLockupFarm).withdrawPartially(0, amt / 2);
BaseFarm(nonLockupFarm).claimRewards(1);

//Check if all the rewards are distributed to the deposits
totalRewardClaimed += rewardsForEachSubs1[0][0] + rewardsForEachSubs2[0][0];
assertEq(totalRewardClaimed, time * rewardRate);

skip(time);
rewardsForEachSubs1[0] = BaseFarm(nonLockupFarm).computeRewards(currentActor, 0);
rewardsForEachSubs2[0] = BaseFarm(nonLockupFarm).computeRewards(currentActor, 1);

//The first Deposit amount is the half than the second one so the the ratio should be 1/3 and 2/3
for (uint8 i = 0; i < farmRewardTokens.length; ++i) {
assertEq(rewardsForEachSubs1[0][i], rewardsForEachSubs2[0][i] / 2);
}
BaseFarm(nonLockupFarm).claimRewards(0);
BaseFarm(nonLockupFarm).claimRewards(1);

//Check if all the rewards are distributed to the deposits
totalRewardClaimed += rewardsForEachSubs1[0][0] + rewardsForEachSubs2[0][0];
assertEq(totalRewardClaimed, 2 * time * rewardRate);
}
}
162 changes: 162 additions & 0 deletions test/e20-farms/balancer/BalancerFarm.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import "../../BaseFarm.t.sol";
import "../BaseE20Farm.t.sol";

import {Demeter_BalancerFarm} from "../../../contracts/e20-farms/balancer/Demeter_BalancerFarm.sol";
import {Demeter_BalancerFarm_Deployer} from "../../../contracts/e20-farms/balancer/Demeter_BalancerFarm_Deployer.sol";

struct JoinPoolRequest {
address[] assets;
uint256[] maxAmountsIn;
bytes userData;
bool fromInternalBalance;
}

interface IAsset {
// solhint-disable-previous-line no-empty-blocks
}

interface IBalancerVault {
enum PoolSpecialization {
GENERAL,
MINIMAL_SWAP_INFO,
TWO_TOKEN
}

function joinPool(bytes32 poolId, address sender, address recipient, JoinPoolRequest memory request)
external
payable;
function getPool(bytes32 poolId) external view returns (address, PoolSpecialization);

function getPoolTokens(bytes32 poolId)
external
view
returns (IERC20[] memory tokens, uint256[] memory balances, uint256 lastChangeBlock);
}

interface ICustomOracle {
function updateDIAParams(uint256 _weightDIA, uint128 _maxTime) external;

function getPrice() external view returns (uint256, uint256);
}

contract BalancerFarmTest is
DepositTest,
WithdrawTest,
ClaimRewardsTest,
GetRewardFundInfoTest,
InitiateCooldownTest,
AddRewardsTest,
SetRewardRateTest,
GetRewardBalanceTest,
GetNumSubscriptionsTest,
SubscriptionInfoTest,
UpdateTokenManagerTest,
FarmPauseSwitchTest,
UpdateFarmStartTimeTest,
UpdateCoolDownPeriodTest,
IncreaseDepositTest,
WithdrawPartiallyTest,
RecoverERC20Test,
RecoverRewardFundsTest,
_SetupFarmTest
{
// Define variables
bytes32 internal POOL_ID = 0x423a1323c871abc9d89eb06855bf5347048fc4a5000000000000000000000496; //Balancer Stable 4pool (4POOL-BPT)
Demeter_BalancerFarm_Deployer public balancerFarmDeployer;

function setUp() public override {
super.setUp();

vm.startPrank(PROXY_OWNER);
// Deploy and register farm deployer
FarmFactory factory = FarmFactory(DEMETER_FACTORY);
balancerFarmDeployer = new Demeter_BalancerFarm_Deployer(
DEMETER_FACTORY,
BALANCER_VAULT,
"Balancer Deployer"
);
factory.registerFarmDeployer(address(balancerFarmDeployer));

// Configure rewardTokens
rwdTokens.push(USDCe);
rwdTokens.push(DAI);

invalidRewardToken = USDT;

vm.stopPrank();

// Create and setup Farms
lockupFarm = createFarm(block.timestamp, true);
nonLockupFarm = createFarm(block.timestamp, false);
}

function createFarm(uint256 startTime, bool lockup) public override useKnownActor(owner) returns (address) {
address[] memory rewardToken = rwdTokens;
RewardTokenData[] memory rwdTokenData = new RewardTokenData[](
rewardToken.length
);
for (uint8 i = 0; i < rewardToken.length; ++i) {
rwdTokenData[i] = RewardTokenData(rewardToken[i], currentActor);
}
/// Create Farm
Demeter_BalancerFarm_Deployer.FarmData memory _data = Demeter_BalancerFarm_Deployer.FarmData({
farmAdmin: owner,
farmStartTime: startTime,
cooldownPeriod: lockup ? COOLDOWN_PERIOD : 0,
poolId: POOL_ID, //Balancer Stable 4pool (4POOL-BPT)
rewardData: rwdTokenData
});

// Approve Farm fee
IERC20(FEE_TOKEN()).approve(address(balancerFarmDeployer), 1e22);
address farm = balancerFarmDeployer.createFarm(_data);

assertEq(Demeter_BalancerFarm(farm).FARM_ID(), "Demeter_BalancerV2_v1");

return farm;
}

/// @notice Farm specific deposit logic
function deposit(address farm, bool locked, uint256 baseAmt) public override useKnownActor(user) {
assertEq(currentActor, actors[0], "Wrong actor");
address poolAddress = getPoolAddress();
uint256 amt = baseAmt * 10 ** ERC20(poolAddress).decimals();
deal(poolAddress, currentActor, amt);
ERC20(poolAddress).approve(address(farm), amt);
uint256 usrBalanceBefore = ERC20(poolAddress).balanceOf(currentActor);
uint256 farmBalanceBefore = ERC20(poolAddress).balanceOf(farm);
vm.expectEmit(true, true, false, true);
emit Deposited(currentActor, locked, BaseFarm(farm).getNumDeposits(currentActor) + 1, amt);
Demeter_BalancerFarm(farm).deposit(amt, locked);
uint256 usrBalanceAfter = ERC20(poolAddress).balanceOf(currentActor);
uint256 farmBalanceAfter = ERC20(poolAddress).balanceOf(farm);
assertEq(usrBalanceAfter, usrBalanceBefore - amt);
assertEq(farmBalanceAfter, farmBalanceBefore + amt);
}

/// @notice Farm specific deposit logic
function deposit(address farm, bool locked, uint256 baseAmt, bytes memory revertMsg)
public
override
useKnownActor(user)
{
address poolAddress = getPoolAddress();
uint256 amt = baseAmt * 10 ** ERC20(poolAddress).decimals();
deal(poolAddress, currentActor, amt);
ERC20(poolAddress).approve(address(farm), amt);

vm.expectRevert(revertMsg);
Demeter_BalancerFarm(farm).deposit(amt, locked);
}

function getPoolAddress() public view override returns (address) {
address poolAddress;
(poolAddress,) = IBalancerVault(BALANCER_VAULT).getPool(POOL_ID);
return poolAddress;
}
}
58 changes: 58 additions & 0 deletions test/utils/BaseSetup.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {Test} from "forge-std/Test.sol";
import {BaseFarm} from "../../contracts/BaseFarm.sol";
import {FarmFactory} from "../../contracts/FarmFactory.sol";
import {BaseFarmDeployer} from "../../contracts/BaseFarmDeployer.sol";
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";

abstract contract BaseSetup is Test {
// Define global constants | Test config
// @dev Make it 0 to test on latest
uint256 public constant NUM_ACTORS = 12;
uint256 public constant GAS_LIMIT = 1000000000;

// Define Demeter constants here
address internal PROXY_OWNER;
address internal FACTORY_OWNER;
address internal PROXY_ADMIN;
address internal DEMETER_FACTORY;

// Define fork networks
uint256 internal forkCheck;

address public owner;
address public factory;
address[] public actors;
address internal currentActor;

/// @notice Get a pre-set address for prank
/// @param actorIndex Index of the actor
modifier useActor(uint256 actorIndex) {
currentActor = actors[bound(actorIndex, 0, actors.length - 1)];
vm.startPrank(currentActor);
_;
vm.stopPrank();
}

/// @notice Start a prank session with a known user addr
modifier useKnownActor(address user) {
currentActor = user;
vm.startPrank(currentActor);
_;
vm.stopPrank();
}

/// @notice Initialize global test configuration.
function setUp() public virtual {
/// @dev Initialize actors for testing.
string memory mnemonic = vm.envString("TEST_MNEMONIC");
for (uint32 i = 0; i < NUM_ACTORS; ++i) {
(address act,) = deriveRememberKey(mnemonic, i);
actors.push(act);
}
owner = actors[4];
FACTORY_OWNER = actors[5];
}
}
11 changes: 11 additions & 0 deletions test/utils/TestNetworkConfig.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {Arbitrum} from "./networkConfig/Arbitrum.t.sol";

// Select the test network configuration
abstract contract TestNetworkConfig is Arbitrum {
function setUp() public virtual override {
super.setUp();
}
}
22 changes: 22 additions & 0 deletions test/utils/UpgradeUtil.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//SPDX-License-Identifier: Unlicensed
pragma solidity 0.8.16;

import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

contract UpgradeUtil {
ProxyAdmin public proxyAdmin;

constructor() {
proxyAdmin = new ProxyAdmin();
}

function deployErc1967Proxy(address impl) public returns (address) {
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
impl,
address(proxyAdmin),
""
);
return address(proxy);
}
}
71 changes: 71 additions & 0 deletions test/utils/networkConfig/Arbitrum.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {BaseSetup} from "../BaseSetup.t.sol";
import {INetworkConfig} from "./INetworkConfig.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

interface IVault {
function mintBySpecifyingCollateralAmt(
address _collateral,
uint256 _collateralAmt,
uint256 _minUSDSAmt,
uint256 _maxSPAburnt,
uint256 _deadline
) external;
}

abstract contract Arbitrum is BaseSetup, INetworkConfig {
// Tokens
address public constant SPA = 0x5575552988A3A80504bBaeB1311674fCFd40aD4B;
address public constant USDS = 0xD74f5255D557944cf7Dd0E45FF521520002D5748;
address public constant DAI = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1;
address public constant USDCe = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8;
address public constant USDT = 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9;

// Demeter constants
// @note Add only demeter related constants and configurations
address public constant SPA_REWARD_MANAGER = 0x432c3BcdF5E26Ec010dF9C1ddf8603bbe261c188;
address public constant USDS_VAULT = 0xF783DD830A4650D2A8594423F123250652340E3f;

// Farm constants
// @note Add only specific farm related params, try to group them together
// Balancer
address public constant BALANCER_VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;

function fundFeeToken() public useKnownActor(owner) {
uint256 amt = 1e22;
deal(USDCe, currentActor, amt);
IERC20(USDCe).approve(USDS_VAULT, amt);
IVault(USDS_VAULT).mintBySpecifyingCollateralAmt(USDCe, amt, 0, 0, block.timestamp + 1200);
}

function setForkNetwork() public override {
uint256 forkBlock = vm.envUint("FORK_BLOCK");
string memory arbRpcUrl = vm.envString("ARB_URL");
forkCheck = vm.createFork(arbRpcUrl);
vm.selectFork(forkCheck);
if (forkBlock != 0) vm.rollFork(forkBlock);
}

function setUp() public virtual override {
super.setUp();
setForkNetwork();

fundFeeToken();

// ** Setup global addresses ** //
// Demeter addresses
PROXY_OWNER = 0x6d5240f086637fb408c7F727010A10cf57D51B62;
PROXY_ADMIN = 0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25;
DEMETER_FACTORY = 0xC4fb09E0CD212367642974F6bA81D8e23780A659;
}

function FEE_TOKEN() public pure override returns (address) {
return USDS;
}

function NETWORK_ID() public pure override returns (string memory) {
return "Arbitrum";
}
}
10 changes: 10 additions & 0 deletions test/utils/networkConfig/INetworkConfig.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

interface INetworkConfig {
function setForkNetwork() external;

function FEE_TOKEN() external pure returns (address);

function NETWORK_ID() external pure returns (string memory);
}