Skip to content

Commit

Permalink
test: add foundry tests (#196)
Browse files Browse the repository at this point in the history
* chore: setup foundry test

* chore: add remappings

* forge install: erc4626-tests

* test: add foundry fuzzing tests

* fix: max uint deposit limit

* fix: test strategy

* fix: foundry runner

* fix: clamp overflow

* fix: default tests

* chore: clean up linting

* fix: new strategy version
  • Loading branch information
Schlagonia authored Feb 3, 2024
1 parent c23843d commit c8fab70
Show file tree
Hide file tree
Showing 20 changed files with 439 additions and 16 deletions.
65 changes: 65 additions & 0 deletions .github/workflows/foundry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Foundry tests

on:
push:
branches:
- master
pull_request:

concurrency:
group: ${{github.workflow}}-${{github.ref}}
cancel-in-progress: true

jobs:
unit:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- ubuntu-latest
architecture:
- "x64"
python-version:
- "3.10"
node_version:
- 16

steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: recursive

- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.architecture }}

- name: Install Ape
uses: ApeWorX/[email protected]
with:
python-version: '3.10'

- name: install vyper
run: pip install git+https://github.com/vyperlang/vyper

- name: Compile contracts
# Compile Ape contracts to get dependencies
run: ape compile --force --size

- name: Install Vyper
run: pip install vyper==0.3.7

- name: Use Node.js ${{ matrix.node_version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node_version }}

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Foundry tests
run: forge test -vvv
2 changes: 1 addition & 1 deletion .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ jobs:
run: pip install -r requirements.txt

- name: Run black
run: black --check --include "(tests|scripts)" .
run: black --check .
1 change: 0 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ jobs:
- uses: ApeWorX/[email protected]
with:
python-version: '3.10'
ape-version-pin: "==0.7.0"

