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: add foundry tests #196

Merged
merged 11 commits into from
Feb 3, 2024
Merged
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
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
4 changes: 2 additions & 2 deletions ape-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ default_ecosystem: ethereum
dependencies:
- name: openzeppelin
github: OpenZeppelin/openzeppelin-contracts
ref: 4.7.3
ref: 4.9.5
- name: tokenized-strategy
github: yearn/tokenized-strategy
ref: dev_302
contracts_folder: src

solidity:
import_remapping:
- "@openzeppelin/contracts=openzeppelin/v4.7.3"
- "@openzeppelin/contracts=openzeppelin/v4.9.5"
- "@tokenized-strategy=tokenized-strategy/dev_302"

ethereum:
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
10 changes: 2 additions & 8 deletions contracts/test/ERC4626BaseStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ abstract contract ERC4626BaseStrategy is ERC4626 {
constructor(
address _vault,
address _asset
) ERC4626(IERC20Metadata(address(_asset))) {
) ERC4626(IERC20(address(_asset))) {
_initialize(_vault, _asset);
}

Expand All @@ -30,13 +30,7 @@ abstract contract ERC4626BaseStrategy is ERC4626 {
vault = _vault;
}

function decimals()
public
view
virtual
override(ERC20, IERC20Metadata)
returns (uint8)
{
function decimals() public view virtual override returns (uint8) {
return _decimals;
}

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
Loading