Start testing contracts

Signed-off-by: AlienTornadosaurusHex <>
This commit is contained in:
AlienTornadosaurusHex 2023-06-10 00:14:20 +00:00
parent a8e09194ec
commit 827e1d6991
13 changed files with 579 additions and 15 deletions

1
.env.example Normal file

@ -0,0 +1 @@
MAINNET_RPC_URL=

4
.gitignore vendored

@ -4,3 +4,7 @@ node_modules/
.env .env
.gitsigners .gitsigners
.trash .trash
yarn-error.log
yarn.lock
cache_hardhat
artifacts

@ -5,10 +5,11 @@ out = 'out'
libs = ["node_modules", "lib"] libs = ["node_modules", "lib"]
# Compiler # Compiler
evm_version = 'shanghai'
auto_detect_solc = true auto_detect_solc = true
via_ir = true
optimizer = true
optimizer-runs = 1 optimizer-runs = 1
optimizer = true
via_ir = true
# Network # Network
chain_id = 1 chain_id = 1
@ -19,6 +20,9 @@ verbosity = 2
# Remappings # Remappings
remappings = [ remappings = [
'src/=src/',
'base/=src/base/',
'common/=src/common/',
'ds-test/=lib/ds-test/src/', 'ds-test/=lib/ds-test/src/',
'solmate/=lib/solmate/src/', 'solmate/=lib/solmate/src/',
'forge-std/=lib/forge-std/src/', 'forge-std/=lib/forge-std/src/',

@ -9,15 +9,7 @@
}, },
"scripts": { "scripts": {
"postinstall": "chmod +x script/* && ./script/setup.sh", "postinstall": "chmod +x script/* && ./script/setup.sh",
"build": "chmod +x script/* && ./script/setup.sh", "build": "chmod +x script/* && ./script/setup.sh"
"prettier": "prettier --write 'src/**/*.sol'",
"prettier:list": "prettier --list-different 'src/**/*.sol'",
"prettier:check": "prettier --check 'src/**/*.sol'",
"solhint": "solhint --config ./.solhint.json 'src/**/*.sol' --fix",
"solhint:check": "solhint --config ./.solhint.json 'src/**/*.sol'",
"lint": "npm run prettier && npm run solhint",
"lint:check": "npm run prettier:check && npm run solhint:check",
"test": "forge test"
}, },
"devDependencies": { "devDependencies": {
"prettier": "^2.5.1", "prettier": "^2.5.1",

@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IGovernance } from "common/interfaces/IGovernance.sol";
import { IGnosisSafe } from "common/interfaces/IGnosisSafe.sol";
import { TornadoAddresses } from "common/TornadoAddresses.sol";
abstract contract TornadoProposal is TornadoAddresses {
function executeProposal() public virtual;
function getMultisig() internal pure returns (IGnosisSafe) {
return IGnosisSafe(getMultisigAddress());
}
function getTornToken() internal pure returns (IERC20) {
return IERC20(getTornTokenAddress());
}
function getGovernance() internal pure returns (IGovernance) {
return IGovernance(getGovernanceProxyAddress());
}
}

@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
contract TornadoAddresses {
function getENSAddress() internal pure returns (address) {
return 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e;
}
function getTornTokenAddress() internal pure returns (address) {
return 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
}
function getMultisigAddress() internal pure returns (address) {
return 0xb04E030140b30C27bcdfaafFFA98C57d80eDa7B4;
}
function getGovernanceProxyAddress() internal pure returns (address) {
return 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
}
function getStakingProxyAddress() internal pure returns (address) {
return 0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29;
}
function getRegistryProxyAddress() internal pure returns (address) {
return 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
}
}

@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
interface IGnosisSafe {
enum Operation {
Call,
DelegateCall
}
function NAME() external view returns (string memory);
function VERSION() external view returns (string memory);
function nonce() external view returns (uint256);
function domainSeparator() external view returns (bytes32);
function signedMessages(bytes32) external view returns (uint256);
function approvedHashes(address, bytes32) external view returns (uint256);
function setup(
address[] calldata _owners,
uint256 _threshold,
address to,
bytes calldata data,
address fallbackHandler,
address paymentToken,
uint256 payment,
address payable paymentReceiver
) external;
function execTransaction(
address to,
uint256 value,
bytes calldata data,
Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes calldata signatures
) external returns (bool success);
function requiredTxGas(address to, uint256 value, bytes calldata data, Operation operation)
external
returns (uint256);
function approveHash(bytes32 hashToApprove) external;
function signMessage(bytes calldata _data) external;
function isValidSignature(bytes calldata _data, bytes calldata _signature) external returns (bytes4);
function getMessageHash(bytes memory message) external view returns (bytes32);
function encodeTransactionData(
address to,
uint256 value,
bytes memory data,
Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
uint256 _nonce
) external view returns (bytes memory);
function getTransactionHash(
address to,
uint256 value,
bytes memory data,
Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
uint256 _nonce
) external view returns (bytes32);
}

@ -0,0 +1,56 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
enum ProposalState {
Pending,
Active,
Defeated,
Timelocked,
AwaitingExecution,
Executed,
Expired
}
struct Proposal {
// Creator of the proposal
address proposer;
// target addresses for the call to be made
address target;
// The block at which voting begins
uint256 startTime;
// The block at which voting ends: votes must be cast prior to this block
uint256 endTime;
// Current number of votes in favor of this proposal
uint256 forVotes;
// Current number of votes in opposition to this proposal
uint256 againstVotes;
// Flag marking whether the proposal has been executed
bool executed;
// Flag marking whether the proposal voting time has been extended
// Voting time can be extended once, if the proposal outcome has changed during CLOSING_PERIOD
bool extended;
}
interface IGovernance {
function initialized() external view returns (bool);
function initializing() external view returns (bool);
function EXECUTION_DELAY() external view returns (uint256);
function EXECUTION_EXPIRATION() external view returns (uint256);
function QUORUM_VOTES() external view returns (uint256);
function PROPOSAL_THRESHOLD() external view returns (uint256);
function VOTING_DELAY() external view returns (uint256);
function VOTING_PERIOD() external view returns (uint256);
function CLOSING_PERIOD() external view returns (uint256);
function VOTE_EXTEND_TIME() external view returns (uint256);
function torn() external view returns (address);
function proposals(uint256 index) external view returns (Proposal memory);
function lockedBalance(address account) external view returns (uint256);
function propose(address target, string memory description) external returns (uint256);
function castVote(uint256 proposalId, bool support) external;
function lock(address owner, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
function lockWithApproval(uint256 amount) external;
function execute(uint256 proposalId) external payable;
function state(uint256 proposalId) external view returns (ProposalState);
}

@ -73,7 +73,7 @@ library CurveChainedOracles {
bytes4 _selector = _selectors[o]; bytes4 _selector = _selectors[o];
// Index of the coin in the curve pool for second UINT256 selector // Index of the coin in the curve pool for second UINT256 selector
uint8 _coin = _coins[0]; uint8 _coin = _coins[o];
// Check whether the config actually works // Check whether the config actually works
if (_selector == PRICE_ORACLE_SELECTOR) { if (_selector == PRICE_ORACLE_SELECTOR) {
@ -106,7 +106,8 @@ library CurveChainedOracles {
uint256 priceDivisor = 1e18; uint256 priceDivisor = 1e18;
uint256 inverseNumerator = 1e36; uint256 inverseNumerator = 1e36;
uint256 numOracles = _oracle.length; // We know that each oracle addition encodes to exactly 26 bytes
uint256 numOracles = _oracle.length / 26;
for (uint256 o = 0; o < numOracles; o++) { for (uint256 o = 0; o < numOracles; o++) {
bytes32 chunk; bytes32 chunk;
@ -175,7 +176,7 @@ contract CurveFeeOracle is IFeeOracle {
/* @dev For each instance, a set of data which translates to a set of chained price oracle calls, we call /* @dev For each instance, a set of data which translates to a set of chained price oracle calls, we call
this data "chainedPriceOracles" because it encodes all data necessary to execute the chain and calc the this data "chainedPriceOracles" because it encodes all data necessary to execute the chain and calc the
price */ price */
mapping(ITornadoInstance => bytes) internal chainedPriceOracles; mapping(ITornadoInstance => bytes) public chainedPriceOracles;
/* @dev When setting, store the names as a historical record, key is keccak256(bytes) */ /* @dev When setting, store the names as a historical record, key is keccak256(bytes) */
mapping(bytes32 => string) public chainedPriceOracleNames; mapping(bytes32 => string) public chainedPriceOracleNames;

145
test/OracleTests.sol Normal file

@ -0,0 +1,145 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
// OZ Imports
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// Tornado imports
import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/ITornadoInstance.sol";
// STD Imports
import { Test } from "forge-std/Test.sol";
import { console2 } from "forge-std/console2.sol";
// Local imports
import { IGovernance, Proposal } from "common/interfaces/IGovernance.sol";
import { TornadoAddresses } from "common/TornadoAddresses.sol";
import { UniswapV3FeeOracle } from "src/v2/UniswapV3FeeOracle.sol";
import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "src/v2/CurveFeeOracle.sol";
import { InstanceData } from "src/v2/InstanceRegistry.sol";
contract OracleTests is Test {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
address public constant crvUSDUSDCStableswap2Pool = 0x4DEcE678ceceb27446b35C672dC7d61F30bAD69E;
address public constant tricryptoUSDCPool = 0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B;
ITornadoInstance public constant cu10_000 = ITornadoInstance(0x49f173CDAB99a2C3800F1255393DF9B7a17B82Bb);
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
UniswapV3FeeOracle v3FeeOracle;
CurveFeeOracle feeOracle;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TESTING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function setUp() public {
vm.createSelectFork(vm.envString("MAINNET_RPC_URL"));
v3FeeOracle = new UniswapV3FeeOracle(address(this));
v3FeeOracle.setGlobalTornPoolFee(10_000, true);
v3FeeOracle.setGlobalTwapIntervalSeconds(5400);
v3FeeOracle.setGlobalMinObservationCardinality(10);
feeOracle = new CurveFeeOracle(address(this));
feeOracle.setTornOracleIsUniswapV3(false);
feeOracle.setUniswapV3FeeOracle(v3FeeOracle);
}
function test_curveFeeSingleTricrypto() public {
_setCurveFeeSimpleTricryptoOracleForInstance(feeOracle, cu10_000); // CRVUSD 10_000
console2.log(
"\nShould be 30 * (ETH/USD) ------------------------------------------------\n",
uint256(
feeOracle.getFee(
TORN, cu10_000, InstanceData(IERC20(cu10_000.token()), 0, true, true), 30, 10_000
)
),
"\n------------------------------------------------------------------------\n"
);
}
function test_curveFeeChainedTORN() public {
feeOracle.setTornOracleIsUniswapV3(true);
_setCurveFeeChainedOracleForInstance(feeOracle, cu10_000); // CRVUSD 10_000
console2.log(
"\nTORN Fee calculated ------------------------------------------------------\n",
uint256(
feeOracle.getFee(
TORN, cu10_000, InstanceData(IERC20(cu10_000.token()), 0, true, true), 30, 10_000
)
),
"\n------------------------------------------------------------------------\n"
);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function _setCurveFeeSimpleTricryptoOracleForInstance(
CurveFeeOracle _feeOracle,
ITornadoInstance _instance
) internal {
ICurvePriceOracle[] memory _oracles = new ICurvePriceOracle[](1);
_oracles[0] = ICurvePriceOracle(tricryptoUSDCPool);
bytes4[] memory _selectors = new bytes4[](1);
_selectors[0] = CurveChainedOracles.PRICE_ORACLE_UINT256_SELECTOR;
uint8[] memory _coins = new uint8[](1);
_coins[0] = 1; // ETHER
bool[] memory _invert = new bool[](1);
_invert[0] = true;
_feeOracle.modifyChainedOracleForInstance(
_instance, _oracles, _selectors, _coins, _invert, "ETH/CRVUSD"
);
}
function _setCurveFeeChainedOracleForInstance(CurveFeeOracle _feeOracle, ITornadoInstance _instance)
internal
{
ICurvePriceOracle[] memory _oracles = new ICurvePriceOracle[](2);
_oracles[0] = ICurvePriceOracle(crvUSDUSDCStableswap2Pool);
_oracles[1] = ICurvePriceOracle(tricryptoUSDCPool);
bytes4[] memory _selectors = new bytes4[](2);
_selectors[0] = CurveChainedOracles.PRICE_ORACLE_SELECTOR;
_selectors[1] = CurveChainedOracles.PRICE_ORACLE_UINT256_SELECTOR;
uint8[] memory _coins = new uint8[](2);
_coins[1] = 1;
bool[] memory _invert = new bool[](2);
_invert[0] = false;
_invert[1] = true;
_feeOracle.modifyChainedOracleForInstance(
_instance, _oracles, _selectors, _coins, _invert, "ETH/CRVUSD"
);
}
}

75
test/ProposalTests.sol Normal file

@ -0,0 +1,75 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
// OZ Imports
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// Local imports
import { InfrastructureUpgradeProposal } from "src/proposals/InfrastructureUpgradeProposal.sol";
import { UniswapV3FeeOracle } from "src/v2/UniswapV3FeeOracle.sol";
import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "src/v2/CurveFeeOracle.sol";
import { InstanceRegistry } from "src/v2/InstanceRegistry.sol";
import { FeeOracleManager } from "src/v2/FeeOracleManager.sol";
import { TornadoRouter } from "src/v2/TornadoRouter.sol";
// Test imports
import { TornadoProposalTest } from "./TornadoProposalTest.sol";
contract ProposalTests is TornadoProposalTest {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
InstanceRegistry implInstanceRegisty;
UniswapV3FeeOracle v3FeeOracle;
FeeOracleManager feeOracleManager;
TornadoRouter router;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TESTING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function setUp() public override {
super.setUp();
implInstanceRegisty = new InstanceRegistry(address(governance));
v3FeeOracle = new UniswapV3FeeOracle(address(governance));
feeOracleManager = new FeeOracleManager(address(TORN), address(governance));
router = new TornadoRouter(address(governance));
}
function test_infrastructureUpgradeProposalBasic() public {
// Create proposal
address proposal = address(
new InfrastructureUpgradeProposal(
address(v3FeeOracle),
address(feeOracleManager),
address(implInstanceRegisty),
address(router)
)
);
// Propose
uint256 id = easyPropose(proposal);
// Wait
waitUntilExecutable(id);
// Exec
governance.execute(id);
}
}

@ -0,0 +1,139 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { IGovernance, Proposal } from "common/interfaces/IGovernance.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Test } from "forge-std/Test.sol";
import { TornadoAddresses } from "common/TornadoAddresses.sol";
contract TornadoProposalTest is Test, TornadoAddresses {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PERMIT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bytes32 public constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant EIP712_DOMAIN = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("TornadoCash")),
keccak256(bytes("1")),
1,
VERIFIER_ADDRESS
)
);
uint16 public constant PERMIT_FUNC_SELECTOR = uint16(0x1901);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TEST DUMMIES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
address public constant TEST_REAL_ADDRESS_WITH_BALANCE = 0x9Ff3C1Bea9ffB56a78824FE29f457F066257DD58;
address public constant TEST_RELAYER_ADDRESS = 0x30F96AEF199B399B722F8819c9b0723016CEAe6C; // moon-relayer.eth
// (just for testing)
uint256 public constant TEST_PRIVATE_KEY_ONE =
0x66ddbd7cbe4a566df405f6ded0b908c669f88cdb1656380c050e3a457bd21df0;
uint256 public constant TEST_PRIVATE_KEY_TWO =
0xa4c8c98120e77741a87a116074a2df4ddb20d1149069290fd4a3d7ee65c55064;
address public constant TEST_ADDRESS_ONE = 0x118251976c65AFAf291f5255450ddb5b6A4d8B88;
address public constant TEST_ADDRESS_TWO = 0x63aE7d90Eb37ca39FC62dD9991DbEfeE70673a20;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ADDRESSES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
address public constant VERIFIER_ADDRESS = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GOVERNANCE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
string public constant PROPOSAL_DESCRIPTION = "{title:'Some proposal',description:''}";
uint256 public EXECUTION_DELAY;
uint256 public EXECUTION_EXPIRATION;
uint256 public QUORUM_VOTES;
uint256 public PROPOSAL_THRESHOLD;
uint256 public VOTING_DELAY;
uint256 public VOTING_PERIOD;
uint256 public CLOSING_PERIOD;
uint256 public VOTE_EXTEND_TIME;
IGovernance public immutable governance = IGovernance(getGovernanceProxyAddress());
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TEST UTILS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function setUp() public virtual {
vm.createSelectFork(vm.envString("MAINNET_RPC_URL"));
_fetchConfiguration();
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function waitUntilExecutable(uint256 proposalId) internal {
uint256 proposalExecutableTime = getProposalExecutableTime(proposalId);
require(block.timestamp < proposalExecutableTime, "Too late to execute proposal");
vm.warp(proposalExecutableTime);
}
function easyPropose(address proposalAddress) public returns (uint256) {
retrieveAndLockBalance(TEST_PRIVATE_KEY_ONE, TEST_ADDRESS_ONE, QUORUM_VOTES);
retrieveAndLockBalance(TEST_PRIVATE_KEY_TWO, TEST_ADDRESS_TWO, 1 ether);
/* ----------PROPOSER------------ */
vm.startPrank(TEST_ADDRESS_ONE);
uint256 proposalId = governance.propose(proposalAddress, PROPOSAL_DESCRIPTION);
// TIME-TRAVEL
vm.warp(block.timestamp + 6 hours);
governance.castVote(proposalId, true);
vm.stopPrank();
/* ------------------------------ */
/* -------------VOTER-------------*/
vm.startPrank(TEST_ADDRESS_TWO);
governance.castVote(proposalId, true);
vm.stopPrank();
/* ------------------------------ */
return proposalId;
}
function retrieveAndLockBalance(uint256, address voter, uint256 amount) internal {
/* ----------GOVERNANCE------- */
vm.startPrank(getGovernanceProxyAddress());
IERC20(getTornTokenAddress()).transfer(voter, amount);
vm.stopPrank();
/* ----------------------------*/
/* ----------VOTER------------ */
vm.startPrank(voter);
IERC20(getTornTokenAddress()).approve(address(governance), amount);
governance.lockWithApproval(amount);
vm.stopPrank();
/* ----------------------------*/
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function getProposalExecutableTime(uint256 proposalId) internal view returns (uint256) {
Proposal memory proposal = IGovernance(getGovernanceProxyAddress()).proposals(proposalId);
return proposal.endTime + EXECUTION_DELAY + 1 hours;
}
function _fetchConfiguration() internal {
EXECUTION_DELAY = governance.EXECUTION_DELAY();
EXECUTION_EXPIRATION = governance.EXECUTION_EXPIRATION();
QUORUM_VOTES = governance.QUORUM_VOTES();
PROPOSAL_THRESHOLD = governance.PROPOSAL_THRESHOLD();
VOTING_DELAY = governance.VOTING_DELAY();
VOTING_PERIOD = governance.VOTING_PERIOD();
CLOSING_PERIOD = governance.CLOSING_PERIOD();
VOTE_EXTEND_TIME = governance.VOTE_EXTEND_TIME();
}
}

@ -360,7 +360,12 @@ prettier-plugin-solidity@^1.0.0-beta.19:
semver "^7.3.8" semver "^7.3.8"
solidity-comments-extractor "^0.0.7" solidity-comments-extractor "^0.0.7"
prettier@^2.5.1, prettier@^2.8.3: prettier@^2.5.1:
version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
prettier@^2.8.3:
version "2.8.4" version "2.8.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3"
integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw== integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==