diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..61567d3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,87 @@ +# top-most EditorConfig file +root = true + +# All files +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +# Solidity +# https://github.com/sambacha/prettier-config-solidity +[*.sol] +indent_size = 4 +indent_style = space + +# q +# kdb+ +[*.q] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# Markdown +[*.{md,adoc,asciidoc}] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = false + +# Match nix files, set indent to spaces with width of two +[*.nix] +indent_style = space +indent_size = 2 + +# JavaScript, JSON, JSX, JavaScript Modules, TypeScript +# https://github.com/feross/standard +# https://prettier.io +[*.{cjs,js,json,jsx,mjs,ts,tsx,mts,cts}] +indent_size = 2 +indent_style = space + +# TOML +# https://github.com/toml-lang/toml/tree/master/examples +[*.toml] +indent_size = 2 +indent_style = space + +# YAML +# http://yaml.org/spec/1.2/2009-07-21/spec.html#id2576668 +[*.{yaml,yml}] +indent_size = 2 +indent_style = space + +# Shell +# https://google.github.io/styleguide/shell.xml#Indentation +[*.{bash,sh,zsh}] +indent_size = 2 +indent_style = space + +# confg + cfg +[*.{conf,cfg}] +charset = UTF-8 +end_of_line = LF +indent_size = 4 +indent_style = tab +insert_final_newline = true +tab_width = 4 +trim_trailing_whitespace = true + +# Match diffs, avoid to trim trailing whitespace +[*.{diff,patch}] +trim_trailing_whitespace = false + +# Ignore fixtures and vendored files +[{dist,artifacts,vendor,test/fixtures,tests_config,__snapshot__,}/**] +charset = unset +end_of_line = unset +indent_size = unset +indent_style = unset +insert_final_newline = unset +trim_trailing_spaces = unset \ No newline at end of file diff --git a/.gas-snapshot b/.gas-snapshot new file mode 100644 index 0000000..eebfdc9 --- /dev/null +++ b/.gas-snapshot @@ -0,0 +1,7 @@ +UnitTests:testAddLiquidity() (gas: 347004) +UnitTests:testCanAddRewards() (gas: 522056) +UnitTests:testCanCompoundFees() (gas: 899427) +UnitTests:testCannotCallbeforeInit() (gas: 2265365) +UnitTests:testFeesAccrue() (gas: 884410) +UnitTests:testNewUsersDontStealFees() (gas: 1145644) +UnitTests:testRemoveLiquidity() (gas: 575956) \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index d0b5421..b58bad4 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,22 +3,172 @@ solc_version = '0.8.25' src = 'src' out = 'out' libs = ['lib'] + +remappings = [ + "ds-test/=lib/forge-std/lib/ds-test/src/", + "forge-std/=lib/forge-std/src/", + "libsol/=lib/libsol/src/", + "solady/=lib/solady/", + "solmate/=lib/solmate/src/", +] + +# {@see {@link https://solidity-fr.readthedocs.io/fr/latest/using-the-compiler.html#input-description} +# includes the contract's metadata in the contract's json artifact +# includes the source mappings for compiled bytecode artifact +extra_output = ['irOptimized', 'evm.assembly', 'evm.bytecode', 'evm.bytecode.generatedSources'] +# emits the output selection as separate json artifact files +extra_output_files = ['metadata'] +names = false +sizes = false optimizer = true -optimizer_runs = 10_000_000 -# @see {@link https://github.com/foundry-rs/foundry/issues/4060} +optimizer_runs = 1_000 +via_ir = true +# {@see {@link https://github.com/foundry-rs/foundry/issues/4060} } +auto_detect_remappings = false +# Whether to store the referenced sources in the metadata as literal data. Helps with verification +use_literal_content = true +# The metadata hash is machine dependent. By default, this is set to none to allow for deterministic code. +# {@see {@link https://docs.soliditylang.org/en/latest/metadata.html} } bytecode_hash = "none" cbor_metadata = false -sparse_mode = false -build_info = true +# Only the required contracts/files will be selected to be included in solc's output selection. +sparse_mode = true +ast = false +isolate = false +create2_library_salt = "0x0000000000000000000000000000000000000000000000000000000000000000" +prague = false +unchecked_cheatcode_artifacts = false -via_ir = true -fuzz_runs = 500 -deny_warnings = false +[profile.ci] +optimizer = true +via_ir = false +fuzz_runs = 4_069 +force = true +verbosity = 4 +gas_reports = ["*"] +revert_strings = 'debug' +extra_output = [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", +] +# Environment: FOUNDRY_PROMPT_TIMEOUT +# The number of seconds to wait before vm.prompt reverts with a timeout. +# default = 120 +prompt_timeout = 30 +seed = '0x6900000000000000000000000000000000000000000000000000000000000000' +cache = true +cache_path = '.cache' + +show_progress = false +unchecked_cheatcode_artifacts = false + + +[[profile.default.fs_permissions]] +access = "read" +path = "out" + +[profile.default.rpc_storage_caching] +chains = "all" +endpoints = "all" + +[fmt] +# default 120 +line_length = 100 +# default 4 +tab_width = 2 +bracket_spacing = false +int_types = "long" +multiline_func_header = "attributes_first" +quote_style = "double" +number_underscore = "thousands" +single_line_statement_blocks = "preserve" +override_spacing = false +wrap_comments = true +ignore = ['*.mutant.sol', '*.vendor.sol'] +contract_new_lines = false +# import statements are sorted alphabetically within their import groups. +# while preserving the relative ordering of the groups. +sort_imports = false + +[doc] +out = "docs" +title = "" +book = "book.toml" +ignore = [] + +[profile.docs] +title = 'Protocol docs' +# root_path variable in build-docs.sh +src = 'src' + +[fuzz] +fuzz_seed = '0x3e8' +# The number of fuzz runs for fuzz tests +runs = 10_000 +# The maximum number of test case rejections allowed by proptest, to be +# encountered during usage of `vm.assume` cheatcode. This will be used +# to set the `max_global_rejects` value in proptest test runner config. +# `max_local_rejects` option isn't exposed here since we're not using +# `prop_filter`. +max_test_rejects = 120000 +# The weight of the dictionary +dictionary_weight = 40 +# The flag indicating whether to include values from storage +include_storage = true +# The flag indicating whether to include push bytes values +include_push_bytes = true +max_fuzz_dictionary_addresses = 15728640 +max_fuzz_dictionary_values = 6553600 +max_calldata_fuzz_dictionary_addresses = 0 +gas_report_samples = 256 +failure_persist_dir = "cache/fuzz" +failure_persist_file = "failures" + +[invariant] +# The number of runs that must execute for each invariant test group +runs = 256 +# The number of calls executed to attempt to break invariants in one run +depth = 500 +# Fails the invariant fuzzing if a revert occurs +fail_on_revert = false +# Allows overriding an unsafe external call when running invariant tests. eg. reentrancy checks +call_override = false +# The weight of the dictionary +dictionary_weight = 80 +# The flag indicating whether to include values from storage +include_storage = true +# The flag indicating whether to include push bytes values +include_push_bytes = true +max_fuzz_dictionary_addresses = 15728640 +max_fuzz_dictionary_values = 6553600 +max_calldata_fuzz_dictionary_addresses = 0 +shrink_sequence = true +# run limit max: 262144 +shrink_run_limit = 5000 +preserve_state = false +max_assume_rejects = 65536 +gas_report_samples = 256 +failure_persist_dir = "cache/invariant" [profile.default.optimizer_details] -constantOptimizer = true +# constantOptimizer = true yul = true - +# this sets the `yulDetails` of the `optimizer_details` for the `default` profile [profile.default.optimizer_details.yulDetails] stackAllocation = true +# ACHTUNG! Setting this is extremely dangerous +# {@see {@link https://soliditylang.org/blog/2023/07/19/full-inliner-non-expression-split-argument-evaluation-order-bug/} } +# optimizerSteps = 'u:' + +# [default.model_checker] +# contracts = { '/path/to/project/src/Contract.sol' = [ 'Contract' ] } +# engine = 'chc' +# timeout = 10000 +# targets = [ 'assert' ] +[bind_json] +out = "utils/JsonBindings.sol" +include = [] +exclude = [] \ No newline at end of file diff --git a/test/UnitTests.t.sol b/test/UnitTests.t.sol index 28a417c..936718d 100644 --- a/test/UnitTests.t.sol +++ b/test/UnitTests.t.sol @@ -6,7 +6,6 @@ import "test/interfaces/ISwapRouter.sol"; contract UnitTests is BaseCaptiveTest { ISwapRouter public router = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); - /// @dev Ensure that balances and state variables are updated correctly. function testAddLiquidity() public { fold.transfer(User01, 1_000 ether); @@ -27,7 +26,6 @@ contract UnitTests is BaseCaptiveTest { assertEq(token1FeeDebt, 0); } - /// @dev Ensure that balances and state variables are updated correctly. function testRemoveLiquidity() public { testAddLiquidity(); @@ -54,7 +52,6 @@ contract UnitTests is BaseCaptiveTest { assertEq(amount, liq / 4); } - /// @dev Ensure fees are accrued correctly and distributed proportionately. function testFeesAccrue() public { testAddLiquidity(); @@ -103,7 +100,6 @@ contract UnitTests is BaseCaptiveTest { assertGt(foldCaptiveStaking.token1FeesPerLiquidity(), 0); } - /// @dev Ensure fees are compounded correctly and state variables are updated. function testCanCompoundFees() public { testAddLiquidity(); @@ -151,7 +147,6 @@ contract UnitTests is BaseCaptiveTest { assertGt(newAmount, amount); } - /// @dev Ensure new users can't steal fees accrued by others. function testNewUsersDontStealFees() public { testFeesAccrue(); @@ -199,7 +194,6 @@ contract UnitTests is BaseCaptiveTest { stakingTwo.depositRewards(); } - /// @dev Ensure rewards are added and collected correctly. function testCanAddRewards() public { testAddLiquidity(); @@ -228,7 +222,6 @@ contract UnitTests is BaseCaptiveTest { foldCaptiveStaking.withdraw(liq / 3); } - /// @dev Ensure the owner can claim insurance correctly. function testClaimInsurance() public { testAddLiquidity(); @@ -248,23 +241,6 @@ contract UnitTests is BaseCaptiveTest { vm.stopPrank(); } - /// @dev Ensure pro-rata withdrawals are handled correctly - function testProRataWithdrawals() public { - testAddLiquidity(); - - (uint128 liq,,,) = foldCaptiveStaking.balances(User01); - - // Attempt to withdraw more than allowed amount - vm.expectRevert(WithdrawProRata.selector); - foldCaptiveStaking.withdraw(liq); - - // Pro-rated withdrawal - foldCaptiveStaking.withdraw(liq / 2); - (uint128 amount,,,) = foldCaptiveStaking.balances(User01); - assertEq(amount, liq / 2); - } - - /// @dev Ensure zero deposits are handled correctly and revert as expected. function testZeroDeposit() public { vm.expectRevert(); foldCaptiveStaking.deposit(0, 0, 0); @@ -272,7 +248,6 @@ contract UnitTests is BaseCaptiveTest { assertEq(amount, 0); } - /// @dev Ensure the contract is protected against reentrancy attacks. function testReentrancy() public { testAddLiquidity(); @@ -298,26 +273,6 @@ contract UnitTests is BaseCaptiveTest { // Expect revert due to minimum deposit requirement vm.expectRevert(DepositAmountBelowMinimum.selector); foldCaptiveStaking.deposit(0.5 ether, 0.5 ether, 0); - /// @dev Deposit Cap Enforcement: Test to ensure the deposit cap is respected. - function testDepositCap() public { - uint256 cap = 100 ether; - foldCaptiveStaking.setDepositCap(cap); - - fold.transfer(User01, 2000 ether); - - vm.deal(User01, 2000 ether); - vm.startPrank(User01); - - weth.deposit{value: 2000 ether}(); - weth.approve(address(foldCaptiveStaking), type(uint256).max); - fold.approve(address(foldCaptiveStaking), type(uint256).max); - - // First deposit should succeed - foldCaptiveStaking.deposit(1_000 ether, 1_000 ether, 0); - - // Second deposit should revert due to cap - vm.expectRevert(DepositCapReached.selector); - foldCaptiveStaking.deposit(1_000 ether, 1_000 ether, 0); vm.stopPrank(); } @@ -339,52 +294,6 @@ contract UnitTests is BaseCaptiveTest { // Withdraw after cooldown period foldCaptiveStaking.withdraw(liq / 2); (uint128 amount,,,) = foldCaptiveStaking.balances(User01); - /// @dev Multiple Users: Test simultaneous deposits and withdrawals by multiple users. - function testMultipleUsersDepositWithdraw() public { - // User 1 deposits - fold.transfer(User01, 1_000 ether); - vm.deal(User01, 1_000 ether); - vm.startPrank(User01); - - weth.deposit{value: 1_000 ether}(); - weth.approve(address(foldCaptiveStaking), type(uint256).max); - fold.approve(address(foldCaptiveStaking), type(uint256).max); - - foldCaptiveStaking.deposit(1_000 ether, 1_000 ether, 0); - - vm.stopPrank(); - - // User 2 deposits - fold.transfer(User02, 500 ether); - vm.deal(User02, 500 ether); - vm.startPrank(User02); - - weth.deposit{value: 500 ether}(); - weth.approve(address(foldCaptiveStaking), type(uint256).max); - fold.approve(address(foldCaptiveStaking), type(uint256).max); - - foldCaptiveStaking.deposit(500 ether, 500 ether, 0); - - vm.stopPrank(); - - // User 1 withdraws - vm.startPrank(User01); - - (uint128 liq,,,) = foldCaptiveStaking.balances(User01); - foldCaptiveStaking.withdraw(liq / 2); - - (uint128 amount,,,) = foldCaptiveStaking.balances(User01); - assertEq(amount, liq / 2); - - vm.stopPrank(); - - // User 2 withdraws - vm.startPrank(User02); - - (liq,,,) = foldCaptiveStaking.balances(User02); - foldCaptiveStaking.withdraw(liq / 2); - - (amount,,,) = foldCaptiveStaking.balances(User02); assertEq(amount, liq / 2); vm.stopPrank(); @@ -407,4 +316,4 @@ contract ReentrancyAttack { receive() external payable { staking.withdraw(1); } -} +} \ No newline at end of file