- name: install vyper
run: pip install git+https://github.com/vyperlang/vyper
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
pyenv.cfg
vyper_git_commithash.txt
bin/
lib/
cache/
out/
share/
build/
include/
Expand Down
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/erc4626-tests"]
path = lib/erc4626-tests
url = https://github.com/a16z/erc4626-tests
2 changes: 1 addition & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
"not-rely-on-time": "off",
"private-vars-leading-underscore": "warn",
"reason-string": ["warn", { "maxLength": 64 }],
"yearn/underscore-function-args": "error"
"yearn/underscore-function-args": "off"
}
}
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ This repository runs on [ApeWorx](https://www.apeworx.io/). A python based devel

You will need:
- Python 3.8 or later
- Vyper 0.3.7
- [Vyper 0.3.7](https://docs.vyperlang.org/en/stable/installing-vyper.html)
- [Foundry](https://book.getfoundry.sh/getting-started/installation)
- Linux or macOS
- Windows: Install Windows Subsystem Linux (WSL) with Python 3.8 or later
- [Hardhat](https://hardhat.org/) installed globally
Expand All @@ -24,7 +25,7 @@ You will need:
Fork the repository and clone onto your local device

```
git clone https://github.com/user/yearn-vaults-v3
git clone --recursive https://github.com/user/yearn-vaults-v3
cd yearn-vaults-v3
```

Expand Down Expand Up @@ -60,6 +61,14 @@ and test smart contracts with:
ape test
```

To run the Foundry tests

NOTE: You will need to first compile with Ape before running foundry tests.
```
forge test
```


### To make a contribution please follow the [guidelines](https://github.com/yearn/yearn-vaults-v3/bloc/master/CONTRIBUTING.md)

See the ApeWorx [documentation](https://docs.apeworx.io/ape/stable/) and [github](https://github.com/ApeWorX/ape) for more information.
Expand Down
5 changes: 4 additions & 1 deletion contracts/VaultV3.vy
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,11 @@ def _max_deposit(receiver: address) -> uint256:
return IDepositLimitModule(deposit_limit_module).available_deposit_limit(receiver)

# Else use the standard flow.
_total_assets: uint256 = self._total_assets()
_deposit_limit: uint256 = self.deposit_limit
if (_deposit_limit == max_value(uint256)):
return _deposit_limit

_total_assets: uint256 = self._total_assets()
if (_total_assets >= _deposit_limit):
return 0

Expand Down
3 changes: 2 additions & 1 deletion contracts/test/mocks/ERC4626/LossyStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ contract ERC4626LossyStrategy is MockTokenizedStrategy {
address public yieldSource;

constructor(
address _factory,
address _asset,
string memory _name,
address _management,
address _keeper,
address _vault
) MockTokenizedStrategy(_asset, _name, _management, _keeper) {
) MockTokenizedStrategy(_factory, _asset, _name, _management, _keeper) {
yieldSource = address(new YieldSource(_asset));
ERC20(_asset).safeApprove(yieldSource, type(uint256).max);
// So we can record losses when it happens.
Expand Down
5 changes: 3 additions & 2 deletions contracts/test/mocks/ERC4626/MockTokenizedStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ contract MockTokenizedStrategy is TokenizedStrategy {
uint256 public maxDebt = type(uint256).max;

constructor(
address _factory,
address _asset,
string memory _name,
address _management,
address _keeper
) {
) TokenizedStrategy(_factory) {
// Cache storage pointer
StrategyData storage S = _strategyStorage();

Expand All @@ -24,7 +25,7 @@ contract MockTokenizedStrategy is TokenizedStrategy {
S.decimals = ERC20(_asset).decimals();

// Set last report to this block.
S.lastReport = uint128(block.timestamp);
S.lastReport = uint96(block.timestamp);

// Set the default management address. Can't be 0.
require(_management != address(0), "ZERO ADDRESS");
Expand Down
26 changes: 26 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[profile.default]
src = 'contracts'
test = 'foundry_tests'
out = 'out'
libs = ['lib']

remappings = [
'forge-std/=lib/forge-std/src/',
'erc4626-tests/=lib/erc4626-tests/',
"@tokenized-strategy=contracts/.cache/tokenized-strategy/dev_302",
'@openzeppelin/contracts=contracts/.cache/openzeppelin/v4.9.5/',
]
fs_permissions = [{ access = "read", path = "./"}]

match_path = "foundry_tests/tests/*"
ffi = true

[fuzz]
runs = 250
max_test_rejects = 1_000_000

[invariant]
runs = 100
depth = 100

# See more config options https://github.com/gakonst/foundry/tree/master/config
43 changes: 43 additions & 0 deletions foundry_tests/tests/ERC4626Std.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

import "erc4626-tests/ERC4626.test.sol";

import {Setup} from "../utils/Setup.sol";

// SEE https://github.com/a16z/erc4626-tests
contract VaultERC4626StdTest is ERC4626Test, Setup {
function setUp() public override(ERC4626Test, Setup) {
super.setUp();
_underlying_ = address(asset);
_vault_ = address(vault);
_delta_ = 0;
_vaultMayBeEmpty = true;
_unlimitedAmount = true;
}

// NOTE: The following tests are relaxed to consider only smaller values (of type uint120),
// since the maxWithdraw(), and maxRedeem() functions fail with large values (due to overflow).

function test_maxWithdraw(Init memory init) public override {
init = clamp(init, type(uint120).max);
super.test_maxWithdraw(init);
}

function test_maxRedeem(Init memory init) public override {
init = clamp(init, type(uint120).max);
super.test_maxRedeem(init);
}

function clamp(
Init memory init,
uint max
) internal pure returns (Init memory) {
for (uint i = 0; i < N; i++) {
init.share[i] = init.share[i] % max;
init.asset[i] = init.asset[i] % max;
}
init.yield = init.yield % int(max);
return init;
}
}
86 changes: 86 additions & 0 deletions foundry_tests/utils/ExtendedTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.18;

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

contract ExtendedTest is Test {
// solhint-disable-next-line
function assertNeq(address a, address b) internal {
if (a == b) {
emit log("Error: a != b not satisfied [address]");
emit log_named_address(" Expected", b);
emit log_named_address(" Actual", a);
fail();
}
}

// @dev checks whether @a is within certain percentage of @b
// @a actual value
// @b expected value
// solhint-disable-next-line
function assertRelApproxEq(
uint256 a,
uint256 b,
uint256 maxPercentDelta
) internal virtual {
uint256 delta = a > b ? a - b : b - a;
uint256 maxRelDelta = b / maxPercentDelta;

if (delta > maxRelDelta) {
emit log("Error: a ~= b not satisfied [uint]");
emit log_named_uint(" Expected", b);
emit log_named_uint(" Actual", a);
emit log_named_uint(" Max Delta", maxRelDelta);
emit log_named_uint(" Delta", delta);
fail();
}
}

// Can be removed once https://github.com/dapphub/ds-test/pull/25 is merged and we update submodules, but useful for now
// solhint-disable-next-line
function assertApproxEq(
uint256 a,
uint256 b,
uint256 margin_of_error
) internal {
if (a > b) {
if (a - b > margin_of_error) {
emit log("Error a not equal to b");
emit log_named_uint(" Expected", b);
emit log_named_uint(" Actual", a);
fail();
}
} else {
if (b - a > margin_of_error) {
emit log("Error a not equal to b");
emit log_named_uint(" Expected", b);
emit log_named_uint(" Actual", a);
fail();
}
}
}

// solhint-disable-next-line
function assertApproxEq(
uint256 a,
uint256 b,
uint256 margin_of_error,
string memory err
) internal {
if (a > b) {
if (a - b > margin_of_error) {
emit log_named_string("Error", err);
emit log_named_uint(" Expected", b);
emit log_named_uint(" Actual", a);
fail();
}
} else {
if (b - a > margin_of_error) {
emit log_named_string("Error", err);
emit log_named_uint(" Expected", b);
emit log_named_uint(" Actual", a);
fail();
}
}
}
}
Loading

0 comments on commit c8fab70

Please sign in to comment.