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: adding smock package #10

Merged
merged 9 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
- name: Install dependencies
run: yarn --frozen-lockfile --network-concurrency 1

- name: Create mock files with smock
run: yarn smock

- name: Precompile using 0.8.14 and via-ir=false
run: yarn build

Expand Down Expand Up @@ -58,6 +61,9 @@ jobs:
- name: Install dependencies
run: yarn --frozen-lockfile --network-concurrency 1

- name: Create mock files with smock
run: yarn smock

- name: Precompile using 0.8.14 and via-ir=false
run: yarn build

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ broadcast/*/*/*

# Out dir
out

# Smock dir
test/smock
5 changes: 4 additions & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"immutable-name-snakecase": "warn",
"avoid-low-level-calls": "off",
"no-console": "off",
"max-line-length": ["warn", 120]
"max-line-length": ["warn", 120],
"TODO": "REMOVE_TEMPORARY_LINTER_SETTINGS_BELOW",
"custom-errors": "warn",
"definition-name-capwords": "warn"
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"lint:sol-logic": "solhint -c .solhint.json 'src/**/*.sol' 'script/**/*.sol'",
"lint:sol-tests": "solhint -c .solhint.tests.json 'test/**/*.sol'",
"prepare": "husky install",
"smock": "smock-foundry --contracts src/contracts",

Choose a reason for hiding this comment

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

we should add this when building and running the tests

Copy link
Member Author

Choose a reason for hiding this comment

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

mhhh, i've added it on the CI, but i believe smock contracts are sth that are not gonna suffer much changes, and if it does all you get is a compilation error on a smock contract, and you'll know you must run yarn smock, but adding this routine to the yarn build seems like suboptimal time-wise, wdyt?

Choose a reason for hiding this comment

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

no worries, I was suggesting it so you can run yarn test:unit without having to run yarn smock before

"test": "forge test -vvv",
"test:integration": "forge test --match-contract Integration -vvv",
"test:unit": "forge test --match-contract Unit -vvv",
Expand All @@ -42,6 +43,7 @@
"@commitlint/cli": "19.3.0",
"@commitlint/config-conventional": "19.2.2",
"@defi-wonderland/natspec-smells": "1.1.1",
"@defi-wonderland/smock-foundry": "1.5.0",
"forge-std": "github:foundry-rs/forge-std#5475f85",
"husky": ">=8",
"lint-staged": ">=10",
Expand Down
4 changes: 2 additions & 2 deletions src/contracts/BFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@

// Builds new BPools, logging their addresses and providing `isBPool(address) -> (bool)`

import './BPool.sol';

Check warning on line 18 in src/contracts/BFactory.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

global import of path ./BPool.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

