diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..5161f1c --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Initial linting +1696c504527d64d9b22cefe03e38ac1f41c90268 diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index 160c03c..c354248 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -1,6 +1,11 @@ -name: test +name: Foundry -on: workflow_dispatch +on: + workflow_dispatch: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] env: FOUNDRY_PROFILE: ci @@ -22,9 +27,7 @@ jobs: submodules: recursive - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly + uses: foundry-rs/foundry-toolchain@v1.2.0 - name: Run Forge build run: | @@ -36,3 +39,7 @@ jobs: run: | forge test -vvv id: test + - name: Generate coverage report + run: | + forge coverage --report summary + id: coverage diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..caba825 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,16 @@ +{ + "plugins": ["prettier-plugin-solidity"], + "overrides": [ + { + "files": "*.sol", + "options": { + "parser": "solidity-parse", + "printWidth": 80, + "tabWidth": 4, + "useTabs": false, + "singleQuote": false, + "bracketSpacing": false + } + } + ] +} diff --git a/foundry/script/Counter.s.sol b/foundry/script/Counter.s.sol deleted file mode 100644 index be2adb1..0000000 --- a/foundry/script/Counter.s.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.25; - -import {Script, console} from "forge-std/Script.sol"; - -contract CounterScript is Script { - function setUp() public {} - - function run() public { - vm.broadcast(); - } -} diff --git a/foundry/src/FoxStaking.sol b/foundry/src/FoxStaking.sol index 596a1f7..42a176c 100644 --- a/foundry/src/FoxStaking.sol +++ b/foundry/src/FoxStaking.sol @@ -5,13 +5,14 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; import {IFoxStaking, StakingInfo} from "./IFoxStaking.sol"; -import {console} from "forge-std/Script.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; contract FoxStaking is IFoxStaking, Ownable(msg.sender), // Deployer is the owner Pausable { + using SafeERC20 for IERC20; IERC20 public foxToken; mapping(address => StakingInfo) public stakingInfo; @@ -32,6 +33,7 @@ contract FoxStaking is event Withdraw(address indexed account, uint256 amount); event SetRuneAddress( address indexed account, + string indexed oldRuneAddress, string indexed newRuneAddress ); @@ -95,13 +97,12 @@ contract FoxStaking is uint256 amount, string memory runeAddress ) external whenNotPaused whenStakingNotPaused { - require(bytes(runeAddress).length > 0, "Rune address cannot be empty"); - require(amount > 0, "FOX amount to stake must be greater than 0"); - // Transfer fundus from msg.sender to contract assuming allowance has been set - here goes nothing require( - foxToken.transferFrom(msg.sender, address(this), amount), - "Transfer failed" + bytes(runeAddress).length == 43, + "Rune address must be 43 characters" ); + require(amount > 0, "FOX amount to stake must be greater than 0"); + foxToken.safeTransferFrom(msg.sender, address(this), amount); StakingInfo storage info = stakingInfo[msg.sender]; info.stakingBalance += amount; @@ -138,17 +139,19 @@ contract FoxStaking is require(block.timestamp >= info.cooldownExpiry, "Not cooled down yet"); uint256 withdrawAmount = info.unstakingBalance; info.unstakingBalance = 0; - require( - foxToken.transfer(msg.sender, withdrawAmount), - "Transfer failed" - ); + foxToken.safeTransfer(msg.sender, withdrawAmount); emit Withdraw(msg.sender, withdrawAmount); } function setRuneAddress(string memory runeAddress) external { + require( + bytes(runeAddress).length == 43, + "Rune address must be 43 characters" + ); StakingInfo storage info = stakingInfo[msg.sender]; + string memory oldRuneAddress = info.runeAddress; info.runeAddress = runeAddress; - emit SetRuneAddress(msg.sender, runeAddress); + emit SetRuneAddress(msg.sender, oldRuneAddress, runeAddress); } function balanceOf(address account) external view returns (uint256 total) { diff --git a/foundry/src/IFoxStaking.sol b/foundry/src/IFoxStaking.sol index 8a4b09c..b61dc79 100644 --- a/foundry/src/IFoxStaking.sol +++ b/foundry/src/IFoxStaking.sol @@ -2,13 +2,12 @@ pragma solidity ^0.8.25; struct StakingInfo { - uint256 stakingBalance; - uint256 unstakingBalance; - uint256 cooldownExpiry; - string runeAddress; + uint256 stakingBalance; + uint256 unstakingBalance; + uint256 cooldownExpiry; + string runeAddress; } - /// @notice This interface outlines the functions for staking FOX tokens, managing RUNE addresses for rewards, and claiming 'em. interface IFoxStaking { /// @notice Pauses deposits @@ -59,7 +58,5 @@ interface IFoxStaking { /// This can be initiated by any address with any address as param, as this has view modifier i.e everything is public on-chain /// @param account The address we're getting the staked FOX balance for. /// @return total The total amount of FOX tokens held. - function balanceOf( - address account - ) external view returns (uint256 total); + function balanceOf(address account) external view returns (uint256 total); } diff --git a/foundry/test/FoxStaking.t.sol b/foundry/test/FoxStaking.t.sol index b66cd36..c79b68e 100644 --- a/foundry/test/FoxStaking.t.sol +++ b/foundry/test/FoxStaking.t.sol @@ -19,6 +19,68 @@ contract MockFOXToken is ERC20 { } } +contract FOXStakingTestRuneAddress is Test { + FoxStaking public foxStaking; + MockFOXToken public foxToken; + address user = address(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045); + + function setUp() public { + foxToken = new MockFOXToken(); + foxStaking = new FoxStaking(address(foxToken)); + } + + function testCanSetRuneAddress() public { + vm.startPrank(user); + + string + memory newRuneAddress = "thor17gw75axcnr8747pkanye45pnrwk7p9c3cqncsv"; + + foxStaking.setRuneAddress(newRuneAddress); + + (, , , string memory runeAddress) = foxStaking.stakingInfo(user); + assertEq( + runeAddress, + newRuneAddress, + "setRuneAddress should update the rune address when called by the owner" + ); + + vm.stopPrank(); + } + + function testCannotSetInvalidLengthRuneAddress() public { + vm.startPrank(user); + + string memory invalidLengthRuneAddress = "thor1234"; + + vm.expectRevert("Rune address must be 43 characters"); + foxStaking.setRuneAddress(invalidLengthRuneAddress); + + vm.stopPrank(); + } + + function cannotStakeWithEmptyRuneAddress() public { + vm.startPrank(user); + + string memory emptyRuneAddress = ""; + + vm.expectRevert("Rune address cannot be empty"); + foxStaking.stake(1e18, emptyRuneAddress); + + vm.stopPrank(); + } + + function cannotStakeWithInvalidLengthRuneAddress() public { + vm.startPrank(user); + + string memory invalidLengthRuneAddress = "thor1234"; + + vm.expectRevert("Rune address must be 42 characters"); + foxStaking.stake(1e18, invalidLengthRuneAddress); + + vm.stopPrank(); + } +} + contract FOXStakingTestOwnership is Test { FoxStaking public foxStaking; MockFOXToken public foxToken; @@ -31,9 +93,13 @@ contract FOXStakingTestOwnership is Test { function testOwnerCanUpdateCooldownPeriod() public { uint256 newCooldownPeriod = 14 days; - + foxStaking.setCooldownPeriod(newCooldownPeriod); - assertEq(foxStaking.cooldownPeriod(), newCooldownPeriod, "setCooldownPeriod should update the cooldown period when called by the owner"); + assertEq( + foxStaking.cooldownPeriod(), + newCooldownPeriod, + "setCooldownPeriod should update the cooldown period when called by the owner" + ); } function testNonOwnerCannotUpdateCooldownPeriod() public { @@ -41,10 +107,10 @@ contract FOXStakingTestOwnership is Test { vm.prank(nonOwner); vm.expectRevert( - abi.encodeWithSelector( - Ownable.OwnableUnauthorizedAccount.selector, - address(nonOwner) - ) + abi.encodeWithSelector( + Ownable.OwnableUnauthorizedAccount.selector, + address(nonOwner) + ) ); foxStaking.setCooldownPeriod(newCooldownPeriod); } @@ -60,26 +126,186 @@ contract FOXStakingTestStaking is Test { } function testCannotStakeWhenStakingPaused() public { - foxStaking.pauseStaking(); + foxStaking.pauseStaking(); - address user = address(0xBABE); - vm.startPrank(user); - vm.expectRevert("Staking is paused"); - foxStaking.stake(1e18, "runeAddress"); - vm.stopPrank(); + address user = address(0xBABE); + vm.startPrank(user); + vm.expectRevert("Staking is paused"); + foxStaking.stake(1e18, "runeAddress"); + vm.stopPrank(); + } + + function testCanStakeAfterUnpausingStake() public { + address user = address(0xBABE); + uint256 amount = 1000; + string + memory runeAddress = "thor17gw75axcnr8747pkanye45pnrwk7p9c3cqncsv"; + + foxToken.makeItRain(user, amount); + + // Check user staking balances + ( + uint256 stakingBalance_before, + uint256 unstakingBalance_before, + , + + ) = foxStaking.stakingInfo(user); + vm.assertEq(stakingBalance_before + unstakingBalance_before, 0); + vm.assertEq(stakingBalance_before, 0); + vm.assertEq(unstakingBalance_before, 0); + + foxStaking.pauseStaking(); + + vm.startPrank(user); + vm.expectRevert("Staking is paused"); + foxStaking.stake(amount, runeAddress); + vm.stopPrank(); + + foxStaking.unpauseStaking(); + + vm.startPrank(user); + foxToken.approve(address(foxStaking), amount); + foxStaking.stake(amount, runeAddress); + + // Check user staking balances reflect the withdrawal request + ( + uint256 stakingBalance_after, + uint256 unstakingBalance_after, + , + + ) = foxStaking.stakingInfo(user); + vm.assertEq(stakingBalance_after + unstakingBalance_after, 1000); + vm.assertEq(stakingBalance_after, 1000); + vm.assertEq(unstakingBalance_after, 0); + + vm.stopPrank(); } function testCannotStakeWhenContractPaused() public { - foxStaking.pause(); + foxStaking.pause(); + + address user = address(0xBABE); + vm.startPrank(user); + vm.expectRevert( + abi.encodeWithSelector(Pausable.EnforcedPause.selector) + ); + foxStaking.stake(1e18, "runeAddress"); + vm.stopPrank(); + } + + function testCanStakeWhenUnpausingAfterPaused() public { + foxStaking.pause(); + + address user = address(0xBABE); + uint256 amount = 1000; + string + memory runeAddress = "thor17gw75axcnr8747pkanye45pnrwk7p9c3cqncsv"; + + ( + uint256 stakingBalance_before, + uint256 unstakingBalance_before, + , + + ) = foxStaking.stakingInfo(user); + vm.assertEq(stakingBalance_before + unstakingBalance_before, 0); + vm.assertEq(stakingBalance_before, 0); + vm.assertEq(unstakingBalance_before, 0); + + foxToken.makeItRain(user, amount); + + vm.startPrank(user); + foxToken.approve(address(foxStaking), amount); + vm.expectRevert( + abi.encodeWithSelector(Pausable.EnforcedPause.selector) + ); + foxStaking.stake(amount, runeAddress); + vm.stopPrank(); + + foxStaking.unpause(); + + vm.startPrank(user); + foxStaking.stake(amount, runeAddress); + + ( + uint256 stakingBalance_after, + uint256 unstakingBalance_after, + , + + ) = foxStaking.stakingInfo(user); + vm.assertEq(stakingBalance_after + unstakingBalance_after, 1000); + vm.assertEq(stakingBalance_after, 1000); + vm.assertEq(unstakingBalance_after, 0); + } + + function testStake_cannotStakeZero() public { + address user = address(0xD00D); + string + memory runeAddress = "thor17gw75axcnr8747pkanye45pnrwk7p9c3cqncsv"; + + vm.startPrank(user); + + // Check user staking balances + (uint256 stakingBalance, uint256 unstakingBalance, , ) = foxStaking + .stakingInfo(user); + vm.assertEq(stakingBalance + unstakingBalance, 0); + vm.assertEq(stakingBalance, 0); + vm.assertEq(unstakingBalance, 0); + + // Try to stake 0 + vm.expectRevert("FOX amount to stake must be greater than 0"); + foxStaking.stake(0, runeAddress); - address user = address(0xBABE); - vm.startPrank(user); - vm.expectRevert(abi.encodeWithSelector(Pausable.EnforcedPause.selector)); - foxStaking.stake(1e18, "runeAddress"); - vm.stopPrank(); + // Check user staking balances are unchanged + ( + uint256 stakingBalance_after, + uint256 unstakingBalance_after, + , + + ) = foxStaking.stakingInfo(user); + vm.assertEq(stakingBalance_after + unstakingBalance_after, 0); + vm.assertEq(stakingBalance_after, 0); + vm.assertEq(unstakingBalance_after, 0); + + vm.stopPrank(); } + function testStake_cannotStakeWithEmptyRuneAddress() public { + address user = address(0xD00D); + + vm.startPrank(user); + + // Check user staking balances + (uint256 stakingBalance, uint256 unstakingBalance, , ) = foxStaking + .stakingInfo(user); + vm.assertEq(stakingBalance + unstakingBalance, 0); + vm.assertEq(stakingBalance, 0); + vm.assertEq(unstakingBalance, 0); + + // Try to stake with empty rune address + vm.expectRevert("Rune address must be 43 characters"); + foxStaking.stake(1e18, ""); + + // Check user staking balances are unchanged + ( + uint256 stakingBalance_after, + uint256 unstakingBalance_after, + , + + ) = foxStaking.stakingInfo(user); + vm.assertEq(stakingBalance_after + unstakingBalance_after, 0); + vm.assertEq(stakingBalance_after, 0); + vm.assertEq(unstakingBalance_after, 0); + + vm.stopPrank(); + } + + // "e2e" staking test for multiple users function testStaking() public { + string[3] memory runeAddresses = [ + "thor17gw75axcnr8747pkanye45pnrwk7p9c3cqncs0", + "thor17gw75axcnr8747pkanye45pnrwk7p9c3cqncs1", + "thor17gw75axcnr8747pkanye45pnrwk7p9c3cqncs2" + ]; address[] memory users = new address[](3); users[0] = address(0xBABE); users[1] = address(0xC0DE); @@ -92,8 +318,6 @@ contract FOXStakingTestStaking is Test { // Simulate each user staking FOX tokens for (uint256 i = 0; i < users.length; i++) { - // Unique mock address per user - string memory runeAddress = string(abi.encodePacked("runeAddress", Strings.toString(i))); // Free FOX tokens for each user foxToken.makeItRain(users[i], amounts[i]); // https://book.getfoundry.sh/cheatcodes/start-prank @@ -101,11 +325,11 @@ contract FOXStakingTestStaking is Test { // Approve FoxStaking contract to spend user's FOX tokens foxToken.approve(address(foxStaking), amounts[i]); // Stake tokens - foxStaking.stake(amounts[i], runeAddress); + foxStaking.stake(amounts[i], runeAddresses[i]); vm.stopPrank(); // Verify each user's staked amount - (uint256 total) = foxStaking.balanceOf(users[i]); + uint256 total = foxStaking.balanceOf(users[i]); assertEq(total, amounts[i]); } } @@ -117,7 +341,7 @@ contract FOXStakingTestUnstake is Test { address user = address(0xBEEF); uint256 amount = 1000; - string constant runeAddress = "thorFooBarBaz"; + string constant runeAddress = "thor17gw75axcnr8747pkanye45pnrwk7p9c3cqncsv"; function setUp() public { foxToken = new MockFOXToken(); @@ -136,31 +360,71 @@ contract FOXStakingTestUnstake is Test { } function testCannotUnstakeWhenUnstakingPaused() public { - foxStaking.pauseUnstaking(); + foxStaking.pauseUnstaking(); - vm.startPrank(user); - vm.expectRevert("Unstaking is paused"); - foxStaking.unstake(amount); - vm.stopPrank(); + vm.startPrank(user); + vm.expectRevert("Unstaking is paused"); + foxStaking.unstake(amount); + vm.stopPrank(); + } + + function testCanUnstakeAfterUnpausingUnstaking() public { + vm.startPrank(user); + // Check user staking balances + ( + uint256 stakingBalance_before, + uint256 unstakingBalance_before, + , + + ) = foxStaking.stakingInfo(user); + vm.assertEq(stakingBalance_before + unstakingBalance_before, 1000); + vm.assertEq(stakingBalance_before, 1000); + vm.assertEq(unstakingBalance_before, 0); + vm.stopPrank(); + + foxStaking.pauseUnstaking(); + + vm.startPrank(user); + vm.expectRevert("Unstaking is paused"); + foxStaking.unstake(amount); + vm.stopPrank(); + + foxStaking.unpauseUnstaking(); + + vm.startPrank(user); + foxStaking.unstake(amount); + + // Check user staking balances reflect the withdrawal request + ( + uint256 stakingBalance_after, + uint256 unstakingBalance_after, + , + + ) = foxStaking.stakingInfo(user); + vm.assertEq(stakingBalance_after + unstakingBalance_after, 1000); + vm.assertEq(stakingBalance_after, 0); + vm.assertEq(unstakingBalance_after, 1000); + + vm.stopPrank(); } function testCannotUnstakeWhenContractPaused() public { - foxStaking.pause(); + foxStaking.pause(); - vm.startPrank(user); - vm.expectRevert(abi.encodeWithSelector( - Pausable.EnforcedPause.selector - ) - ); - foxStaking.unstake(amount); - vm.stopPrank(); + vm.startPrank(user); + vm.expectRevert( + abi.encodeWithSelector(Pausable.EnforcedPause.selector) + ); + foxStaking.unstake(amount); + vm.stopPrank(); } function testunstake_cannotRequestZero() public { vm.startPrank(user); // Check user staking balances - (uint256 stakingBalance, uint256 unstakingBalance, , ) = foxStaking.stakingInfo(user); + (uint256 stakingBalance, uint256 unstakingBalance, , ) = foxStaking + .stakingInfo(user); vm.assertEq(stakingBalance + unstakingBalance, 1000); vm.assertEq(stakingBalance, 1000); vm.assertEq(unstakingBalance, 0); @@ -170,7 +434,12 @@ contract FOXStakingTestUnstake is Test { foxStaking.unstake(0); // Check user staking balances are unchanged - (uint256 stakingBalance_after, uint256 unstakingBalance_after, , ) = foxStaking.stakingInfo(user); + ( + uint256 stakingBalance_after, + uint256 unstakingBalance_after, + , + + ) = foxStaking.stakingInfo(user); vm.assertEq(stakingBalance_after + unstakingBalance_after, 1000); vm.assertEq(stakingBalance_after, 1000); @@ -183,17 +452,27 @@ contract FOXStakingTestUnstake is Test { vm.startPrank(user); // Check user staking balances - (uint256 stakingBalance_before, uint256 unstakingBalance_before, , ) = foxStaking.stakingInfo(user); + ( + uint256 stakingBalance_before, + uint256 unstakingBalance_before, + , + + ) = foxStaking.stakingInfo(user); vm.assertEq(stakingBalance_before + unstakingBalance_before, 1000); vm.assertEq(stakingBalance_before, 1000); vm.assertEq(unstakingBalance_before, 0); - + // Try to request more than balance vm.expectRevert("Unstake amount exceeds staked balance"); foxStaking.unstake(amount + 1); // Check user staking balances are unchanged - (uint256 stakingBalance_after, uint256 unstakingBalance_after, , ) = foxStaking.stakingInfo(user); + ( + uint256 stakingBalance_after, + uint256 unstakingBalance_after, + , + + ) = foxStaking.stakingInfo(user); vm.assertEq(stakingBalance_after + unstakingBalance_after, 1000); vm.assertEq(stakingBalance_after, 1000); vm.assertEq(unstakingBalance_after, 0); @@ -205,7 +484,12 @@ contract FOXStakingTestUnstake is Test { vm.startPrank(user); // Check user staking balances - (uint256 stakingBalance_before, uint256 unstakingBalance_before, , ) = foxStaking.stakingInfo(user); + ( + uint256 stakingBalance_before, + uint256 unstakingBalance_before, + , + + ) = foxStaking.stakingInfo(user); vm.assertEq(stakingBalance_before + unstakingBalance_before, 1000); vm.assertEq(stakingBalance_before, 1000); vm.assertEq(unstakingBalance_before, 0); @@ -214,7 +498,12 @@ contract FOXStakingTestUnstake is Test { foxStaking.unstake(amount); // Check user staking balances reflect the withdrawal request - (uint256 stakingBalance_after, uint256 unstakingBalance_after, , ) = foxStaking.stakingInfo(user); + ( + uint256 stakingBalance_after, + uint256 unstakingBalance_after, + , + + ) = foxStaking.stakingInfo(user); vm.assertEq(stakingBalance_after + unstakingBalance_after, 1000); vm.assertEq(stakingBalance_after, 0); vm.assertEq(unstakingBalance_after, 1000); @@ -226,7 +515,12 @@ contract FOXStakingTestUnstake is Test { vm.startPrank(user); // Check user staking balances - (uint256 stakingBalance_before, uint256 unstakingBalance_before, , ) = foxStaking.stakingInfo(user); + ( + uint256 stakingBalance_before, + uint256 unstakingBalance_before, + , + + ) = foxStaking.stakingInfo(user); vm.assertEq(stakingBalance_before + unstakingBalance_before, 1000); vm.assertEq(stakingBalance_before, 1000); vm.assertEq(unstakingBalance_before, 0); @@ -235,7 +529,12 @@ contract FOXStakingTestUnstake is Test { foxStaking.unstake(800); // Check user staking balances reflect the withdrawal request - (uint256 stakingBalance_after, uint256 unstakingBalance_after, , ) = foxStaking.stakingInfo(user); + ( + uint256 stakingBalance_after, + uint256 unstakingBalance_after, + , + + ) = foxStaking.stakingInfo(user); vm.assertEq(stakingBalance_after + unstakingBalance_after, 1000); vm.assertEq(stakingBalance_after, 200); vm.assertEq(unstakingBalance_after, 800); @@ -248,7 +547,12 @@ contract FOXStakingTestUnstake is Test { vm.startPrank(user); // Check user staking balances - (uint256 stakingBalance_before, uint256 unstakingBalance_before, , ) = foxStaking.stakingInfo(user); + ( + uint256 stakingBalance_before, + uint256 unstakingBalance_before, + , + + ) = foxStaking.stakingInfo(user); vm.assertEq(stakingBalance_before + unstakingBalance_before, 1000); vm.assertEq(stakingBalance_before, 1000); vm.assertEq(unstakingBalance_before, 0); @@ -261,7 +565,12 @@ contract FOXStakingTestUnstake is Test { foxStaking.withdraw(); // Check cooldown period is set - (uint256 stakingBalance_one, uint256 unstakingBalance_one, uint256 cooldownExpiry_one, ) = foxStaking.stakingInfo(user); + ( + uint256 stakingBalance_one, + uint256 unstakingBalance_one, + uint256 cooldownExpiry_one, + + ) = foxStaking.stakingInfo(user); vm.assertEq(cooldownExpiry_one, block.timestamp + 28 days); // Check user staking balances reflect the withdrawal request @@ -276,7 +585,12 @@ contract FOXStakingTestUnstake is Test { foxStaking.withdraw(); // Check user staking balances reflect the withdrawal - (uint256 stakingBalance_two, uint256 unstakingBalance_two, , ) = foxStaking.stakingInfo(user); + ( + uint256 stakingBalance_two, + uint256 unstakingBalance_two, + , + + ) = foxStaking.stakingInfo(user); vm.assertEq(stakingBalance_two + unstakingBalance_two, 700); vm.assertEq(stakingBalance_two, 700); vm.assertEq(unstakingBalance_two, 0); @@ -285,7 +599,12 @@ contract FOXStakingTestUnstake is Test { foxStaking.unstake(700); // Check cooldown period is set - (uint256 stakingBalance_three, uint256 unstakingBalance_three, uint256 cooldownExpiry_three, ) = foxStaking.stakingInfo(user); + ( + uint256 stakingBalance_three, + uint256 unstakingBalance_three, + uint256 cooldownExpiry_three, + + ) = foxStaking.stakingInfo(user); vm.assertGt(cooldownExpiry_three, block.timestamp); // Check user staking balances reflect the withdrawal request @@ -303,7 +622,7 @@ contract FOXStakingTestWithdraw is Test { address user = address(0xBEEF); uint256 amount = 1000; - string constant runeAddress = "thorFooBarBaz"; + string constant runeAddress = "thor17gw75axcnr8747pkanye45pnrwk7p9c3cqncsv"; function setUp() public { foxToken = new MockFOXToken(); @@ -324,21 +643,68 @@ contract FOXStakingTestWithdraw is Test { } function testCannotWithdrawWhenWithdrawalsPaused() public { - foxStaking.pauseWithdrawals(); + foxStaking.pauseWithdrawals(); - vm.startPrank(user); - vm.expectRevert("Withdrawals are paused"); // Make sure this matches the actual revert message used in your contract - foxStaking.withdraw(); - vm.stopPrank(); + vm.startPrank(user); + vm.expectRevert("Withdrawals are paused"); // Make sure this matches the actual revert message used in your contract + foxStaking.withdraw(); + vm.stopPrank(); } function testCannotWithdrawWhenContractPaused() public { - foxStaking.pause(); + foxStaking.pause(); + + vm.startPrank(user); + vm.expectRevert( + abi.encodeWithSelector(Pausable.EnforcedPause.selector) + ); + foxStaking.withdraw(); + vm.stopPrank(); + } + + function testCanWithdrawAfterUnpausingWithdraw() public { + vm.startPrank(user); + // Check user staking balances + ( + uint256 stakingBalance_before, + uint256 unstakingBalance_before, + , + + ) = foxStaking.stakingInfo(user); + vm.assertEq(stakingBalance_before + unstakingBalance_before, 1000); + vm.assertEq(stakingBalance_before, 1000); + vm.assertEq(unstakingBalance_before, 0); + // Request withdraw + foxStaking.unstake(amount); + vm.stopPrank(); + + foxStaking.pauseWithdrawals(); + + // Fast-forward time by 28 days + vm.warp(block.timestamp + 28 days); + + vm.startPrank(user); + vm.expectRevert("Withdrawals are paused"); + foxStaking.withdraw(); + vm.stopPrank(); + + foxStaking.unpauseWithdrawals(); + + vm.startPrank(user); + foxStaking.withdraw(); - vm.startPrank(user); - vm.expectRevert(abi.encodeWithSelector(Pausable.EnforcedPause.selector)); - foxStaking.withdraw(); - vm.stopPrank(); + // Check user staking balances reflect the withdrawal request + ( + uint256 stakingBalance_after, + uint256 unstakingBalance_after, + , + + ) = foxStaking.stakingInfo(user); + vm.assertEq(stakingBalance_after + unstakingBalance_after, 0); + vm.assertEq(stakingBalance_after, 0); + vm.assertEq(unstakingBalance_after, 0); + + vm.stopPrank(); } function testWithdraw_cannotWithdrawBeforeCooldown() public { diff --git a/package.json b/package.json new file mode 100644 index 0000000..7e288a1 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "devDependencies": { + "eslint": "8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "5.1.3", + "prettier": "^3.2.5", + "prettier-plugin-solidity": "1.3.1", + "pretty-quick": "^4.0.0", + "simple-git-hooks": "^2.11.1" + }, + "scripts": { + "lint:sol": "npx prettier --write --plugin=prettier-plugin-solidity foundry/src/**/*.sol foundry/test/**/*.t.sol" + }, + "simple-git-hooks": { + "pre-commit": "yarn lint:sol && yarn pretty-quick --staged" + } +} diff --git a/scripts/rewards-distribution/constants.ts b/scripts/rewards-distribution/constants.ts index 9fc4243..8e149f9 100644 --- a/scripts/rewards-distribution/constants.ts +++ b/scripts/rewards-distribution/constants.ts @@ -1,19 +1,19 @@ import { createPublicClient, createWalletClient, http } from "viem"; import { localhost } from "viem/chains"; -const ANVIL_JSON_RPC_URL = 'http://localhost:8545' +const ANVIL_JSON_RPC_URL = "http://localhost:8545"; export const localChain = { ...localhost, - id: 31337 -} as const + id: 31337, +} as const; export const localWalletClient = createWalletClient({ chain: localChain, - transport: http(ANVIL_JSON_RPC_URL) + transport: http(ANVIL_JSON_RPC_URL), }); export const localPublicClient = createPublicClient({ chain: localChain, - transport: http(ANVIL_JSON_RPC_URL) + transport: http(ANVIL_JSON_RPC_URL), }); diff --git a/scripts/rewards-distribution/events.ts b/scripts/rewards-distribution/events.ts index 04d45f1..bad4690 100644 --- a/scripts/rewards-distribution/events.ts +++ b/scripts/rewards-distribution/events.ts @@ -1,44 +1,81 @@ -import { AbiEvent, Address, Log, parseEventLogs } from 'viem' +import { AbiEvent, Address, Log, parseEventLogs } from "viem"; -type StakingEventName = 'Stake' | 'Unstake' +type StakingEventName = "Stake" | "Unstake"; type StakingAbiEvent = { - type: 'event', - anonymous: false, + type: "event"; + anonymous: false; inputs: [ { - name: 'account', - type: 'address', - indexed: true + name: "account"; + type: "address"; + indexed: true; }, { - name: 'amount', - type: 'uint256', - indexed: false - } + name: "amount"; + type: "uint256"; + indexed: false; + }, // TOOD(gomes): if runeAddress is part of the staking fn, then it should be part of the Stake event too and should be reflected here - ], - name: T -} + ]; + name: T; +}; -type GenericStakingEventLog = Log[], T> +type GenericStakingEventLog = Log< + bigint, + number, + false, + AbiEvent, + true, + StakingAbiEvent[], + T +>; // explicit union of all possible event logs to ensure event args are correctly parsed by ts (fixes defaulting to unknown) -export type StakingEventLog = GenericStakingEventLog<'Stake'> | GenericStakingEventLog<'Unstake'> +export type StakingEventLog = + | GenericStakingEventLog<"Stake"> + | GenericStakingEventLog<"Unstake">; -const addressA: Address = '0xA' -const addressB: Address = '0xB' -const addressC: Address = '0xC' -const addressD: Address = '0xD' -const addressE: Address = '0xE' +const addressA: Address = "0xA"; +const addressB: Address = "0xB"; +const addressC: Address = "0xC"; +const addressD: Address = "0xD"; +const addressE: Address = "0xE"; -export type StakingLog = Pick +export type StakingLog = Pick< + StakingEventLog, + "blockNumber" | "eventName" | "args" +>; export const logs: StakingLog[] = [ - { blockNumber: 20n, eventName: 'Stake', args: { account: addressA, amount: 100n } }, - { blockNumber: 25n, eventName: 'Stake', args: { account: addressB, amount: 150n } }, - { blockNumber: 32n, eventName: 'Stake', args: { account: addressC, amount: 10000n } }, - { blockNumber: 33n, eventName: 'Stake', args: { account: addressD, amount: 1200n } }, - { blockNumber: 60n, eventName: 'Unstake', args: { account: addressA, amount: 100n } }, - { blockNumber: 65n, eventName: 'Stake', args: { account: addressE, amount: 500n } }, -] + { + blockNumber: 20n, + eventName: "Stake", + args: { account: addressA, amount: 100n }, + }, + { + blockNumber: 25n, + eventName: "Stake", + args: { account: addressB, amount: 150n }, + }, + { + blockNumber: 32n, + eventName: "Stake", + args: { account: addressC, amount: 10000n }, + }, + { + blockNumber: 33n, + eventName: "Stake", + args: { account: addressD, amount: 1200n }, + }, + { + blockNumber: 60n, + eventName: "Unstake", + args: { account: addressA, amount: 100n }, + }, + { + blockNumber: 65n, + eventName: "Stake", + args: { account: addressE, amount: 500n }, + }, +]; diff --git a/scripts/rewards-distribution/helpers.ts b/scripts/rewards-distribution/helpers.ts index 672a325..fd07336 100644 --- a/scripts/rewards-distribution/helpers.ts +++ b/scripts/rewards-distribution/helpers.ts @@ -1,3 +1,3 @@ export const assertUnreachable = (x: never): never => { - throw Error(`unhandled case: ${x}`) -} \ No newline at end of file + throw Error(`unhandled case: ${x}`); +}; diff --git a/scripts/rewards-distribution/index.ts b/scripts/rewards-distribution/index.ts index 75e9724..1e6edca 100644 --- a/scripts/rewards-distribution/index.ts +++ b/scripts/rewards-distribution/index.ts @@ -1,96 +1,112 @@ -import { Address, parseAbiItem } from 'viem' -import { StakingLog } from './events' -import { assertUnreachable } from './helpers' -import { simulateStaking } from './simulateStaking' -import { localPublicClient } from './constants' +import { Address, parseAbiItem } from "viem"; +import { StakingLog } from "./events"; +import { assertUnreachable } from "./helpers"; +import { simulateStaking } from "./simulateStaking"; +import { localPublicClient } from "./constants"; - - -const getLogs = async ({fromBlock, toBlock}: {fromBlock: bigint, toBlock: bigint}) => { - const logs = await localPublicClient.getLogs({ - // address: '0x' - events: [ - parseAbiItem('event Stake(address indexed account, uint256 amount, string runeAddress)'), - parseAbiItem('event Unstake(address indexed user, uint256 amount)'), - ], +const getLogs = async ({ fromBlock, - toBlock -}) -return logs as StakingLog[] -} - + toBlock, +}: { + fromBlock: bigint; + toBlock: bigint; +}) => { + const logs = await localPublicClient.getLogs({ + // address: '0x' + events: [ + parseAbiItem( + "event Stake(address indexed account, uint256 amount, string runeAddress)", + ), + parseAbiItem("event Unstake(address indexed user, uint256 amount)"), + ], + fromBlock, + toBlock, + }); + return logs as StakingLog[]; +}; const getStakingAmount = (log: StakingLog): bigint => { switch (log.eventName) { - case 'Stake': - return log.args.amount - case 'Unstake': - return -log.args.amount + case "Stake": + return log.args.amount; + case "Unstake": + return -log.args.amount; default: - assertUnreachable(log.eventName) + assertUnreachable(log.eventName); } - throw Error('should be unreachable') -} + throw Error("should be unreachable"); +}; // get the epoch and block reward for a given block number // TODO: this is a placeholder function matching the spreadsheet logic const getEpochBlockReward = (_epochEndBlockNumber: bigint) => { // TODO: blockReward is calculated as half the total rune earned by the DAO divided by the number of blocks in the epoch - return 10n -} + return 10n; +}; // get the block range for the current epoch const getEpochBlockRange = () => { // Monkey-patched to 0 and 5 for testing for now since the current simulation only goes up to block 5 - const previousEpochEndBlockNumber = 0n - const currentBlockNumber = 5n - return { fromBlockNumber: previousEpochEndBlockNumber, toBlockNumber: currentBlockNumber } -} + const previousEpochEndBlockNumber = 0n; + const currentBlockNumber = 5n; + return { + fromBlockNumber: previousEpochEndBlockNumber, + toBlockNumber: currentBlockNumber, + }; +}; // TODO: this should only process 1 epoch at a time const main = async () => { - await simulateStaking() + await simulateStaking(); // While testing, and with the current simulation flow we only need logs from block 1 to 5 but this may change - const logs = await getLogs({fromBlock: 0n, toBlock: 5n}) + const logs = await getLogs({ fromBlock: 0n, toBlock: 5n }); // index logs by block number - const logsByBlockNumber = logs.reduce>((acc, log) => { - if (!acc[log.blockNumber.toString()]) { - acc[log.blockNumber.toString()] = [] - } - acc[log.blockNumber.toString()].push(log) - return acc - }, {}) + const logsByBlockNumber = logs.reduce>( + (acc, log) => { + if (!acc[log.blockNumber.toString()]) { + acc[log.blockNumber.toString()] = []; + } + acc[log.blockNumber.toString()].push(log); + return acc; + }, + {}, + ); // TODO: these will be initialized from the last epoch's state - let totalStaked = 0n - const balanceByAccountBaseUnit: Record = {} + let totalStaked = 0n; + const balanceByAccountBaseUnit: Record = {}; // this must be initialized to empty - const epochRewardByAccount: Record = {} + const epochRewardByAccount: Record = {}; // iterate all blocks for the current epoch - const { fromBlockNumber, toBlockNumber } = getEpochBlockRange() + const { fromBlockNumber, toBlockNumber } = getEpochBlockRange(); - const epochBlockReward = getEpochBlockReward(toBlockNumber) + const epochBlockReward = getEpochBlockReward(toBlockNumber); - for (let blockNumber = fromBlockNumber; blockNumber <= toBlockNumber; blockNumber++) { - const incomingLogs: StakingLog[] | undefined = logsByBlockNumber[blockNumber.toString()] + for ( + let blockNumber = fromBlockNumber; + blockNumber <= toBlockNumber; + blockNumber++ + ) { + const incomingLogs: StakingLog[] | undefined = + logsByBlockNumber[blockNumber.toString()]; // process logs if there are any if (incomingLogs !== undefined) { for (const log of incomingLogs) { - const account = log.args.account + const account = log.args.account; if (!balanceByAccountBaseUnit[account]) { - balanceByAccountBaseUnit[account] = 0n + balanceByAccountBaseUnit[account] = 0n; } - const stakingAmountBaseUnit = getStakingAmount(log) - balanceByAccountBaseUnit[account] += stakingAmountBaseUnit - totalStaked += stakingAmountBaseUnit + const stakingAmountBaseUnit = getStakingAmount(log); + balanceByAccountBaseUnit[account] += stakingAmountBaseUnit; + totalStaked += stakingAmountBaseUnit; // clear empty balances if (balanceByAccountBaseUnit[account] === 0n) { - delete balanceByAccountBaseUnit[account] + delete balanceByAccountBaseUnit[account]; } } } @@ -98,18 +114,21 @@ const main = async () => { for (const account of Object.keys(balanceByAccountBaseUnit) as Address[]) { // calculate rewards for the current block // TODO: Bignumber math should be used here to allow for more precision with floating point numbers - const proportionOfTotalStaked = totalStaked > 0n ? Number(balanceByAccountBaseUnit[account]) / Number(totalStaked) : 0 - const reward = Number(epochBlockReward) * proportionOfTotalStaked + const proportionOfTotalStaked = + totalStaked > 0n + ? Number(balanceByAccountBaseUnit[account]) / Number(totalStaked) + : 0; + const reward = Number(epochBlockReward) * proportionOfTotalStaked; if (epochRewardByAccount[account] == undefined) { - epochRewardByAccount[account] = 0 + epochRewardByAccount[account] = 0; } - epochRewardByAccount[account] += reward + epochRewardByAccount[account] += reward; } } - console.log('rewards to be distributed:') - console.log(epochRewardByAccount) -} + console.log("rewards to be distributed:"); + console.log(epochRewardByAccount); +}; -main() +main(); diff --git a/scripts/rewards-distribution/simulateStaking.ts b/scripts/rewards-distribution/simulateStaking.ts index 3330902..9672005 100644 --- a/scripts/rewards-distribution/simulateStaking.ts +++ b/scripts/rewards-distribution/simulateStaking.ts @@ -1,90 +1,106 @@ -import { Address, formatUnits, parseUnits } from 'viem' -import { Hex } from 'viem' +import { Address, formatUnits, parseUnits } from "viem"; +import { Hex } from "viem"; - -import FoxStaking from "../../foundry/out/FoxStaking.sol/FOXStaking.json" -import MockFOXToken from "../../foundry/out/FoxStaking.t.sol/MockFOXToken.json" -import { localPublicClient, localWalletClient } from './constants' +import FoxStaking from "../../foundry/out/FoxStaking.sol/FOXStaking.json"; +import MockFOXToken from "../../foundry/out/FoxStaking.t.sol/MockFOXToken.json"; +import { localPublicClient, localWalletClient } from "./constants"; export const simulateStaking = async () => { - - const walletClient = localWalletClient - const publicClient = localPublicClient - // Deploy the MockFOXToken contract from Alice's wallet - const [alice, bob] = await walletClient.getAddresses() - const mockFoxtokenDeployHash = await walletClient.deployContract({ - abi: MockFOXToken.abi, - account: alice, - bytecode: MockFOXToken.bytecode.object as Hex, - }) - - const { contractAddress: mockFoxtokenContractAddress } = await publicClient.waitForTransactionReceipt({ hash: mockFoxtokenDeployHash}) - console.log(`MockFOXToken deployed to: ${mockFoxtokenContractAddress}`); - - // Deploy the FOXStaking contract with the address of the deployed MockFOXToken as FOX - - const mockFoxStakingDeployHash = await walletClient.deployContract({ - abi: FoxStaking.abi, - account: alice, - bytecode: FoxStaking.bytecode.object as Hex, - args: [mockFoxtokenContractAddress] - }) - - const { contractAddress: mockFoxStakingContractAddress } = await publicClient.waitForTransactionReceipt({ hash: mockFoxStakingDeployHash}) - console.log(`FOXStaking deployed to: ${mockFoxStakingContractAddress}`); - - const foxDecimals = await publicClient.readContract({ - address: mockFoxtokenContractAddress as Address, - abi: MockFOXToken.abi, - functionName: 'decimals', - args: [] - }) as number - - // Make FOX rain to Bob - const makeItRainTxHash = await walletClient.writeContract({ - address: mockFoxtokenContractAddress as Address, - abi: MockFOXToken.abi, - account: bob, - functionName: 'makeItRain', - args: [bob, parseUnits('1000', foxDecimals)], + const walletClient = localWalletClient; + const publicClient = localPublicClient; + // Deploy the MockFOXToken contract from Alice's wallet + const [alice, bob] = await walletClient.getAddresses(); + const mockFoxtokenDeployHash = await walletClient.deployContract({ + abi: MockFOXToken.abi, + account: alice, + bytecode: MockFOXToken.bytecode.object as Hex, + }); + + const { contractAddress: mockFoxtokenContractAddress } = + await publicClient.waitForTransactionReceipt({ + hash: mockFoxtokenDeployHash, }); + console.log(`MockFOXToken deployed to: ${mockFoxtokenContractAddress}`); - await publicClient.waitForTransactionReceipt({ hash: makeItRainTxHash }); - console.log(`1000 FOX tokens sent to Bob`); - - const amountToStakeCryptoPrecision = '100' - const amountToStakeCryptoBaseUnit = parseUnits(amountToStakeCryptoPrecision, foxDecimals) - - // Approve FOX to be spent by the FOXStaking contract - - const approveTxHash = await walletClient.writeContract({ - address: mockFoxtokenContractAddress as Address, - abi: MockFOXToken.abi, - account: bob, - functionName: 'approve', - args: [mockFoxStakingContractAddress, amountToStakeCryptoBaseUnit], - }); - const { transactionHash } = await publicClient.waitForTransactionReceipt({ hash: approveTxHash }); + // Deploy the FOXStaking contract with the address of the deployed MockFOXToken as FOX - console.log(`Granted allowance for ${amountToStakeCryptoPrecision} FOX tokens to be spent by FOXStaking contract: ${transactionHash}`); + const mockFoxStakingDeployHash = await walletClient.deployContract({ + abi: FoxStaking.abi, + account: alice, + bytecode: FoxStaking.bytecode.object as Hex, + args: [mockFoxtokenContractAddress], + }); - const stakeTxHash = await walletClient.writeContract({ - address: mockFoxStakingContractAddress as Address, - abi: FoxStaking.abi, - account: bob, - functionName: 'stake', - args: [amountToStakeCryptoBaseUnit], + const { contractAddress: mockFoxStakingContractAddress } = + await publicClient.waitForTransactionReceipt({ + hash: mockFoxStakingDeployHash, }); - - const { transactionHash: stakeTransactionHash } = await publicClient.waitForTransactionReceipt({ hash: stakeTxHash }); - console.log(`Staked ${amountToStakeCryptoPrecision} FOX from Bob to FOXStaking contract: ${stakeTransactionHash}`); - - const bobStakedBalance = await publicClient.readContract({ - address: mockFoxStakingContractAddress as Address, - abi: FoxStaking.abi, - functionName: 'balanceOf', - args: [bob], - }) as number - - console.log(`Bob's staked balance: ${formatUnits(BigInt(bobStakedBalance), foxDecimals)} FOX`); -} + console.log(`FOXStaking deployed to: ${mockFoxStakingContractAddress}`); + + const foxDecimals = (await publicClient.readContract({ + address: mockFoxtokenContractAddress as Address, + abi: MockFOXToken.abi, + functionName: "decimals", + args: [], + })) as number; + + // Make FOX rain to Bob + const makeItRainTxHash = await walletClient.writeContract({ + address: mockFoxtokenContractAddress as Address, + abi: MockFOXToken.abi, + account: bob, + functionName: "makeItRain", + args: [bob, parseUnits("1000", foxDecimals)], + }); + + await publicClient.waitForTransactionReceipt({ hash: makeItRainTxHash }); + console.log(`1000 FOX tokens sent to Bob`); + + const amountToStakeCryptoPrecision = "100"; + const amountToStakeCryptoBaseUnit = parseUnits( + amountToStakeCryptoPrecision, + foxDecimals, + ); + + // Approve FOX to be spent by the FOXStaking contract + + const approveTxHash = await walletClient.writeContract({ + address: mockFoxtokenContractAddress as Address, + abi: MockFOXToken.abi, + account: bob, + functionName: "approve", + args: [mockFoxStakingContractAddress, amountToStakeCryptoBaseUnit], + }); + const { transactionHash } = await publicClient.waitForTransactionReceipt({ + hash: approveTxHash, + }); + + console.log( + `Granted allowance for ${amountToStakeCryptoPrecision} FOX tokens to be spent by FOXStaking contract: ${transactionHash}`, + ); + + const stakeTxHash = await walletClient.writeContract({ + address: mockFoxStakingContractAddress as Address, + abi: FoxStaking.abi, + account: bob, + functionName: "stake", + args: [amountToStakeCryptoBaseUnit], + }); + + const { transactionHash: stakeTransactionHash } = + await publicClient.waitForTransactionReceipt({ hash: stakeTxHash }); + console.log( + `Staked ${amountToStakeCryptoPrecision} FOX from Bob to FOXStaking contract: ${stakeTransactionHash}`, + ); + + const bobStakedBalance = (await publicClient.readContract({ + address: mockFoxStakingContractAddress as Address, + abi: FoxStaking.abi, + functionName: "balanceOf", + args: [bob], + })) as number; + + console.log( + `Bob's staked balance: ${formatUnits(BigInt(bobStakedBalance), foxDecimals)} FOX`, + ); +}; diff --git a/scripts/rewards-distribution/tsconfig.json b/scripts/rewards-distribution/tsconfig.json index ce33b01..5e6e92e 100644 --- a/scripts/rewards-distribution/tsconfig.json +++ b/scripts/rewards-distribution/tsconfig.json @@ -13,6 +13,6 @@ "esModuleInterop": true, "skipLibCheck": true, "moduleResolution": "node16", - "resolveJsonModule": true, + "resolveJsonModule": true } } diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..94470d0 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,848 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgr/core@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" + integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== + +"@solidity-parser/parser@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.17.0.tgz#52a2fcc97ff609f72011014e4c5b485ec52243ef" + integrity sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw== + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.3.1, debug@^4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f" + integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== + +eslint-plugin-prettier@5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz#17cfade9e732cef32b5f5be53bd4e07afd8e67e1" + integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.8.6" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@8.57.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +ignore@^5.2.0, ignore@^5.3.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.0, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +mri@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-3.0.1.tgz#817033161def55ec9638567a2f3bbc876b3e7516" + integrity sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier-plugin-solidity@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.3.1.tgz#59944d3155b249f7f234dee29f433524b9a4abcf" + integrity sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA== + dependencies: + "@solidity-parser/parser" "^0.17.0" + semver "^7.5.4" + solidity-comments-extractor "^0.0.8" + +prettier@^3.2.5: + version "3.2.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" + integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== + +pretty-quick@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pretty-quick/-/pretty-quick-4.0.0.tgz#ea5cce85a5804bfbec7327b0e064509155d03f39" + integrity sha512-M+2MmeufXb/M7Xw3Afh1gxcYpj+sK0AxEfnfF958ktFeAyi5MsKY5brymVURQLgPLV1QaF5P4pb2oFJ54H3yzQ== + dependencies: + execa "^5.1.1" + find-up "^5.0.0" + ignore "^5.3.0" + mri "^1.2.0" + picocolors "^1.0.0" + picomatch "^3.0.1" + tslib "^2.6.2" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +semver@^7.5.4: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +simple-git-hooks@^2.11.1: + version "2.11.1" + resolved "https://registry.yarnpkg.com/simple-git-hooks/-/simple-git-hooks-2.11.1.tgz#4102f0b49dd72f148394a4446a958ad5ed461991" + integrity sha512-tgqwPUMDcNDhuf1Xf6KTUsyeqGdgKMhzaH4PAZZuzguOgTl5uuyeYe/8mWgAr6IBxB5V06uqEf6Dy37gIWDtDg== + +solidity-comments-extractor@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.8.tgz#f6e148ab0c49f30c1abcbecb8b8df01ed8e879f8" + integrity sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g== + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +synckit@^0.8.6: + version "0.8.8" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.8.tgz#fe7fe446518e3d3d49f5e429f443cf08b6edfcd7" + integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ== + dependencies: + "@pkgr/core" "^0.1.0" + tslib "^2.6.2" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tslib@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==