Skip to content

Commit

Permalink
Merge pull request #14 from charlesndalton/migrate-foundry
Browse files Browse the repository at this point in the history
Migrate test suite to foundry
  • Loading branch information
charlesndalton authored Feb 24, 2024
2 parents 357a6f6 + 02fed4b commit f7719b5
Show file tree
Hide file tree
Showing 39 changed files with 809 additions and 2,293 deletions.
52 changes: 6 additions & 46 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,13 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1

- name: Cache compiler installations
uses: actions/cache@v2
with:
path: |
~/.solcx
~/.vvm
key: ${{ runner.os }}-compiler-cache

- name: Setup node.js
uses: actions/setup-node@v1
with:
node-version: '12.x'

- name: Install ganache
run: npm install -g [email protected]

- name: Set up python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9

- name: Set pip cache directory path
id: pip-cache-dir-path
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: Restore pip cache
uses: actions/cache@v2
id: pip-cache
- uses: actions/checkout@v3
with:
path: |
${{ steps.pip-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
restore-keys: |
${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
${{ runner.os }}-pip-
submodules: recursive

- name: Install python dependencies
run: pip install -r requirements-dev.txt
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Compile Code
run: brownie compile --size
- name: Run tests
run: forge test -vv --fork-url "https://mainnet.infura.io/v3/e74132f416d346308763252779d7df22" --etherscan-api-key "MW5CQA6QK5YMJXP2WP3RA36HM5A7RA1IHA" --ffi

- name: Run Tests
env:
ETHERSCAN_TOKEN: MW5CQA6QK5YMJXP2WP3RA36HM5A7RA1IHA
WEB3_INFURA_PROJECT_ID: b7821200399e4be2b4e5dbdf06fbe85b
run: brownie test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ build/
reports/
node_modules/
.DS_Store
cache
out
15 changes: 15 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/openzeppelin/openzeppelin-contracts
[submodule "lib/contracts"]
path = lib/contracts
url = https://github.com/cowprotocol/contracts
[submodule "lib/balancer-v2-monorepo"]
path = lib/balancer-v2-monorepo
url = https://github.com/balancer/balancer-v2-monorepo
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/surl-0.7"]
path = lib/surl-0.7
url = https://github.com/charlesndalton/surl-0.7
30 changes: 30 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# include .env file and export its env vars
# (-include to ignore error if it does not exist)
-include .env

# deps
update:; forge update

# Build & test
# change ETH_RPC_URL to another one (e.g., FTM_RPC_URL) for different chains
FORK_URL := ${ETH_RPC_URL}

.PHONY: build test test-operation test-all-operation trace test-contract trace-contract deploy test-local trace-local clean snapshot

# For deployments. Add all args without a comma
# ex: 0x316..FB5 "Name" 10
constructor-args :=

build :; forge build
test :; forge test -vv --fork-url ${FORK_URL} --etherscan-api-key ${ETHERSCAN_API_KEY} --ffi
test-operation :; forge test -vvv --fork-url ${FORK_URL} --etherscan-api-key ${ETHERSCAN_API_KEY} --match-test "testProfitableHarvest_AngleFarmingProfit"
test-all-operation :; forge test -vv --fork-url ${FORK_URL} --etherscan-api-key ${ETHERSCAN_API_KEY} --match-contract "StrategyOperation"
trace :; forge test -vvv --fork-url ${FORK_URL} --etherscan-api-key ${ETHERSCAN_API_KEY}
test-contract :; forge test -vv --fork-url ${FORK_URL} --match-contract $(contract) --etherscan-api-key ${ETHERSCAN_API_KEY}
trace-contract :; forge test -vvv --fork-url ${FORK_URL} --match-contract $(contract) --etherscan-api-key ${ETHERSCAN_API_KEY}
deploy :; forge create --rpc-url ${FORK_URL} --constructor-args ${constructor-args} --private-key ${PRIV_KEY} src/Strategy.sol:Strategy --etherscan-api-key ${ETHERSCAN_API_KEY} --verify
# local tests without fork
test-local :; forge test
trace-local :; forge test -vvv
clean :; forge clean
snapshot :; forge snapshot
26 changes: 0 additions & 26 deletions brownie-config.yml

This file was deleted.

12 changes: 0 additions & 12 deletions contracts/pricecheckers/IExpectedOutCalculator.sol

This file was deleted.

1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
profile = { default = { libs = ["node_modules", "lib"] } }
1 change: 1 addition & 0 deletions lib/balancer-v2-monorepo
Submodule balancer-v2-monorepo added at c5b42e
1 change: 1 addition & 0 deletions lib/contracts
Submodule contracts added at c0e7e4
1 change: 1 addition & 0 deletions lib/forge-std
Submodule forge-std added at 4513bc
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at 5c8746
1 change: 1 addition & 0 deletions lib/surl-0.7
Submodule surl-0.7 added at 443d6b
6 changes: 0 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,5 @@
"lint": "pretty-quick --pattern '**/*.*(sol|json)' --verbose",
"lint:check": "prettier --check **/*.sol **/*.json",
"lint:fix": "prettier --write **/*.sol **/*.json **/**/*.sol"
},
"husky": {
"hooks": {
"pre-commit": "yarn lint:fix",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}
4 changes: 4 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@openzeppelin/=lib/openzeppelin-contracts/
@cow-protocol/=lib/contracts/src
@balancer=lib/balancer-v2-monorepo/contracts
surl=lib/surl-0.7/src
4 changes: 0 additions & 4 deletions requirements-dev.txt

This file was deleted.

102 changes: 26 additions & 76 deletions contracts/Milkman.sol → src/Milkman.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity ^0.7.6;

pragma abicoder v2;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
Expand Down Expand Up @@ -30,11 +31,9 @@ contract Milkman {
);

/// @dev The contract Milkman needs to give allowance.
address internal constant VAULT_RELAYER =
0xC92E8bdf79f0507f65a392b0ab4667716BFE0110;
address internal constant VAULT_RELAYER = 0xC92E8bdf79f0507f65a392b0ab4667716BFE0110;
/// @dev The settlement contract's EIP-712 domain separator. Milkman uses this to verify that a provided UID matches provided order parameters.
bytes32 public constant DOMAIN_SEPARATOR =
0xc078f884a2676e1345748b1feace7b0abee5d00ecadb6e574dcdd109a63e8943;
bytes32 public constant DOMAIN_SEPARATOR = 0xc078f884a2676e1345748b1feace7b0abee5d00ecadb6e574dcdd109a63e8943;
bytes4 internal constant MAGIC_VALUE = 0x1626ba7e;
bytes4 internal constant NON_MAGIC_VALUE = 0xffffffff;
bytes32 internal constant ROOT_MILKMAN_SWAP_HASH =
Expand Down Expand Up @@ -67,38 +66,24 @@ contract Milkman {
address to,
address priceChecker,
bytes calldata priceCheckerData
) external {
)
external
{
require(address(this) == ROOT_MILKMAN, "!root_milkman"); // can't call `requestSwapExactTokensForTokens` from order contracts
require(priceChecker != address(0), "!price_checker"); // need to supply a valid price checker

address orderContract = createOrderContract();

fromToken.safeTransferFrom(msg.sender, orderContract, amountIn);

bytes32 _swapHash = keccak256(
abi.encode(
msg.sender,
to,
fromToken,
toToken,
amountIn,
priceChecker,
priceCheckerData
)
);
bytes32 _swapHash =
keccak256(abi.encode(msg.sender, to, fromToken, toToken, amountIn, priceChecker, priceCheckerData));

Milkman(orderContract).initialize(fromToken, _swapHash);

emit SwapRequested(
orderContract,
msg.sender,
amountIn,
address(fromToken),
address(toToken),
to,
priceChecker,
priceCheckerData
);
orderContract, msg.sender, amountIn, address(fromToken), address(toToken), to, priceChecker, priceCheckerData
);
}

function initialize(IERC20 fromToken, bytes32 _swapHash) external {
Expand All @@ -117,22 +102,15 @@ contract Milkman {
address to,
address priceChecker,
bytes calldata priceCheckerData
) external {
)
external
{
bytes32 _storedSwapHash = swapHash;

require(_storedSwapHash != ROOT_MILKMAN_SWAP_HASH, "!cancel_from_root");

bytes32 _calculatedSwapHash = keccak256(
abi.encode(
msg.sender,
to,
fromToken,
toToken,
amountIn,
priceChecker,
priceCheckerData
)
);
bytes32 _calculatedSwapHash =
keccak256(abi.encode(msg.sender, to, fromToken, toToken, amountIn, priceChecker, priceCheckerData));

require(_storedSwapHash == _calculatedSwapHash, "!valid_creator_proof");

Expand All @@ -141,45 +119,25 @@ contract Milkman {

/// @param orderDigest The EIP-712 signing digest derived from the order
/// @param encodedOrder Bytes-encoded order information, originally created by an off-chain bot. Created by concatening the order data (in the form of GPv2Order.Data), the price checker address, and price checker data.
function isValidSignature(bytes32 orderDigest, bytes calldata encodedOrder)
external
view
returns (bytes4)
{
function isValidSignature(bytes32 orderDigest, bytes calldata encodedOrder) external view returns (bytes4) {
bytes32 _storedSwapHash = swapHash;

require(
_storedSwapHash != ROOT_MILKMAN_SWAP_HASH,
"!is_valid_sig_from_root"
);
require(_storedSwapHash != ROOT_MILKMAN_SWAP_HASH, "!is_valid_sig_from_root");

(
GPv2Order.Data memory _order,
address _orderCreator,
address _priceChecker,
bytes memory _priceCheckerData
) = decodeOrder(encodedOrder);
(GPv2Order.Data memory _order, address _orderCreator, address _priceChecker, bytes memory _priceCheckerData) =
decodeOrder(encodedOrder);

require(_order.hash(DOMAIN_SEPARATOR) == orderDigest, "!match");

require(_order.kind == GPv2Order.KIND_SELL, "!kind_sell");

require(
_order.validTo >= block.timestamp + 5 minutes,
"expires_too_soon"
);
require(_order.validTo >= block.timestamp + 5 minutes, "expires_too_soon");

require(!_order.partiallyFillable, "!fill_or_kill");

require(
_order.sellTokenBalance == GPv2Order.BALANCE_ERC20,
"!sell_erc20"
);
require(_order.sellTokenBalance == GPv2Order.BALANCE_ERC20, "!sell_erc20");

require(
_order.buyTokenBalance == GPv2Order.BALANCE_ERC20,
"!buy_erc20"
);
require(_order.buyTokenBalance == GPv2Order.BALANCE_ERC20, "!buy_erc20");

require(
IPriceChecker(_priceChecker).checkPrice(
Expand Down Expand Up @@ -223,10 +181,8 @@ contract Milkman {
bytes memory _priceCheckerData
)
{
(_order, _orderCreator, _priceChecker, _priceCheckerData) = abi.decode(
_encodedOrder,
(GPv2Order.Data, address, address, bytes)
);
(_order, _orderCreator, _priceChecker, _priceCheckerData) =
abi.decode(_encodedOrder, (GPv2Order.Data, address, address, bytes));
}

function createOrderContract() internal returns (address _orderContract) {
Expand All @@ -236,15 +192,9 @@ contract Milkman {
assembly {
// EIP-1167 bytecode
let clone_code := mload(0x40)
mstore(
clone_code,
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
)
mstore(clone_code, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(clone_code, 0x14), addressBytes)
mstore(
add(clone_code, 0x28),
0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000
)
mstore(add(clone_code, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
_orderContract := create(0, clone_code, 0x37)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity ^0.7.6;

pragma abicoder v2;

interface EIP1271 {
function isValidSignature(bytes32, bytes calldata)
external
returns (bytes4);
function isValidSignature(bytes32, bytes calldata) external returns (bytes4);
}

/// Check that `isValidSignature` doesn't take up too much gas
contract GasChecker {
function isValidSignatureCheck(
address milkman,
bytes32 orderDigest,
bytes calldata encodedOrder
) external returns (bytes4) {
function isValidSignatureCheck(address milkman, bytes32 orderDigest, bytes calldata encodedOrder)
external
returns (bytes4)
{
return EIP1271(milkman).isValidSignature(orderDigest, encodedOrder);
}
}
Loading

0 comments on commit f7719b5

Please sign in to comment.