contract BFactory is BBronze {
event LOG_NEW_POOL(address indexed caller, address indexed pool);

Check warning on line 21 in src/contracts/BFactory.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

event name must be in CapWords

event LOG_BLABS(address indexed caller, address indexed blabs);

Check warning on line 23 in src/contracts/BFactory.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

event name must be in CapWords

mapping(address => bool) private _isBPool;
mapping(address => bool) internal _isBPool;

Check warning on line 25 in src/contracts/BFactory.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Function order is incorrect, state variable declaration can not go after event definition (line 23)

function isBPool(address b) external view returns (bool) {
return _isBPool[b];
Expand All @@ -36,7 +36,7 @@
return bpool;
}

address private _blabs;
address internal _blabs;

constructor() {
_blabs = msg.sender;
Expand All @@ -47,15 +47,15 @@
}

function setBLabs(address b) external {
require(msg.sender == _blabs, 'ERR_NOT_BLABS');

Check warning on line 50 in src/contracts/BFactory.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Use Custom Errors instead of require statements
emit LOG_BLABS(msg.sender, b);
_blabs = b;
}

function collect(BPool pool) external {
require(msg.sender == _blabs, 'ERR_NOT_BLABS');

Check warning on line 56 in src/contracts/BFactory.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Use Custom Errors instead of require statements
uint256 collected = IERC20(pool).balanceOf(address(this));
bool xfer = pool.transfer(_blabs, collected);
require(xfer, 'ERR_ERC20_FAILED');

Check warning on line 59 in src/contracts/BFactory.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Use Custom Errors instead of require statements
}
}
20 changes: 10 additions & 10 deletions src/contracts/BPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import './BToken.sol';
contract BPool is BBronze, BToken, BMath {
struct Record {
bool bound; // is token bound to pool
uint256 index; // private
uint256 index; // internal
uint256 denorm; // denormalized weight
uint256 balance;
}
Expand Down Expand Up @@ -55,20 +55,20 @@ contract BPool is BBronze, BToken, BMath {
_;
}

bool private _mutex;
bool internal _mutex;

address private _factory; // BFactory address to push token exitFee to
address private _controller; // has CONTROL role
bool private _publicSwap; // true if PUBLIC can call SWAP functions
address internal _factory; // BFactory address to push token exitFee to
address internal _controller; // has CONTROL role
bool internal _publicSwap; // true if PUBLIC can call SWAP functions

// `setSwapFee` and `finalize` require CONTROL
// `finalize` sets `PUBLIC can SWAP`, `PUBLIC can JOIN`
uint256 private _swapFee;
bool private _finalized;
uint256 internal _swapFee;
bool internal _finalized;

address[] private _tokens;
mapping(address => Record) private _records;
uint256 private _totalWeight;
address[] internal _tokens;
mapping(address => Record) internal _records;
uint256 internal _totalWeight;

constructor() {
_controller = msg.sender;
Expand Down
6 changes: 3 additions & 3 deletions src/contracts/BToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ abstract contract BTokenBase is BNum, IERC20 {
}

contract BToken is BTokenBase {
string private _name = 'Balancer Pool Token';
string private _symbol = 'BPT';
uint8 private _decimals = 18;
string internal _name = 'Balancer Pool Token';
string internal _symbol = 'BPT';
uint8 internal _decimals = 18;

function name() public view returns (string memory) {
return _name;
Expand Down
23 changes: 0 additions & 23 deletions src/contracts/Migrations.sol

This file was deleted.

140 changes: 92 additions & 48 deletions test/unit/BPool.t.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {BConst} from 'contracts/BConst.sol';
import {BPool} from 'contracts/BPool.sol';
import {MockBPool} from 'test/smock/MockBPool.sol';

import {BConst} from 'contracts/BConst.sol';
import {IERC20} from 'contracts/BToken.sol';
import {Test} from 'forge-std/Test.sol';
import {LibString} from 'solmate/utils/LibString.sol';
Expand All @@ -15,72 +17,57 @@ abstract contract BasePoolTest is Test, BConst, Utils {
using LibString for *;

uint256 public constant TOKENS_AMOUNT = 3;
uint256 internal constant _RECORD_MAPPING_SLOT_NUMBER = 10;
uint256 internal constant _TOKENS_ARRAY_SLOT_NUMBER = 9;

struct FuzzScenario {
uint256 poolAmountOut;
uint256 initPoolSupply;
uint256[TOKENS_AMOUNT] balance;
}

BPool public bPool;
MockBPool public bPool;
address[TOKENS_AMOUNT] public tokens;

modifier happyPath(FuzzScenario memory _fuzz) {
_assumeHappyPath(_fuzz);
_setValues(_fuzz);
_;
}

function setUp() public {
bPool = new BPool();
bPool = new MockBPool();

// Create fake tokens
for (uint256 i = 0; i < tokens.length; i++) {
tokens[i] = makeAddr(i.toString());
}
}

function _setValues(FuzzScenario memory _fuzz) internal {
// Create mocks
function _tokensToMemory() internal view returns (address[] memory _tokens) {
_tokens = new address[](tokens.length);
for (uint256 i = 0; i < tokens.length; i++) {
vm.mockCall(tokens[i], abi.encodeWithSelector(IERC20(tokens[i]).transfer.selector), abi.encode(true));
vm.mockCall(tokens[i], abi.encodeWithSelector(IERC20(tokens[i]).transferFrom.selector), abi.encode(true));
_tokens[i] = tokens[i];
}
}

// Set tokens
uint256 _arraySlotNumber = 9;
_writeArrayLengthToStorage(address(bPool), _arraySlotNumber, tokens.length); // write length
for (uint256 i = 0; i < tokens.length; i++) {
_writeAddressArrayItemToStorage(address(bPool), _arraySlotNumber, i, tokens[i]); // write token
}
function _mockTransfer(address _token) internal {
// TODO: add amount to transfer to check that it's called with the right amount
vm.mockCall(_token, abi.encodeWithSelector(IERC20(_token).transfer.selector), abi.encode(true));
}

// Set balances
uint256 _mappingSlotNumber = 10;
for (uint256 i = 0; i < tokens.length; i++) {
_writeStructPropertyAtAddressMapping(address(bPool), _mappingSlotNumber, tokens[i], 0, 1); // bound (1 == true)
_writeStructPropertyAtAddressMapping(address(bPool), _mappingSlotNumber, tokens[i], 3, _fuzz.balance[i]); // balance
}
function _mockTransferFrom(address _token) internal {
// TODO: add from and amount to transfer to check that it's called with the right params
vm.mockCall(_token, abi.encodeWithSelector(IERC20(_token).transferFrom.selector), abi.encode(true));
}

// Set public swap
_writeUintToStorage(address(bPool), 6, 0x0000000000000000000000010000000000000000000000000000000000000000);
// Set finalize
_writeUintToStorage(address(bPool), 8, 1);
// Set totalSupply
_writeUintToStorage(address(bPool), 2, _fuzz.initPoolSupply);
function _setTokens(address[] memory _tokens) internal {
bPool.set__tokens(_tokens);
}

function _assumeHappyPath(FuzzScenario memory _fuzz) internal pure {
vm.assume(_fuzz.initPoolSupply >= INIT_POOL_SUPPLY);
vm.assume(_fuzz.poolAmountOut >= _fuzz.initPoolSupply);
vm.assume(_fuzz.poolAmountOut < type(uint256).max / BONE);
function _setRecord(address _token, BPool.Record memory _record) internal {
bPool.set__records(_token, _record);
}

uint256 _ratio = (_fuzz.poolAmountOut * BONE) / _fuzz.initPoolSupply; // bdiv uses '* BONE'
uint256 _maxTokenAmountIn = type(uint256).max / _ratio;
function _setPublicSwap(bool _isPublicSwap) internal {
bPool.set__publicSwap(_isPublicSwap);
}

for (uint256 i = 0; i < _fuzz.balance.length; i++) {
vm.assume(_fuzz.balance[i] >= MIN_BALANCE);
vm.assume(_fuzz.balance[i] <= _maxTokenAmountIn); // L272
}
function _setFinalize(bool _isFinalized) internal {
bPool.set__finalized(_isFinalized);
}

function _setTotalSupply(uint256 _totalSupply) internal {
// NOTE: not in smock as it uses ERC20.totalSupply()
_writeUintToStorage(address(bPool), 2, _totalSupply);
}
}

Expand Down Expand Up @@ -329,7 +316,64 @@ contract BPool_Unit_GetSpotPriceSansFee is BasePoolTest {
}

contract BPool_Unit_JoinPool is BasePoolTest {
function test_HappyPath(FuzzScenario memory _fuzz) public happyPath(_fuzz) {
struct JoinPool_FuzzScenario {
uint256 poolAmountOut;
uint256 initPoolSupply;
uint256[TOKENS_AMOUNT] balance;
}

function _setValues(JoinPool_FuzzScenario memory _fuzz) internal {
// Create mocks
for (uint256 i = 0; i < tokens.length; i++) {
_mockTransfer(tokens[i]);
_mockTransferFrom(tokens[i]);
}

// Set tokens
_setTokens(_tokensToMemory());

// Set balances
for (uint256 i = 0; i < tokens.length; i++) {
_setRecord(
tokens[i],
BPool.Record({
bound: true,
index: 0, // NOTE: irrelevant for this method
denorm: 0, // NOTE: irrelevant for this method
balance: _fuzz.balance[i]
})
);
}

// Set public swap
_setPublicSwap(true);
// Set finalize
_setFinalize(true);
// Set totalSupply
_setTotalSupply(_fuzz.initPoolSupply);
}

function _assumeHappyPath(JoinPool_FuzzScenario memory _fuzz) internal pure {
vm.assume(_fuzz.initPoolSupply >= INIT_POOL_SUPPLY);
vm.assume(_fuzz.poolAmountOut >= _fuzz.initPoolSupply);
vm.assume(_fuzz.poolAmountOut < type(uint256).max / BONE);

uint256 _ratio = (_fuzz.poolAmountOut * BONE) / _fuzz.initPoolSupply; // bdiv uses '* BONE'
uint256 _maxTokenAmountIn = type(uint256).max / _ratio;

for (uint256 i = 0; i < _fuzz.balance.length; i++) {
vm.assume(_fuzz.balance[i] >= MIN_BALANCE);
vm.assume(_fuzz.balance[i] <= _maxTokenAmountIn); // L272
}
}

modifier happyPath(JoinPool_FuzzScenario memory _fuzz) {
_assumeHappyPath(_fuzz);
_setValues(_fuzz);
_;
}

function test_HappyPath(JoinPool_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
uint256[] memory maxAmountsIn = new uint256[](tokens.length);
for (uint256 i = 0; i < tokens.length; i++) {
maxAmountsIn[i] = type(uint256).max;
Expand Down
Loading
Loading