diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..8551061baf --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +diff-tests/core/driver/traces.json filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 6071d7ba42..40281f218c 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -6,12 +6,16 @@ jobs: steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 + with: + lfs: true + - name: Checkout LFS objects + run: git lfs checkout - name: Setup Go uses: actions/setup-go@v2 with: go-version: "1.18.0" # The Go version to download (if necessary) and use. - - name: Unit and e2e tests + - name: Unit, e2e and difference tests run: go test ./... - name: Integration tests diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f6e4846bf2..818b358f50 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,11 +12,18 @@ jobs: steps: - uses: actions/checkout@v2 with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + lfs: true + - name: Checkout LFS objects + run: git lfs checkout + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: "1.18.0" # The Go version to download (if necessary) and use. - name: Test with coverage run: go test -coverprofile=coverage.out ./... - name: SonarCloud Scan uses: SonarSource/sonarcloud-github-action@master env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 6371471814..2626a3d83c 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/setup-go@v3 with: - go-version: 1.17 + go-version: 1.18 - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000000..8167fa16c3 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,5 @@ +linters-settings: + staticcheck: + checks: + - all + - "-SA1019" # cosmosEd25519 allowed in tests diff --git a/Dockerfile b/Dockerfile index 699672f541..acb53bbede 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ RUN apk add --no-cache $PACKAGES ENV CGO_ENABLED=0 ENV GOOS=linux +ENV GOFLAGS="-buildvcs=false" WORKDIR /downloads diff --git a/diff-tests/core/README.md b/diff-tests/core/README.md new file mode 100644 index 0000000000..fd94412726 --- /dev/null +++ b/diff-tests/core/README.md @@ -0,0 +1,136 @@ +# Differential testing for Interchain Security 'core' protocol + +This directory contains model and trace generation code for the differential approach to testing Interchain Security. In particular, this work is used to test 'core' (normal operation) features of the protocol. + +At a high level, the model consists of one Provider chain and one Consumer chain. There is a single delegator account on the Provider, whose actions will change the delegation and thus the tokens and voting power of the validators. The voting power changes are relayed to the Consumer chain. The entire cycle of unbonding operation maturity is captured, because the Consumer will +send unbonding maturity packets. Moreoever, slashing is modelled, as the Consumer can initiate slashing actions. + +## Scope + +### Tested (Unchecked means that work is in progress. Checked means the work is complete.) + +The following aspects of the system are tested + +- [x] Sending VSC packets from provider to one consumer +- [x] Sending VSC maturities from one consumer to provider +- [x] Slashing logic (not including actual token burning) +- [x] Validator power change +- [x] Validators leaving or joining the active validor set +- [x] Consumer initiated slashing +- [x] Delegation operations +- [x] Undelegation operations +- [x] Validator unbonding +- [x] Valiator jailing +- [x] Validator tombstoning +- [x] Packet acknowledgements +- [x] The 'Bond Based Consumer Voting Power' property ([link](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/system_model_and_properties.md#system-properties)) +- [ ] The 'Validator Set Replication' property ([link](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/system_model_and_properties.md#system-properties)) +- [ ] The 'Slashable Consumer Misbehavior' property ([link](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/system_model_and_properties.md#system-properties)) (_maybe_) +- [ ] PendingVSC when consumer start (_maybe_) +- [ ] Redelegation operations +- [ ] Unjailing operations + +### NOT Tested + +The following aspects of the system are not tested by this work. + +- Completing the IBC handshakes +- Repairing an expired IBC channel through governance +- Slashing with non-zero slash factors +- Submitting proposals +- Executing proposals +- Adding a new consumer chain +- Removing a consumer chain for any reason +- Distribution of rewards +- Provider Governance +- Consumer Governance/Democracy +- Anything to do with cosmwasm +- Client expiry +- Packet timeouts +- Restarting any chain from exported state +- Any logic that deals with having _more than one consumer chain_ +- Multiple delegator accounts + +## Usage + +### Overview + +This typescript project contains code for + +- Modelling the aspects of the system listed under TESTED above +- Generating and executing actions against a model system based on those aspects, in order to explore various behaviors. The actions are generated using heuristics and randomness. +- Recording traces of executions to file +- Choosing a set of traces in a manner convenient for testing the SUT. +- Replaying a given existing trace against a new model instance, for debugging purposes. + +### Usage prerequisities + +```bash +# nodejs version 16 is required. +node --version +# yarn package manager is required +yarn --version +# setup the project +yarn install +``` + +### Commands + +There are several top level yarn project scripts which can be run via + +```bash +yarn +``` + +as per the `scripts` entry in [package.json](./package.json). The most important of these are + +```bash +# install the project +yarn install; +# build in watch mode. Repeatedly build the project when the src changes +# recommended to run in background process +yarn build:watch +# start main.ts - the entry point to the program +yarn start +# test - run the tests in __tests__ +yarn test +``` + +The actual functionality has entrypoint in [src/main.ts](./src/main.ts). Please see the file for details. The available functionalities are + +```bash +# generate traces for x seconds +yarn start gen +# check properties for x seconds +yarn start properties +# create a subset of traces +yarn start subset +# replay a trace from a file (for debugging) +yarn start replay +``` + +### Workflow + +A workflow of updating the model and generating new traces for testing against the SUT might look like + +```bash +# Generate traces for 30 seconds +yarn start gen 30 +# Collect and compact a subset of these traces +yarn start subset 200 +``` + +### Extending the model + +All of the semantic logic of the model that relates to how the system is supposed to work is contained in [src/model.ts](./src/model.ts). All of the logic for generating actions (and thus traces) against the model is contained in [src/main.ts](./src/main.ts). The remaining files are less important. + +### Ensuring a consistent Trace format + +The golang test driver must be able to parse the traces output by this Typescript project. Several tools exist to generate golang type definitions from json files. I strongly suggest using [gojsonstruct](https://github.com/twpayne/go-jsonstruct) to generate a new golang definition whenever the json trace format changes. The steps to do this are + +```bash +# Pass the content of traces.json to gojsonstruct binary which will output a golang type definition +gojsonstruct < > trace.go +``` + +The `trace.go` file output from the above command should be reconciled with the content in `difftest/trace.go`. diff --git a/diff-tests/core/driver/.gitignore b/diff-tests/core/driver/.gitignore new file mode 100644 index 0000000000..a241de283a --- /dev/null +++ b/diff-tests/core/driver/.gitignore @@ -0,0 +1,2 @@ +*.log +debug.json \ No newline at end of file diff --git a/diff-tests/core/driver/common.go b/diff-tests/core/driver/common.go new file mode 100644 index 0000000000..e465cbc3b3 --- /dev/null +++ b/diff-tests/core/driver/common.go @@ -0,0 +1,91 @@ +package core + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + abci "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtypes "github.com/tendermint/tendermint/types" +) + +const P = "provider" +const C = "consumer" + +// ValStates represents the total delegation +// and bond status of a validator +type ValStates struct { + Delegation []int + Tokens []int + ValidatorExtraTokens []int + Status []stakingtypes.BondStatus +} + +type InitState struct { + PKSeeds []string + NumValidators int + MaxValidators int + InitialDelegatorTokens int + SlashDoublesign sdk.Dec + SlashDowntime sdk.Dec + UnbondingP time.Duration + UnbondingC time.Duration + Trusting time.Duration + MaxClockDrift time.Duration + BlockSeconds time.Duration + ConsensusParams *abci.ConsensusParams + ValStates ValStates + MaxEntries int +} + +var initState InitState + +func init() { + // tokens === power + sdk.DefaultPowerReduction = sdk.NewInt(1) + initState = InitState{ + PKSeeds: []string{ + // Fixed seeds are used to create the private keys for validators. + // The seeds are chosen to ensure that the resulting validators are + // sorted in descending order by the staking module. + "bbaaaababaabbaabababbaabbbbbbaaa", + "abbbababbbabaaaaabaaabbbbababaab", + "bbabaabaabbbbbabbbaababbbbabbbbb", + "aabbbabaaaaababbbabaabaabbbbbbba"}, + NumValidators: 4, + MaxValidators: 2, + InitialDelegatorTokens: 10000000000000, + SlashDoublesign: sdk.NewDec(0), + SlashDowntime: sdk.NewDec(0), + UnbondingP: time.Second * 70, + UnbondingC: time.Second * 50, + Trusting: time.Second * 49, + MaxClockDrift: time.Second * 10000, + BlockSeconds: time.Second * 6, + ValStates: ValStates{ + Delegation: []int{4000, 3000, 2000, 1000}, + Tokens: []int{5000, 4000, 3000, 2000}, + ValidatorExtraTokens: []int{1000, 1000, 1000, 1000}, + Status: []stakingtypes.BondStatus{stakingtypes.Bonded, stakingtypes.Bonded, + stakingtypes.Unbonded, stakingtypes.Unbonded}, + }, + MaxEntries: 1000000, + ConsensusParams: &abci.ConsensusParams{ + Block: &abci.BlockParams{ + MaxBytes: 9223372036854775807, + MaxGas: 9223372036854775807, + }, + Evidence: &tmproto.EvidenceParams{ + MaxAgeNumBlocks: 302400, + MaxAgeDuration: 504 * time.Hour, // 3 weeks is the max duration + MaxBytes: 10000, + }, + Validator: &tmproto.ValidatorParams{ + PubKeyTypes: []string{ + tmtypes.ABCIPubKeyTypeEd25519, + }, + }, + }, + } +} diff --git a/diff-tests/core/driver/core_test.go b/diff-tests/core/driver/core_test.go new file mode 100644 index 0000000000..39542f846b --- /dev/null +++ b/diff-tests/core/driver/core_test.go @@ -0,0 +1,468 @@ +package core + +import ( + "fmt" + "math" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + + channelkeeper "github.com/cosmos/ibc-go/v3/modules/core/04-channel/keeper" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" + + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + appConsumer "github.com/cosmos/interchain-security/app/consumer" + appProvider "github.com/cosmos/interchain-security/app/provider" + + simibc "github.com/cosmos/interchain-security/testutil/simibc" + + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + consumerkeeper "github.com/cosmos/interchain-security/x/ccv/consumer/keeper" +) + +type CoreSuite struct { + suite.Suite + + // the current traces being executed + traces Traces + + // simulate a relayed path + simibc simibc.RelayedPath + + // keep around validators for easy access + valAddresses []sdk.ValAddress + + // offsets: the model time and heights start at 0 + // so offsets are needed for comparisons. + offsetTimeUnix int64 + offsetHeight int64 +} + +// ctx returns the sdk.Context for the chain +func (s *CoreSuite) ctx(chain string) sdk.Context { + return s.chain(chain).GetContext() +} + +func (s *CoreSuite) chainID(chain string) string { + return map[string]string{P: ibctesting.GetChainID(0), C: ibctesting.GetChainID(1)}[chain] +} + +// chain returns the TestChain for a given chain identifier +func (s *CoreSuite) chain(chain string) *ibctesting.TestChain { + return map[string]*ibctesting.TestChain{P: s.providerChain(), C: s.consumerChain()}[chain] +} + +func (s *CoreSuite) providerChain() *ibctesting.TestChain { + return s.simibc.Chain(ibctesting.GetChainID(0)) +} + +func (s *CoreSuite) consumerChain() *ibctesting.TestChain { + return s.simibc.Chain(ibctesting.GetChainID(1)) +} + +func (b *CoreSuite) providerStakingKeeper() stakingkeeper.Keeper { + return b.providerChain().App.(*appProvider.App).StakingKeeper +} + +func (b *CoreSuite) providerSlashingKeeper() slashingkeeper.Keeper { + return b.providerChain().App.(*appProvider.App).SlashingKeeper +} + +func (b *CoreSuite) consumerKeeper() consumerkeeper.Keeper { + return b.consumerChain().App.(*appConsumer.App).ConsumerKeeper +} + +// height returns the height of the current header of chain +func (s *CoreSuite) height(chain string) int64 { + return s.chain(chain).CurrentHeader.GetHeight() +} + +// time returns the time of the current header of chain +func (s *CoreSuite) time(chain string) time.Time { + return s.chain(chain).CurrentHeader.Time +} + +// delegator retrieves the address for the delegator account +func (s *CoreSuite) delegator() sdk.AccAddress { + return s.providerChain().SenderAccount.GetAddress() +} + +// validator returns the address for the validator with id (ix) i +func (s *CoreSuite) validator(i int64) sdk.ValAddress { + return s.valAddresses[i] +} + +// consAddr returns the ConsAdd for the validator with id (ix) i +func (s *CoreSuite) consAddr(i int64) sdk.ConsAddress { + return sdk.ConsAddress(s.validator(i)) +} + +// isJailed returns the jail status of validator with id (ix) i +func (s *CoreSuite) isJailed(i int64) bool { + val, found := s.providerStakingKeeper().GetValidator(s.ctx(P), s.validator(i)) + s.Require().Truef(found, "GetValidator() -> !found") + return val.IsJailed() +} + +// consumerPower returns the power on the consumer chain for +// validator with id (ix) i +func (s *CoreSuite) consumerPower(i int64) (int64, error) { + v, found := s.consumerKeeper().GetCCValidator(s.ctx(C), s.validator(i)) + if !found { + return 0, fmt.Errorf("GetCCValidator() -> !found") + } + return v.Power, nil +} + +// delegation returns the number of delegated tokens in the delegation from +// the delegator account to the validator with id (ix) i +func (s *CoreSuite) delegation(i int64) int64 { + d, found := s.providerStakingKeeper().GetDelegation(s.ctx(P), s.delegator(), s.validator(i)) + s.Require().Truef(found, "GetDelegation() -> !found") + return d.Shares.TruncateInt64() +} + +// validatorStatus returns the validator status for validator with id (ix) i +// on the provider chain +func (s *CoreSuite) validatorStatus(i int64) stakingtypes.BondStatus { + v, found := s.providerStakingKeeper().GetValidator(s.ctx(P), s.validator(i)) + s.Require().Truef(found, "GetValidator() -> !found") + return v.GetStatus() +} + +// providerTokens returns the number of tokens that the validator with +// id (ix) i has delegated to it in total on the provider chain +func (s *CoreSuite) providerTokens(i int64) int64 { + v, found := s.providerStakingKeeper().GetValidator(s.ctx(P), s.validator(i)) + s.Require().Truef(found, "GetValidator() -> !found") + return v.Tokens.Int64() +} + +// delegatorBalance returns the balance of the delegator account +func (s *CoreSuite) delegatorBalance() int64 { + d := s.delegator() + bal := s.providerChain().App.(*appProvider.App).BankKeeper.GetBalance(s.ctx(P), d, sdk.DefaultBondDenom) + return bal.Amount.Int64() +} + +// delegate delegates amt tokens to validator val +func (s *CoreSuite) delegate(val int64, amt int64) { + server := stakingkeeper.NewMsgServerImpl(s.providerStakingKeeper()) + coin := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(amt)) + d := s.delegator() + v := s.validator(val) + msg := stakingtypes.NewMsgDelegate(d, v, coin) + _, err := server.Delegate(sdk.WrapSDKContext(s.ctx(P)), msg) + // There may or may not be an error, depending on the trace + _ = err +} + +// undelegate undelegates amt tokens from validator val +func (s *CoreSuite) undelegate(val int64, amt int64) { + server := stakingkeeper.NewMsgServerImpl(s.providerStakingKeeper()) + coin := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(amt)) + d := s.delegator() + v := s.validator(val) + msg := stakingtypes.NewMsgUndelegate(d, v, coin) + _, err := server.Undelegate(sdk.WrapSDKContext(s.ctx(P)), msg) + // There may or may not be an error, depending on the trace + _ = err +} + +// consumerSlash simulates a slash event occurring on the consumer chain. +// It can be for a downtime or doublesign. +func (s *CoreSuite) consumerSlash(val sdk.ConsAddress, h int64, isDowntime bool) { + kind := stakingtypes.DoubleSign + if isDowntime { + kind = stakingtypes.Downtime + } + ctx := s.ctx(C) + before := len(ctx.EventManager().Events()) + s.consumerKeeper().Slash(ctx, val, h, 0, sdk.Dec{}, kind) + // consumer module emits packets on slash, so these must be collected. + evts := ctx.EventManager().ABCIEvents() + for _, e := range evts[before:] { + if e.Type == channeltypes.EventTypeSendPacket { + packet, err := channelkeeper.ReconstructPacketFromEvent(e) + s.Require().NoError(err) + s.simibc.Link.AddPacket(s.chainID(C), packet) + } + } +} + +func (s *CoreSuite) updateClient(chain string) { + s.simibc.UpdateClient(s.chainID(chain)) +} + +// deliver numPackets packets from the network to chain +func (s *CoreSuite) deliver(chain string, numPackets int) { + // Makes sure client is updated + s.updateClient(chain) + // Deliver any outstanding acks + s.simibc.DeliverAcks(s.chainID(chain), 999999) + // Consume deliverable packets from the network + s.simibc.DeliverPackets(s.chainID(chain), numPackets) +} + +func (s *CoreSuite) endAndBeginBlock(chain string) { + s.simibc.EndAndBeginBlock(s.chainID(chain), initState.BlockSeconds, func() { + s.matchState() + }) +} + +// matchState compares the state in the SUT to the state in the +// the model. +func (s *CoreSuite) matchState() { + + // Get a diagnostic for debugging + diagnostic := s.traces.Diagnostic() + chain := s.traces.Action().Chain + + // Model time, height start at 0 so we need an offset for comparisons. + sutTimeOffset := time.Unix(s.offsetTimeUnix, 0).Add(-initState.BlockSeconds).UTC() + modelTimeOffset := time.Duration(s.traces.Time()) * time.Second + sutHeightOffset := s.offsetHeight - 1 + modelHeightOffset := int64(s.traces.Height()) + s.Require().Equalf(sutTimeOffset.Add(modelTimeOffset), s.time(chain), diagnostic+"%s Time mismatch", chain) + s.Require().Equalf(sutHeightOffset+modelHeightOffset, s.height(chain), diagnostic+"%s Time mismatch", chain) + if chain == P { + for j := 0; j < initState.NumValidators; j++ { + have := s.validatorStatus(int64(j)) + s.Require().Equalf(s.traces.Status(j), have, diagnostic+"P bond status mismatch for val %d, expect %s, have %s", j, s.traces.Status(j).String(), have.String()) + } + for j := 0; j < initState.NumValidators; j++ { + s.Require().Equalf(int64(s.traces.Tokens(j)), s.providerTokens(int64(j)), diagnostic+"P tokens mismatch for val %d", j) + } + // TODO: delegations + s.Require().Equalf(int64(s.traces.DelegatorTokens()), s.delegatorBalance(), diagnostic+"P del balance mismatch") + for j := 0; j < initState.NumValidators; j++ { + s.Require().Equalf(s.traces.Jailed(j) != nil, s.isJailed(int64(j)), diagnostic+"P jail status mismatch for val %d", j) + } + } + if chain == C { + for j := 0; j < initState.NumValidators; j++ { + exp := s.traces.ConsumerPower(j) + actual, err := s.consumerPower(int64(j)) + if exp != nil { + s.Require().Nilf(err, diagnostic+" validator not found") + s.Require().Equalf(int64(*exp), actual, diagnostic+" power mismatch for val %d", j) + } else { + s.Require().Errorf(err, diagnostic+" power mismatch for val %d, expect 0 (nil), got %d", j, actual) + } + } + // TODO: outstanding downtime + } +} + +func (s *CoreSuite) executeTrace() { + for i := range s.traces.Actions() { + s.traces.CurrentActionIx = i + + a := s.traces.Action() + + switch a.Kind { + case "Delegate": + s.delegate( + int64(a.Val), + int64(a.Amt), + ) + case "Undelegate": + s.undelegate( + int64(a.Val), + int64(a.Amt), + ) + case "ConsumerSlash": + s.consumerSlash( + s.consAddr(int64(a.Val)), + // The SUT height is greater than the model height + // because the SUT has to do initialization. + int64(a.InfractionHeight)+s.offsetHeight, + a.IsDowntime, + ) + case "UpdateClient": + s.updateClient(a.Chain) + case "Deliver": + s.deliver(a.Chain, a.NumPackets) + case "EndAndBeginBlock": + s.endAndBeginBlock(a.Chain) + default: + s.Require().FailNow("Failed to parse action") + } + } +} + +// TestAssumptions tests that the assumptions used to write the difftest +// driver hold. This test therefore does not test the system, but only that +// the driver is correctly setup. +func (s *CoreSuite) TestAssumptions() { + + const FAIL_MSG = "Assumptions for core diff test failed: there is a problem with the driver or how the test is setup." + + // Staking module maxValidators param is correct + maxValsE := uint32(initState.MaxValidators) + maxVals := s.providerStakingKeeper().GetParams(s.ctx(P)).MaxValidators + + if maxValsE != maxVals { + s.T().Fatal(FAIL_MSG) + } + + // Delegator balance is correct + s.Require().Equal(int64(initState.InitialDelegatorTokens), s.delegatorBalance()) + + // Slash factors are correct + s.Require().Equal(initState.SlashDowntime, s.providerSlashingKeeper().SlashFractionDowntime(s.ctx(P))) + s.Require().Equal(initState.SlashDoublesign, s.providerSlashingKeeper().SlashFractionDoubleSign(s.ctx(P))) + + // Provider unbonding period is correct + stakeParams := s.providerStakingKeeper().GetParams(s.ctx(P)) + s.Require().Equal(stakeParams.UnbondingTime, initState.UnbondingP) + // Consumer unbonding period is correct + s.Require().Equal(s.consumerKeeper().UnbondingTime(s.ctx(C)), initState.UnbondingC) + + // Each validator has signing info + for i := 0; i < len(initState.ValStates.Tokens); i++ { + _, found := s.providerSlashingKeeper().GetValidatorSigningInfo(s.ctx(P), s.consAddr(int64(i))) + if !found { + s.Require().FailNow(FAIL_MSG) + } + } + + // Provider delegations are correct + for i := 0; i < len(initState.ValStates.Delegation); i++ { + E := int64(initState.ValStates.Delegation[i]) + A := s.delegation(int64(i)) + if E != A { + s.T().Fatal(FAIL_MSG) + } + } + + // Provider validator tokens are correct + for i := 0; i < len(initState.ValStates.Tokens); i++ { + E := int64(initState.ValStates.Tokens[i]) + A := s.providerTokens(int64(i)) + if E != A { + s.T().Fatal(FAIL_MSG) + } + } + + // Provider validator status is correct + for i := 0; i < len(initState.ValStates.Status); i++ { + E := initState.ValStates.Status[i] + A := s.validatorStatus(int64(i)) + if E != A { + s.T().Fatal(FAIL_MSG) + } + } + + // Staking module does not contain undelegations + s.providerStakingKeeper().IterateUnbondingDelegations(s.ctx(P), + func(index int64, ubd stakingtypes.UnbondingDelegation) bool { + s.T().Fatal(FAIL_MSG) + return false // Don't stop + }) + + // Staking module does contain redelegations + s.providerStakingKeeper().IterateRedelegations(s.ctx(P), + func(index int64, ubd stakingtypes.Redelegation) bool { + s.T().Fatal(FAIL_MSG) + return false // Don't stop + }) + + // Staking module does not contain unbonding validators + endTime := time.Unix(math.MaxInt64, 0) + endHeight := int64(math.MaxInt64) + unbondingValIterator := s.providerStakingKeeper().ValidatorQueueIterator(s.ctx(P), endTime, endHeight) + defer unbondingValIterator.Close() + for ; unbondingValIterator.Valid(); unbondingValIterator.Next() { + s.T().Fatal(FAIL_MSG) + } + + // Consumer has no slash requests + s.Require().Empty(s.consumerKeeper().GetPendingSlashRequests(s.ctx(C))) + + // Consumer has no maturities + s.consumerKeeper().IteratePacketMaturityTime(s.ctx(C), + func(vscId uint64, timeNs uint64) bool { + s.T().Fatal(FAIL_MSG) + return false // Don't stop + }) + + // Consumer power + for i := 0; i < len(initState.ValStates.Status); i++ { + expectFound := initState.ValStates.Status[i] == stakingtypes.Bonded + expectPower := initState.ValStates.Tokens[i] + addr := s.validator(int64(i)) + val, found := s.consumerKeeper().GetCCValidator(s.ctx(C), addr) + s.Require().Equal(expectFound, found) + if expectFound { + if int64(expectPower) != val.Power { + s.T().Fatal(FAIL_MSG) + } + } + } + + // The offset time is the last committed time, but the SUT is +1 block ahead + // because the currentHeader time is ahead of the last committed. Therefore sub + // the difference (duration of 1 block). + s.Require().Equal(int64(s.offsetTimeUnix), s.time(P).Add(-initState.BlockSeconds).Unix()) + s.Require().Equal(int64(s.offsetTimeUnix), s.time(C).Add(-initState.BlockSeconds).Unix()) + + // The offset height is the last committed height, but the SUT is +1 because + // the currentHeader is +1 ahead of the last committed. Therefore sub 1. + s.Require().Equal(s.offsetHeight, s.height(P)-1) + s.Require().Equal(s.offsetHeight, s.height(C)-1) + + // Network is empty + s.Require().Empty(s.simibc.Link.OutboxPackets[P]) + s.Require().Empty(s.simibc.Link.OutboxPackets[C]) + s.Require().Empty(s.simibc.Link.OutboxAcks[P]) + s.Require().Empty(s.simibc.Link.OutboxAcks[C]) +} + +// Test a set of traces +func (s *CoreSuite) TestTraces() { + s.traces = Traces{ + Data: LoadTraces("traces.json"), + } + // s.traces.Data = []TraceData{s.traces.Data[69]} + for i := range s.traces.Data { + s.Run(fmt.Sprintf("Trace num: %d", i), func() { + // Setup a new pair of chains for each trace + s.SetupTest() + + s.traces.CurrentTraceIx = i + defer func() { + // If a panic occurs, we trap it to print a diagnostic + // and improve debugging experience. + if r := recover(); r != nil { + fmt.Println(s.traces.Diagnostic()) + fmt.Println(r) + // Double panic to halt. + panic("Panic occurred during TestTraces") + } + }() + // Record information about the trace, for debugging + // diagnostics. + s.executeTrace() + }) + } +} + +func TestCoreSuite(t *testing.T) { + suite.Run(t, new(CoreSuite)) +} + +// SetupTest sets up the test suite in a 'zero' state which matches +// the initial state in the model. +func (s *CoreSuite) SetupTest() { + state := initState + path, valAddresses, offsetHeight, offsetTimeUnix := GetZeroState(&s.Suite, state) + s.valAddresses = valAddresses + s.offsetHeight = offsetHeight + s.offsetTimeUnix = offsetTimeUnix + s.simibc = simibc.MakeRelayedPath(s.Suite.T(), path) +} diff --git a/diff-tests/core/driver/seed_gen_fuzzy_test.go b/diff-tests/core/driver/seed_gen_fuzzy_test.go new file mode 100644 index 0000000000..3532537a92 --- /dev/null +++ b/diff-tests/core/driver/seed_gen_fuzzy_test.go @@ -0,0 +1,84 @@ +package core_test + +import ( + "bytes" + "testing" + + cryptoEd25519 "crypto/ed25519" + + cosmosEd25519 "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + mock "github.com/cosmos/ibc-go/v3/testing/mock" + tmtypes "github.com/tendermint/tendermint/types" +) + +func GetPV(seed []byte) mock.PV { + //lint:ignore SA1019 We don't care because this is only a test. + return mock.PV{PrivKey: &cosmosEd25519.PrivKey{Key: cryptoEd25519.NewKeyFromSeed(seed)}} +} + +// getStakingKeyBytes takes seed bytes which can be be used to create +// a validator and returns the bytes that the staking module uses for +// lexicographic comparison using that validator +func getStakingKeyBytes(bz []byte) []byte { + pv := GetPV(bz) + pubKey, _ := pv.GetPubKey() + val := tmtypes.NewValidator(pubKey, 0) + addr, _ := sdk.ValAddressFromHex(val.Address.String()) + PK := pv.PrivKey.PubKey() + valAddr, _ := sdk.ValAddressFromBech32(addr.String()) + validator, _ := stakingtypes.NewValidator(valAddr, PK, stakingtypes.Description{}) + key := stakingtypes.GetValidatorsByPowerIndexKey(validator, sdk.DefaultPowerReduction) + return key +} + +// FuzzPrivateKeys will generate strings that can be used to seed +// new validator private keys, in a manner that ensures a strictly increasing +// order as per the lexicographic ordering of the staking module. +// This is needed to make sure that the lexicographic ordering is always +// consistent between the model and the SUT. +func FuzzPrivateKeys(f *testing.F) { + f.Fuzz(func(t *testing.T, bz []byte) { + k := cryptoEd25519.SeedSize + // Ensure 4 keys are generated + if len(bz) < 4*k { + t.Skip() + } + // Map each byte to 'a' or 'b' characters + for i, char := range bz { + bz[i] = byte(int(char)%2 + int('a')) + } + var keys [][]byte + // Get the staking module lexicographic bytes + for i := 0; i < 4; i++ { + keys = append(keys, getStakingKeyBytes(bz[i*k:i*k+k])) + } + good := true + // Check if the bytes are ordered strictly descending + for i := 0; i < 3; i++ { + // the execution is good if the keys are sorted in descending order + // Compare(a,b) === -1 IFF a > b + good = good && bytes.Compare(keys[i], keys[i+1]) == 1 + } + // If the bytes are ordered strictly descending + // we can use them as validator key seeds for diff testing. + if good { + strings := make([]string, 4) + for i := 0; i < 4; i++ { + strings[i] = string(bz[i*k : i*k+k]) + } + t.Errorf("[%s,\n%s,\n%s,\n%s]", strings[0], strings[1], strings[2], strings[3]) + } + }) + /* + Will output something like + [ + bbaaaababaabbaabababbaabbbbbbaaa, + abbbababbbabaaaaabaaabbbbababaab, + bbabaabaabbbbbabbbaababbbbabbbbb, + aabbbabaaaaababbbabaabaabbbbbbba + ] + which can be used to generate validator private keys. + */ +} diff --git a/diff-tests/core/driver/setup.go b/diff-tests/core/driver/setup.go new file mode 100644 index 0000000000..0e14d1adea --- /dev/null +++ b/diff-tests/core/driver/setup.go @@ -0,0 +1,764 @@ +package core + +import ( + "bytes" + "encoding/json" + "time" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/interchain-security/x/ccv/types" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/ibc-go/v3/testing/mock" + + ibctesting "github.com/cosmos/ibc-go/v3/testing" + + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + simapp "github.com/cosmos/interchain-security/testutil/simapp" + + cryptoEd25519 "crypto/ed25519" + + cosmosEd25519 "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" + + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + appConsumer "github.com/cosmos/interchain-security/app/consumer" + appProvider "github.com/cosmos/interchain-security/app/provider" + simibc "github.com/cosmos/interchain-security/testutil/simibc" + consumerkeeper "github.com/cosmos/interchain-security/x/ccv/consumer/keeper" + consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" + providerkeeper "github.com/cosmos/interchain-security/x/ccv/provider/keeper" + + channelkeeper "github.com/cosmos/ibc-go/v3/modules/core/04-channel/keeper" + ccv "github.com/cosmos/interchain-security/x/ccv/types" +) + +type Builder struct { + suite *suite.Suite + link simibc.OrderedLink + path *ibctesting.Path + coordinator *ibctesting.Coordinator + clientHeaders map[string][]*ibctmtypes.Header + mustBeginBlock map[string]bool + valAddresses []sdk.ValAddress + initState InitState +} + +func (b *Builder) ctx(chain string) sdk.Context { + return b.chain(chain).GetContext() +} + +func (b *Builder) chainID(chain string) string { + return map[string]string{P: ibctesting.GetChainID(0), C: ibctesting.GetChainID(1)}[chain] +} + +func (b *Builder) otherID(chainID string) string { + return map[string]string{ibctesting.GetChainID(0): ibctesting.GetChainID(1), ibctesting.GetChainID(1): ibctesting.GetChainID(0)}[chainID] +} + +func (b *Builder) chain(chain string) *ibctesting.TestChain { + return map[string]*ibctesting.TestChain{P: b.providerChain(), C: b.consumerChain()}[chain] +} + +func (b *Builder) providerChain() *ibctesting.TestChain { + return b.coordinator.GetChain(ibctesting.GetChainID(0)) +} + +func (b *Builder) consumerChain() *ibctesting.TestChain { + return b.coordinator.GetChain(ibctesting.GetChainID(1)) +} + +func (b *Builder) providerStakingKeeper() stakingkeeper.Keeper { + return b.providerChain().App.(*appProvider.App).StakingKeeper +} + +func (b *Builder) providerSlashingKeeper() slashingkeeper.Keeper { + return b.providerChain().App.(*appProvider.App).SlashingKeeper +} + +func (b *Builder) providerKeeper() providerkeeper.Keeper { + return b.providerChain().App.(*appProvider.App).ProviderKeeper +} + +func (b *Builder) consumerKeeper() consumerkeeper.Keeper { + return b.consumerChain().App.(*appConsumer.App).ConsumerKeeper +} + +func (b *Builder) endpointFromID(chainID string) *ibctesting.Endpoint { + return map[string]*ibctesting.Endpoint{ibctesting.GetChainID(0): b.path.EndpointB, ibctesting.GetChainID(1): b.path.EndpointA}[chainID] +} + +func (b *Builder) endpoint(chain string) *ibctesting.Endpoint { + return map[string]*ibctesting.Endpoint{P: b.path.EndpointB, C: b.path.EndpointA}[chain] +} + +func (b *Builder) validator(i int64) sdk.ValAddress { + return b.valAddresses[i] +} + +func (b *Builder) consAddr(i int64) sdk.ConsAddress { + return sdk.ConsAddress(b.validator(i)) +} + +// getValidatorPK returns the validator private key using the given seed index +func (b *Builder) getValidatorPK(seedIx int) mock.PV { + seed := []byte(b.initState.PKSeeds[seedIx]) + //lint:ignore SA1019 We don't care because this is only a test. + return mock.PV{PrivKey: &cosmosEd25519.PrivKey{Key: cryptoEd25519.NewKeyFromSeed(seed)}} +} + +func (b *Builder) getAppBytesAndSenders(chainID string, app ibctesting.TestingApp, genesis map[string]json.RawMessage, + validators *tmtypes.ValidatorSet) ([]byte, []ibctesting.SenderAccount) { + + accounts := []authtypes.GenesisAccount{} + balances := []banktypes.Balance{} + senderAccounts := []ibctesting.SenderAccount{} + + // Create genesis accounts. + for i := 0; i < 2; i++ { + pk := secp256k1.GenPrivKey() + acc := authtypes.NewBaseAccount(pk.PubKey().Address().Bytes(), pk.PubKey(), uint64(i), 0) + + // Give enough funds for many delegations + // Extra units are to delegate to extra validators created later + // in order to bond them and still have INITIAL_DELEGATOR_TOKENS remaining + extra := 0 + for j := 0; j < b.initState.NumValidators; j++ { + if b.initState.ValStates.Status[j] != stakingtypes.Bonded { + extra += b.initState.ValStates.Delegation[j] + } + } + amt := uint64(b.initState.InitialDelegatorTokens + extra) + + bal := banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewIntFromUint64(amt))), + } + + accounts = append(accounts, acc) + balances = append(balances, bal) + + senderAccount := ibctesting.SenderAccount{ + SenderAccount: acc, + SenderPrivKey: pk, + } + + senderAccounts = append(senderAccounts, senderAccount) + } + + // set genesis accounts + genesisAuth := authtypes.NewGenesisState(authtypes.DefaultParams(), accounts) + genesis[authtypes.ModuleName] = app.AppCodec().MustMarshalJSON(genesisAuth) + + stakingValidators := make([]stakingtypes.Validator, 0, len(validators.Validators)) + delegations := make([]stakingtypes.Delegation, 0, len(validators.Validators)) + + // Sum bonded is needed for BondedPool account + sumBonded := sdk.NewInt(0) + + for i, val := range validators.Validators { + status := b.initState.ValStates.Status[i] + delegation := b.initState.ValStates.Delegation[i] + extra := b.initState.ValStates.ValidatorExtraTokens[i] + + tokens := sdk.NewInt(int64(delegation + extra)) + b.suite.Require().Equal(status, stakingtypes.Bonded, "All genesis validators should be bonded") + sumBonded = sumBonded.Add(tokens) + // delegator account receives delShares shares + delShares := sdk.NewDec(int64(delegation)) + // validator has additional sumShares due to extra units + sumShares := sdk.NewDec(int64(delegation + extra)) + + pk, err := cryptocodec.FromTmPubKeyInterface(val.PubKey) + require.NoError(b.suite.T(), err) + pkAny, err := codectypes.NewAnyWithValue(pk) + require.NoError(b.suite.T(), err) + + validator := stakingtypes.Validator{ + OperatorAddress: sdk.ValAddress(val.Address).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: status, + Tokens: tokens, + DelegatorShares: sumShares, + Description: stakingtypes.Description{}, + UnbondingHeight: int64(0), + UnbondingTime: time.Unix(0, 0).UTC(), + Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + MinSelfDelegation: sdk.ZeroInt(), + } + + stakingValidators = append(stakingValidators, validator) + + // Store delegation from the model delegator account + delegations = append(delegations, stakingtypes.NewDelegation(accounts[0].GetAddress(), val.Address.Bytes(), delShares)) + // Remaining delegation is from extra account + delegations = append(delegations, stakingtypes.NewDelegation(accounts[1].GetAddress(), val.Address.Bytes(), sumShares.Sub(delShares))) + } + + bondDenom := sdk.DefaultBondDenom + genesisStaking := stakingtypes.GenesisState{} + + if genesis[stakingtypes.ModuleName] != nil { + // If staking module genesis already exists + app.AppCodec().MustUnmarshalJSON(genesis[stakingtypes.ModuleName], &genesisStaking) + bondDenom = genesisStaking.Params.BondDenom + } + + // Set model parameters + genesisStaking.Params.MaxEntries = uint32(b.initState.MaxEntries) + genesisStaking.Params.MaxValidators = uint32(b.initState.MaxValidators) + genesisStaking.Params.UnbondingTime = b.initState.UnbondingP + genesisStaking = *stakingtypes.NewGenesisState(genesisStaking.Params, stakingValidators, delegations) + genesis[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(&genesisStaking) + + // add bonded amount to bonded pool module account + balances = append(balances, banktypes.Balance{ + Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), + Coins: sdk.Coins{sdk.NewCoin(bondDenom, sumBonded)}, + }) + + // add unbonded amount + balances = append(balances, banktypes.Balance{ + Address: authtypes.NewModuleAddress(stakingtypes.NotBondedPoolName).String(), + Coins: sdk.Coins{sdk.NewCoin(bondDenom, sdk.ZeroInt())}, + }) + + // update total funds supply + genesisBank := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, sdk.NewCoins(), []banktypes.Metadata{}) + genesis[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(genesisBank) + + stateBytes, err := json.MarshalIndent(genesis, "", " ") + require.NoError(b.suite.T(), err) + + return stateBytes, senderAccounts + +} + +func (b *Builder) newChain(coord *ibctesting.Coordinator, appInit ibctesting.AppIniter, chainID string, + validators *tmtypes.ValidatorSet, signers map[string]tmtypes.PrivValidator) *ibctesting.TestChain { + + app, genesis := appInit() + + stateBytes, senderAccounts := b.getAppBytesAndSenders(chainID, app, genesis, validators) + + app.InitChain( + abci.RequestInitChain{ + ChainId: chainID, + Validators: []abci.ValidatorUpdate{}, + ConsensusParams: initState.ConsensusParams, + AppStateBytes: stateBytes, + }, + ) + + app.Commit() + + app.BeginBlock( + abci.RequestBeginBlock{ + Header: tmproto.Header{ + ChainID: chainID, + Height: app.LastBlockHeight() + 1, + AppHash: app.LastCommitID().Hash, + ValidatorsHash: validators.Hash(), + NextValidatorsHash: validators.Hash(), + }, + }, + ) + + chain := &ibctesting.TestChain{ + T: b.suite.T(), + Coordinator: coord, + ChainID: chainID, + App: app, + CurrentHeader: tmproto.Header{ + ChainID: chainID, + Height: 1, + Time: coord.CurrentTime.UTC(), + }, + QueryServer: app.GetIBCKeeper(), + TxConfig: app.GetTxConfig(), + Codec: app.AppCodec(), + Vals: validators, + NextVals: validators, + Signers: signers, + SenderPrivKey: senderAccounts[0].SenderPrivKey, + SenderAccount: senderAccounts[0].SenderAccount, + SenderAccounts: senderAccounts, + } + + coord.CommitBlock(chain) + + return chain +} + +func (b *Builder) createValidators() (*tmtypes.ValidatorSet, map[string]tmtypes.PrivValidator, []sdk.ValAddress) { + addresses := []sdk.ValAddress{} + signers := map[string]tmtypes.PrivValidator{} + validators := []*tmtypes.Validator{} + + for i, power := range b.initState.ValStates.Tokens { + if b.initState.ValStates.Status[i] != stakingtypes.Bonded { + continue + } + privVal := b.getValidatorPK(i) + + pubKey, err := privVal.GetPubKey() + require.NoError(b.suite.T(), err) + + // Compute address + addr, err := sdk.ValAddressFromHex(pubKey.Address().String()) + require.NoError(b.suite.T(), err) + addresses = append(addresses, addr) + + // Save signer + signers[pubKey.Address().String()] = privVal + + // Save validator with power + validators = append(validators, tmtypes.NewValidator(pubKey, int64(power))) + } + + return tmtypes.NewValidatorSet(validators), signers, addresses +} + +func (b *Builder) createChains() { + + coordinator := simapp.NewBasicCoordinator(b.suite.T()) + + // Create validators + validators, signers, addresses := b.createValidators() + // Create provider + coordinator.Chains[ibctesting.GetChainID(0)] = b.newChain(coordinator, simapp.SetupTestingappProvider, ibctesting.GetChainID(0), validators, signers) + // Create consumer, using the same validators. + coordinator.Chains[ibctesting.GetChainID(1)] = b.newChain(coordinator, simapp.SetupTestingAppConsumer, ibctesting.GetChainID(1), validators, signers) + + b.coordinator = coordinator + b.valAddresses = addresses + +} + +// createValidator creates an additional validator with zero commission +// and zero tokens (zero voting power). +func (b *Builder) createValidator(seedIx int) (tmtypes.PrivValidator, sdk.ValAddress) { + privVal := b.getValidatorPK(seedIx) + pubKey, err := privVal.GetPubKey() + b.suite.Require().NoError(err) + val := tmtypes.NewValidator(pubKey, 0) + addr, err := sdk.ValAddressFromHex(val.Address.String()) + b.suite.Require().NoError(err) + PK := privVal.PrivKey.PubKey() + coin := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(0)) + msg, err := stakingtypes.NewMsgCreateValidator(addr, PK, coin, stakingtypes.Description{}, + stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), sdk.ZeroInt()) + b.suite.Require().NoError(err) + pskServer := stakingkeeper.NewMsgServerImpl(b.providerStakingKeeper()) + _, _ = pskServer.CreateValidator(sdk.WrapSDKContext(b.ctx(P)), msg) + return privVal, addr +} + +// setSigningInfos sets the validator signing info in the provider Slashing module +func (b *Builder) setSigningInfos() { + for i := 0; i < 4; i++ { // TODO: unhardcode + info := slashingtypes.NewValidatorSigningInfo( + b.consAddr(int64(i)), + b.chain(P).CurrentHeader.GetHeight(), + 0, + time.Unix(0, 0), + false, + 0, + ) + b.providerSlashingKeeper().SetValidatorSigningInfo(b.ctx(P), b.consAddr(int64(i)), info) + } +} + +// Checks that the lexicographic ordering of validator addresses as computed in +// the staking module match the ordering of validators in the model. +func (b *Builder) ensureValidatorLexicographicOrderingMatchesModel() { + + check := func(lesser sdk.ValAddress, greater sdk.ValAddress) { + lesserV, _ := b.providerStakingKeeper().GetValidator(b.ctx(P), lesser) + greaterV, _ := b.providerStakingKeeper().GetValidator(b.ctx(P), greater) + lesserKey := stakingtypes.GetValidatorsByPowerIndexKey(lesserV, sdk.DefaultPowerReduction) + greaterKey := stakingtypes.GetValidatorsByPowerIndexKey(greaterV, sdk.DefaultPowerReduction) + // The result will be 0 if a==b, -1 if a < b, and +1 if a > b. + res := bytes.Compare(lesserKey, greaterKey) + // Confirm that validator precedence is the same in code as in model + b.suite.Require().Equal(-1, res) + } + + // In order to match the model to the system under test it is necessary + // to enforce a strict lexicographic ordering on the validators. + // We must do this because the staking module will break ties when + // deciding the active validator set by comparing addresses lexicographically. + // Thus, we assert here that the ordering in the model matches the ordering + // in the SUT. + for i := range b.valAddresses[:len(b.valAddresses)-1] { + // validators are chosen sorted descending in the staking module + greater := b.valAddresses[i] + lesser := b.valAddresses[i+1] + check(lesser, greater) + } +} + +// delegate is used to delegate tokens to newly created +// validators in the setup process. +func (b *Builder) delegate(del int, val sdk.ValAddress, amt int64) { + d := b.providerChain().SenderAccounts[del].SenderAccount.GetAddress() + coins := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(amt)) + msg := stakingtypes.NewMsgDelegate(d, val, coins) + pskServer := stakingkeeper.NewMsgServerImpl(b.providerStakingKeeper()) + _, err := pskServer.Delegate(sdk.WrapSDKContext(b.ctx(P)), msg) + b.suite.Require().NoError(err) +} + +func (b *Builder) addExtraValidators() { + + for i, status := range b.initState.ValStates.Status { + if status == stakingtypes.Unbonded { + val, addr := b.createValidator(i) + pubKey, err := val.GetPubKey() + b.suite.Require().Nil(err) + b.valAddresses = append(b.valAddresses, addr) + b.providerChain().Signers[pubKey.Address().String()] = val + b.consumerChain().Signers[pubKey.Address().String()] = val + } + } + + b.setSigningInfos() + + b.ensureValidatorLexicographicOrderingMatchesModel() + + for i := range b.initState.ValStates.Status { + if b.initState.ValStates.Status[i] == stakingtypes.Unbonded { + del := b.initState.ValStates.Delegation[i] + extra := b.initState.ValStates.ValidatorExtraTokens[i] + b.delegate(0, b.validator(int64(i)), int64(del)) + b.delegate(1, b.validator(int64(i)), int64(extra)) + } + } +} + +func (b *Builder) setSlashParams() { + // Set the slash factors on the provider to match the model + sparams := b.providerSlashingKeeper().GetParams(b.ctx(P)) + sparams.SlashFractionDoubleSign = b.initState.SlashDoublesign + sparams.SlashFractionDowntime = b.initState.SlashDowntime + b.providerSlashingKeeper().SetParams(b.ctx(P), sparams) +} + +func (b *Builder) createConsumerGenesis(tmConfig *ibctesting.TendermintConfig) *consumertypes.GenesisState { + // Create Provider client + providerClient := ibctmtypes.NewClientState( + b.providerChain().ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, + b.providerChain().LastHeader.GetHeight().(clienttypes.Height), commitmenttypes.GetSDKSpecs(), + []string{"upgrade", "upgradedIBCState"}, tmConfig.AllowUpdateAfterExpiry, tmConfig.AllowUpdateAfterMisbehaviour, + ) + providerConsState := b.providerChain().LastHeader.ConsensusState() + + // Create Consumer genesis + valUpdates := tmtypes.TM2PB.ValidatorUpdates(b.providerChain().Vals) + params := consumertypes.NewParams( + true, + 1000, // ignore distribution + "", // ignore distribution + "", // ignore distribution + ) + return consumertypes.NewInitialGenesisState(providerClient, providerConsState, valUpdates, params) +} + +func (b *Builder) createLink() { + b.link = simibc.MakeOrderedLink() + // init utility data structures + b.mustBeginBlock = map[string]bool{P: true, C: true} + b.clientHeaders = map[string][]*ibctmtypes.Header{} + for chainID := range b.coordinator.Chains { + b.clientHeaders[chainID] = []*ibctmtypes.Header{} + } +} + +func (b *Builder) doIBCHandshake() { + // Configure the ibc path + b.path = ibctesting.NewPath(b.consumerChain(), b.providerChain()) + b.path.EndpointA.ChannelConfig.PortID = ccv.ConsumerPortID + b.path.EndpointB.ChannelConfig.PortID = ccv.ProviderPortID + b.path.EndpointA.ChannelConfig.Version = ccv.Version + b.path.EndpointB.ChannelConfig.Version = ccv.Version + b.path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED + b.path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED + + providerClientID, ok := b.consumerKeeper().GetProviderClientID(b.ctx(C)) + if !ok { + panic("must already have provider client on consumer chain") + } + b.path.EndpointA.ClientID = providerClientID + err := b.path.EndpointB.Chain.SenderAccount.SetAccountNumber(6) + b.suite.Require().NoError(err) + err = b.path.EndpointA.Chain.SenderAccount.SetAccountNumber(1) + b.suite.Require().NoError(err) + + // Configure and create the consumer Client + tmConfig := b.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig) + // TODO: This is intentionally set to unbonding period for P (provider) + // TODO: Not sure why it breaks without this. + tmConfig.UnbondingPeriod = b.initState.UnbondingP + tmConfig.TrustingPeriod = b.initState.Trusting + tmConfig.MaxClockDrift = b.initState.MaxClockDrift + err = b.path.EndpointB.CreateClient() + b.suite.Require().NoError(err) + + // Create the Consumer chain ID mapping in the provider state + b.providerKeeper().SetConsumerClientId(b.ctx(P), b.consumerChain().ChainID, b.path.EndpointB.ClientID) + + // Handshake + b.coordinator.CreateConnections(b.path) + b.coordinator.CreateChannels(b.path) +} + +// Manually construct and send an empty VSC packet from the provider +// to the consumer. This is necessary to complete the handshake, and thus +// match the model init state, without any additional validator power changes. +func (b *Builder) sendEmptyVSCPacketToFinishHandshake() { + vscID := b.providerKeeper().GetValidatorSetUpdateId(b.providerChain().GetContext()) + + timeout := uint64(types.GetTimeoutTimestamp(b.chain(P).CurrentHeader.Time).UnixNano()) + + pd := types.NewValidatorSetChangePacketData( + []abci.ValidatorUpdate{}, + vscID, + nil, + ) + + seq, ok := b.providerChain().App.(*appProvider.App).GetIBCKeeper().ChannelKeeper.GetNextSequenceSend( + b.ctx(P), ccv.ProviderPortID, b.path.EndpointB.ChannelID) + + b.suite.Require().True(ok) + + packet := channeltypes.NewPacket(pd.GetBytes(), seq, ccv.ProviderPortID, b.endpoint(P).ChannelID, + ccv.ConsumerPortID, b.endpoint(C).ChannelID, clienttypes.Height{}, timeout) + + channelCap := b.endpoint(P).Chain.GetChannelCapability(packet.GetSourcePort(), packet.GetSourceChannel()) + + err := b.endpoint(P).Chain.App.GetIBCKeeper().ChannelKeeper.SendPacket(b.ctx(P), channelCap, packet) + + b.suite.Require().NoError(err) + + // Double commit the packet + b.endBlock(b.chainID(P)) + b.coordinator.CurrentTime = b.coordinator.CurrentTime.Add(time.Second * time.Duration(1)).UTC() + b.beginBlock(b.chainID(P)) + b.endBlock(b.chainID(P)) + b.coordinator.CurrentTime = b.coordinator.CurrentTime.Add(time.Second * time.Duration(1)).UTC() + b.mustBeginBlock[P] = true + + b.updateClient(b.chainID(C)) + + ack, err := simibc.TryRecvPacket(b.endpoint(P), b.endpoint(C), packet) + + b.link.AddAck(b.chainID(C), ack, packet) + + b.suite.Require().NoError(err) +} + +// idempotentBeginBlock begins a new block on chain +// if it is necessary to do so. +func (b *Builder) idempotentBeginBlock(chain string) { + if b.mustBeginBlock[chain] { + b.mustBeginBlock[chain] = false + b.beginBlock(b.chainID(chain)) + b.updateClient(b.chainID(chain)) + } +} + +func (b *Builder) beginBlock(chainID string) { + c := b.coordinator.GetChain(chainID) + c.CurrentHeader = tmproto.Header{ + ChainID: c.ChainID, + Height: c.App.LastBlockHeight() + 1, + AppHash: c.App.LastCommitID().Hash, + Time: b.coordinator.CurrentTime, + ValidatorsHash: c.Vals.Hash(), + NextValidatorsHash: c.NextVals.Hash(), + } + _ = c.App.BeginBlock(abci.RequestBeginBlock{Header: c.CurrentHeader}) +} + +func (b *Builder) updateClient(chainID string) { + for _, header := range b.clientHeaders[b.otherID(chainID)] { + err := simibc.UpdateReceiverClient(b.endpointFromID(b.otherID(chainID)), b.endpointFromID(chainID), header) + if err != nil { + b.coordinator.Fatal("updateClient") + } + } + b.clientHeaders[b.otherID(chainID)] = []*ibctmtypes.Header{} +} + +func (b *Builder) deliver(chainID string) { + packets := b.link.ConsumePackets(b.otherID(chainID), 1) + for _, p := range packets { + receiver := b.endpointFromID(chainID) + sender := receiver.Counterparty + ack, err := simibc.TryRecvPacket(sender, receiver, p.Packet) + if err != nil { + b.coordinator.Fatal("deliver") + } + b.link.AddAck(chainID, ack, p.Packet) + } +} + +func (b *Builder) deliverAcks(chainID string) { + for _, ack := range b.link.ConsumeAcks(b.otherID(chainID), 999999) { + err := simibc.TryRecvAck(b.endpointFromID(b.otherID(chainID)), b.endpointFromID(chainID), ack.Packet, ack.Ack) + if err != nil { + b.coordinator.Fatal("deliverAcks") + } + } +} + +func (b *Builder) endBlock(chainID string) { + c := b.coordinator.GetChain(chainID) + + ebRes := c.App.EndBlock(abci.RequestEndBlock{Height: c.CurrentHeader.Height}) + + c.App.Commit() + + c.Vals = c.NextVals + + c.NextVals = ibctesting.ApplyValSetChanges(c.T, c.Vals, ebRes.ValidatorUpdates) + + c.LastHeader = c.CurrentTMClientHeader() + // Store header to be used in UpdateClient + b.clientHeaders[chainID] = append(b.clientHeaders[chainID], c.LastHeader) + + for _, e := range ebRes.Events { + if e.Type == channeltypes.EventTypeSendPacket { + packet, _ := channelkeeper.ReconstructPacketFromEvent(e) + // Collect packets + b.link.AddPacket(chainID, packet) + } + } + + // Commit packets emmitted up to this point + b.link.Commit(chainID) + + newT := b.coordinator.CurrentTime.Add(b.initState.BlockSeconds).UTC() + + // increment the current header + c.CurrentHeader = tmproto.Header{ + ChainID: c.ChainID, + Height: c.App.LastBlockHeight() + 1, + AppHash: c.App.LastCommitID().Hash, + Time: newT, + ValidatorsHash: c.Vals.Hash(), + NextValidatorsHash: c.NextVals.Hash(), + } + + c.App.BeginBlock(abci.RequestBeginBlock{Header: c.CurrentHeader}) +} + +func (b *Builder) build() { + + b.createChains() + + b.addExtraValidators() + + // Commit the additional validators + b.coordinator.CommitBlock(b.providerChain()) + + b.setSlashParams() + + // Set light client params to match model + tmConfig := ibctesting.NewTendermintConfig() + tmConfig.UnbondingPeriod = b.initState.UnbondingP + tmConfig.TrustingPeriod = b.initState.Trusting + tmConfig.MaxClockDrift = b.initState.MaxClockDrift + + // Init consumer + b.consumerKeeper().InitGenesis(b.ctx(C), b.createConsumerGenesis(tmConfig)) + + // Create a simulated network link link + b.createLink() + + // Establish connection, channel + b.doIBCHandshake() + + // Set the unbonding time on the consumer to the model value + b.consumerKeeper().SetUnbondingTime(b.ctx(C), b.initState.UnbondingC) + + // Send an empty VSC packet from the provider to the consumer to finish + // the handshake. This is necessary because the model starts from a + // completely initialized state, with a completed handshake. + b.sendEmptyVSCPacketToFinishHandshake() + + // Catch up consumer to have the same height and timestamp as provider + b.endBlock(b.chainID(C)) + b.coordinator.CurrentTime = b.coordinator.CurrentTime.Add(time.Second * time.Duration(1)).UTC() + b.beginBlock(b.chainID(C)) + b.endBlock(b.chainID(C)) + b.coordinator.CurrentTime = b.coordinator.CurrentTime.Add(time.Second * time.Duration(1)).UTC() + b.beginBlock(b.chainID(C)) + b.endBlock(b.chainID(C)) + b.coordinator.CurrentTime = b.coordinator.CurrentTime.Add(time.Second * time.Duration(1)).UTC() + b.mustBeginBlock[C] = true + + // Progress chains in unison, allowing first VSC to mature. + for i := 0; i < 11; i++ { + b.idempotentBeginBlock(P) + b.endBlock(b.chainID(P)) + b.idempotentBeginBlock(C) + b.endBlock(b.chainID(C)) + b.mustBeginBlock = map[string]bool{P: true, C: true} + b.coordinator.CurrentTime = b.coordinator.CurrentTime.Add(b.initState.BlockSeconds).UTC() + } + + b.idempotentBeginBlock(P) + // Deliver outstanding ack + b.deliverAcks(b.chainID(P)) + // Deliver the maturity from the first VSC (needed to complete handshake) + b.deliver(b.chainID(P)) + + for i := 0; i < 2; i++ { + b.idempotentBeginBlock(P) + b.endBlock(b.chainID(P)) + b.idempotentBeginBlock(C) + b.deliverAcks(b.chainID(C)) + b.endBlock(b.chainID(C)) + b.mustBeginBlock = map[string]bool{P: true, C: true} + b.coordinator.CurrentTime = b.coordinator.CurrentTime.Add(b.initState.BlockSeconds).UTC() + } + + b.idempotentBeginBlock(P) + b.idempotentBeginBlock(C) + + b.endBlock(b.chainID(P)) + b.endBlock(b.chainID(C)) + b.coordinator.CurrentTime = b.coordinator.CurrentTime.Add(b.initState.BlockSeconds).UTC() + b.beginBlock(b.chainID(P)) + b.beginBlock(b.chainID(C)) + b.updateClient(b.chainID(P)) + b.updateClient(b.chainID(C)) +} + +func GetZeroState(suite *suite.Suite, initState InitState) ( + *ibctesting.Path, []sdk.ValAddress, int64, int64) { + b := Builder{initState: initState, suite: suite} + b.build() + // Height of the last committed block (current header is not committed) + heightLastCommitted := b.chain(P).CurrentHeader.Height - 1 + // Time of the last committed block (current header is not committed) + timeLastCommitted := b.chain(P).CurrentHeader.Time.Add(-b.initState.BlockSeconds).Unix() + return b.path, b.valAddresses, heightLastCommitted, timeLastCommitted +} diff --git a/diff-tests/core/driver/trace.go b/diff-tests/core/driver/trace.go new file mode 100644 index 0000000000..ba708952f9 --- /dev/null +++ b/diff-tests/core/driver/trace.go @@ -0,0 +1,167 @@ +package core + +import ( + "encoding/json" + "fmt" + "io" + "os" + + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +type Action struct { + Amt int `json:"amt,omitempty"` + Chain string `json:"chain,omitempty"` + InfractionHeight int `json:"infractionHeight,omitempty"` + IsDowntime bool `json:"isDowntime"` + Kind string `json:"kind"` + NumPackets int `json:"numPackets,omitempty"` + Val int `json:"val,omitempty"` +} + +type Consequence struct { + Delegation []int `json:"delegation,omitempty"` + DelegatorTokens int `json:"delegatorTokens,omitempty"` + Jailed []*int `json:"jailed,omitempty"` + OutstandingDowntime []bool `json:"outstandingDowntime,omitempty"` + ConsumerPower []*int `json:"consumerPower,omitempty"` + Status []string `json:"status,omitempty"` + Tokens []int `json:"tokens,omitempty"` + H int `json:"h,omitempty"` + T int `json:"t,omitempty"` +} + +type ActionAndConsequence struct { + Action Action `json:"action"` + Consequence Consequence `json:"consequence"` + Ix int `json:"ix"` +} + +type TraceData struct { + Actions []ActionAndConsequence `json:"actions"` + Constants struct { + BlockSeconds int `json:"BLOCK_SECONDS"` + C string `json:"C"` + DelegateAmtMax int `json:"DELEGATE_AMT_MAX"` + DelegateAmtMin int `json:"DELEGATE_AMT_MIN"` + InitialDelegatorTokens int `json:"INITIAL_DELEGATOR_TOKENS"` + IsdowntimeProbability float64 `json:"ISDOWNTIME_PROBABILITY"` + JailSeconds int `json:"JAIL_SECONDS"` + MaxNumPacketsForDeliver int `json:"MAX_NUM_PACKETS_FOR_DELIVER"` + MaxValidators int `json:"MAX_VALIDATORS"` + NumValidators int `json:"NUM_VALIDATORS"` + P string `json:"P"` + SlashDoublesign int `json:"SLASH_DOUBLESIGN"` + SlashDowntime int `json:"SLASH_DOWNTIME"` + TrustingSeconds int `json:"TRUSTING_SECONDS"` + UnbondingSecondsC int `json:"UNBONDING_SECONDS_C"` + UnbondingSecondsP int `json:"UNBONDING_SECONDS_P"` + UndelegateAmtMax int `json:"UNDELEGATE_AMT_MAX"` + UndelegateAmtMin int `json:"UNDELEGATE_AMT_MIN"` + } `json:"constants"` + Events []string `json:"events"` + Meta struct { + Commit string `json:"commit"` + Diff string `json:"diff"` + } `json:"meta"` +} + +func LoadTraces(fn string) []TraceData { + + /* #nosec */ + fd, err := os.Open(fn) + + if err != nil { + panic(err) + } + + /* #nosec */ + defer fd.Close() + + byteValue, _ := io.ReadAll(fd) + + var ret []TraceData + + err = json.Unmarshal([]byte(byteValue), &ret) + + if err != nil { + panic(err) + } + + return ret +} + +// Traces stores a list of traces +// and gives a diagnostic for debugging +// failed tests. +type Traces struct { + // index of trace in json + CurrentTraceIx int + // index of current action + CurrentActionIx int + // traces + Data []TraceData +} + +// diagnostic returns a string for diagnosing errors +func (t *Traces) Diagnostic() string { + return fmt.Sprintf("\n[diagnostic][trace %d, action %d, kind %s]", t.CurrentTraceIx, t.CurrentActionIx, t.Action().Kind) +} + +func (t *Traces) Trace() TraceData { + return t.Data[t.CurrentTraceIx] +} +func (t *Traces) Actions() []ActionAndConsequence { + return t.Trace().Actions +} + +func (t *Traces) Action() Action { + return t.Data[t.CurrentTraceIx].Actions[t.CurrentActionIx].Action +} + +func (t *Traces) Consequence() Consequence { + return t.Data[t.CurrentTraceIx].Actions[t.CurrentActionIx].Consequence +} + +func (t *Traces) Delegation(i int) int { + return t.Consequence().Delegation[i] +} + +func (t *Traces) DelegatorTokens() int { + return t.Consequence().DelegatorTokens +} + +func (t *Traces) Jailed(i int) *int { + return t.Consequence().Jailed[i] +} + +func (t *Traces) OutstandingDowntime(i int) bool { + return t.Consequence().OutstandingDowntime[i] +} + +func (t *Traces) ConsumerPower(i int) *int { + return t.Consequence().ConsumerPower[i] +} + +func (t *Traces) Status(i int) stakingtypes.BondStatus { + s := t.Consequence().Status[i] + if s == "unbonding" { + return stakingtypes.Unbonding + } + if s == "unbonded" { + return stakingtypes.Unbonded + } + return stakingtypes.Bonded +} + +func (t *Traces) Tokens(i int) int { + return t.Consequence().Tokens[i] +} + +func (t *Traces) Time() int { + return t.Consequence().T +} + +func (t *Traces) Height() int { + return t.Consequence().H +} diff --git a/diff-tests/core/driver/traces.json b/diff-tests/core/driver/traces.json new file mode 100644 index 0000000000..fbf3a7a846 --- /dev/null +++ b/diff-tests/core/driver/traces.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:729e48bb84ce09bd104eba9bb8d929a19ad1e14d5072b5aa5b71474d3e85ea81 +size 13345982 diff --git a/diff-tests/core/model/.eslintignore b/diff-tests/core/model/.eslintignore new file mode 100644 index 0000000000..fc40c5a94d --- /dev/null +++ b/diff-tests/core/model/.eslintignore @@ -0,0 +1 @@ +/**/*.js diff --git a/diff-tests/core/model/.eslintrc.json b/diff-tests/core/model/.eslintrc.json new file mode 100644 index 0000000000..239cad64e9 --- /dev/null +++ b/diff-tests/core/model/.eslintrc.json @@ -0,0 +1,27 @@ +{ + "env": { + "browser": false, + "es6": true, + "node": true + }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "tsconfig.json", + "sourceType": "module", + "ecmaVersion": 2020 + }, + "plugins": [ + "@typescript-eslint", + "jest" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:jest/recommended", + "prettier" + ], + "rules": { + "@typescript-eslint/no-var-requires": 0 + } +} \ No newline at end of file diff --git a/diff-tests/core/model/.gitignore b/diff-tests/core/model/.gitignore new file mode 100644 index 0000000000..48c9c9e654 --- /dev/null +++ b/diff-tests/core/model/.gitignore @@ -0,0 +1,31 @@ +# Logs +logs +*.log +npm-debug.log* + +# Dependencies +node_modules/ + +# Coverage +coverage + +# Transpiled files +build/ + +# VS Code +.vscode +!.vscode/tasks.js + +# JetBrains IDEs +.idea/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Misc +.DS_Store + +traces/ \ No newline at end of file diff --git a/diff-tests/core/model/.prettierrc b/diff-tests/core/model/.prettierrc new file mode 100644 index 0000000000..f17b0543ee --- /dev/null +++ b/diff-tests/core/model/.prettierrc @@ -0,0 +1,17 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "overrides": [ + { + "files": [ + "*.ts", + "*.mts" + ], + "options": { + "parser": "typescript" + } + } + ], + "tabWidth": 2, + "printWidth": 74 +} \ No newline at end of file diff --git a/diff-tests/core/model/__tests__/gen.test.ts b/diff-tests/core/model/__tests__/gen.test.ts new file mode 100644 index 0000000000..5bc75b7fdf --- /dev/null +++ b/diff-tests/core/model/__tests__/gen.test.ts @@ -0,0 +1,14 @@ +import { gen } from '../src/main.js'; + +/** + * This test is useful to check how much coverage + * trace generation actually gets over the model. + * + * yarn jest --collect-coverage + */ +describe('check properties', () => { + it('_', () => { + gen(120, true); + expect(true).toBeTruthy(); // satisfies linter + }); +}); diff --git a/diff-tests/core/model/__tests__/tsconfig.json b/diff-tests/core/model/__tests__/tsconfig.json new file mode 100644 index 0000000000..734ac11619 --- /dev/null +++ b/diff-tests/core/model/__tests__/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "node16", + "lib": [ + "ES2022" + ], + "moduleResolution": "Node16", + "rootDir": "..", + "outDir": "build", + "allowSyntheticDefaultImports": true, + "importHelpers": true, + "alwaysStrict": true, + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "allowJs": true, + "esModuleInterop": true + }, + "include": [ + "src/**/*", + "__tests__/**/*" + ] +} \ No newline at end of file diff --git a/diff-tests/core/model/jest.config.js b/diff-tests/core/model/jest.config.js new file mode 100644 index 0000000000..2d2c1ef1d3 --- /dev/null +++ b/diff-tests/core/model/jest.config.js @@ -0,0 +1,24 @@ +export default { + testEnvironment: 'node', + preset: 'ts-jest/presets/js-with-ts-esm', + globals: { + 'ts-jest': { + useESM: true, + tsconfig: '/__tests__/tsconfig.json', + }, + }, + transformIgnorePatterns: [ + "node_modules/(?!(time-span|convert-hrtime))", + ], + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.(m)?js$': '$1', + }, + testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(m)?ts$', + coverageDirectory: 'coverage', + collectCoverageFrom: [ + 'src/**/*.ts', + 'src/**/*.mts', + '!src/**/*.d.ts', + '!src/**/*.d.mts', + ], +}; diff --git a/diff-tests/core/model/package.json b/diff-tests/core/model/package.json new file mode 100644 index 0000000000..cb129e4ce2 --- /dev/null +++ b/diff-tests/core/model/package.json @@ -0,0 +1,50 @@ +{ + "name": "diff-tests-core", + "version": "3.0.1", + "description": "A model for generating difference tests for Interchain Security core protocol components.", + "type": "module", + "engines": { + "node": ">= 16.13 <17" + }, + "devDependencies": { + "@types/jest": "^28.1.4", + "@types/node": "~16", + "@typescript-eslint/eslint-plugin": "~5.26", + "@typescript-eslint/parser": "~5.26", + "eslint": "~8.16", + "eslint-config-prettier": "~8.5", + "eslint-plugin-jest": "~26.2", + "jest": "^28.1.1", + "prettier": "~2.6", + "rimraf": "~3.0", + "source-map-support": "^0.5.21", + "ts-jest": "^28.0.5", + "tsutils": "~3.21", + "typescript": "~4.7" + }, + "scripts": { + "start": "node build/src/main.js", + "clean": "rimraf coverage build tmp", + "prebuild": "npm run lint", + "build": "tsc -p tsconfig.json", + "build:watch": "tsc -w -p tsconfig.json", + "build:release": "npm run clean && tsc -p tsconfig.release.json", + "lint": "eslint . --ext .ts --ext .mts", + "test": "jest --coverage", + "prettier": "prettier --config .prettierrc --write .", + "test:watch": "jest --watch" + }, + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@types/clone-deep": "^4.0.1", + "@types/underscore": "^1.11.4", + "clone-deep": "^4.0.1", + "time-span": "^5.1.0", + "tslib": "~2.4", + "underscore": "^1.13.4" + }, + "volta": { + "node": "16.13.0" + } +} \ No newline at end of file diff --git a/diff-tests/core/model/src/common.ts b/diff-tests/core/model/src/common.ts new file mode 100644 index 0000000000..1c414f179f --- /dev/null +++ b/diff-tests/core/model/src/common.ts @@ -0,0 +1,210 @@ +type Chain = 'provider' | 'consumer'; + +type Validator = number; + +enum Status { + BONDED = 'bonded', + UNBONDING = 'unbonding', + UNBONDED = 'unbonded', +} + +/** + * Represents undelegation logic in the staking module. + */ +interface Undelegation { + val: Validator; + creationHeight: number; + completionTime: number; + balance: number; + initialBalance: number; + onHold: boolean; + opID: number; + willBeProcessedByStakingModule: boolean; +} + +/** + * Represents unbonding validator logic in the staking module. + */ +interface Unval { + val: Validator; + unbondingHeight: number; + unbondingTime: number; + onHold: boolean; + opID: number; +} + +/** + * Validator Set Change data structure + */ +interface Vsc { + vscID: number; + updates: Record; + downtimeSlashAcks: number[]; +} + +/** + * Validator Set Change Maturity notification data structure + */ +interface VscMatured { + vscID: number; +} + +/** + * Consumer Initiated Slash data structure + */ +interface Slash { + val: Validator; + vscID: number; + isDowntime: boolean; +} + +type PacketData = Vsc | VscMatured | Slash; + +interface Packet { + data: PacketData; + sendHeight: number; +} + +interface Action { + kind: string; +} + +type Delegate = { + kind: string; + val: Validator; + amt: number; +}; + +type Undelegate = { + kind: string; + val: Validator; + amt: number; +}; + +type ConsumerSlash = { + kind: string; + val: Validator; + infractionHeight: number; + isDowntime: boolean; +}; + +type UpdateClient = { + kind: string; + chain: Chain; +}; + +type Deliver = { + kind: string; + chain: Chain; + numPackets: number; +}; + +type EndAndBeginBlock = { + kind: string; + chain: Chain; +}; + +type InvariantSnapshot = { + h: Record; + t: Record; + tokens: number[]; + status: Status[]; + undelegationQ: Undelegation[]; + delegatorTokens: number; + consumerPower: (number | undefined)[]; +}; + +/** + * Record a snapshot of both chain states at the height and timestamp + * of a committed block for a chain. + */ +interface CommittedBlock { + chain: Chain; + invariantSnapshot: InvariantSnapshot; +} + +/** + * A snapshot of model state which is the consequence + * of doing an Action against the model. Used + * for state comparisons with the SUT, and to make + * traces human readable. + */ +interface Consequence { + h?: number; // Chain local height + t?: number; // Chain local timestamp + tokens?: number[]; + delegation?: number[]; + delegatorTokens?: number; + jailed?: (number | undefined)[]; + status?: Status[]; + consumerPower?: (number | undefined)[]; + outstandingDowntime?: boolean[]; +} + +interface TraceAction { + ix: number; + action: Action; + consequence: Consequence; +} + +/** + * See model.ts for field docstrings + */ +type ModelInitState = { + h: Record; + t: Record; + staking: { + delegation: number[]; + tokens: number[]; + status: Status[]; + undelegationQ: Undelegation[]; + validatorQ: Unval[]; + jailed: (number | undefined)[]; + delegatorTokens: number; + opID: number; + changes: Record; + lastVals: Validator[]; + lastTokens: number[]; + }; + ccvC: { + hToVscID: Record; + pendingChanges: Record[]; + maturingVscs: Map; + outstandingDowntime: boolean[]; + consumerPower: (number | undefined)[]; + }; + ccvP: { + initialHeight: number; + vscID: number; + vscIDtoH: Record; + vscIDtoOpIDs: Map; + downtimeSlashAcks: number[]; + tombstoned: boolean[]; + matureUnbondingOps: number[]; + }; +}; + +export { + ModelInitState, + Consequence, + TraceAction, + CommittedBlock, + Chain, + Validator, + Action, + Delegate, + Undelegate, + ConsumerSlash, + UpdateClient, + Deliver, + EndAndBeginBlock, + InvariantSnapshot, + Status, + Undelegation, + Unval, + Vsc, + VscMatured, + Slash, + PacketData, + Packet, +}; diff --git a/diff-tests/core/model/src/constants.ts b/diff-tests/core/model/src/constants.ts new file mode 100644 index 0000000000..e6e5cad9f5 --- /dev/null +++ b/diff-tests/core/model/src/constants.ts @@ -0,0 +1,132 @@ +import { ModelInitState, Status } from './common.js'; + +const P = 'provider'; +const C = 'consumer'; + +// Block interval, set to mimic real life. +const BLOCK_SECONDS = 6; +// Trusting period +const TRUSTING_SECONDS = 49; +// Unbonding period for consumer. Must be greater than TRUSTING_SECONDS +const UNBONDING_SECONDS_C = 50; +// Unbonding period for provider. Must be greater than TRUSTING_SECONDS. +// Should be greater than UNBONDING_SECONDS_C +const UNBONDING_SECONDS_P = 70; + +// Total number of validators to model. +const NUM_VALIDATORS = 4; +// Maximum number of active validator at a given time. This should be less +// than NUM_VALIDATORS in order to test scenarios where validators +// join and leave the active set, or are jailed. +const MAX_VALIDATORS = 2; // allows jailing 2 validators +// Slash factor for double signing. This is set to 0 in order to test slashing +// logic while avoiding divergence between model and SUT due to differences +// in numerical implementations. +const SLASH_DOUBLESIGN = 0; +// ^^ +const SLASH_DOWNTIME = 0; +// Jail time. Unjailing is not modelled. +const JAIL_SECONDS = 999999999999999; +// Initial token balance for the (sole) delegator account. Should +// be large enough to allow unlimited delegate actions. +const INITIAL_DELEGATOR_TOKENS = 10000000000000; + +const DELEGATE_AMT_MIN = 1000; +const DELEGATE_AMT_MAX = 5000; +const UNDELEGATE_AMT_MIN = 1000; +const UNDELEGATE_AMT_MAX = 5000; +const ISDOWNTIME_PROBABILITY = 0.5; +const MAX_NUM_PACKETS_FOR_DELIVER = 6; + +const MODEL_INIT_STATE: ModelInitState = { + h: { provider: 0, consumer: 0 }, + t: { provider: 0, consumer: 0 }, + ccvC: { + hToVscID: { 0: 0, 1: 0 }, + pendingChanges: [], + maturingVscs: new Map(), + outstandingDowntime: [false, false, false, false], + consumerPower: [5000, 4000, undefined, undefined], + }, + ccvP: { + initialHeight: 0, + vscID: 0, + vscIDtoH: {}, + vscIDtoOpIDs: new Map(), + downtimeSlashAcks: [], + tombstoned: [false, false, false, false], + matureUnbondingOps: [], + }, + staking: { + delegation: [4000, 3000, 2000, 1000], + tokens: [5000, 4000, 3000, 2000], + status: [ + Status.BONDED, // Bonded as per the delegation above + Status.BONDED, // ^^ + Status.UNBONDED, // Unbonded as per MAX_VALIDATORS + Status.UNBONDED, // ^^ + ], + undelegationQ: [], + validatorQ: [], + jailed: [undefined, undefined, undefined, undefined], + delegatorTokens: INITIAL_DELEGATOR_TOKENS, + opID: 0, + changes: {}, + lastVals: [0, 1], + lastTokens: [5000, 4000, 3000, 2000], + }, +}; + +/* + * Used to track various semantic events that occur during model exploration. + */ +enum Event { + REBOND_UNVAL = 'rebond_unval', + COMPLETE_UNVAL_IN_ENDBLOCK = 'complete_unval_in_endblock', + SET_UNVAL_HOLD_FALSE = 'set_unval_hold_false', + COMPLETE_UNDEL_IN_ENDBLOCK = 'complete_undel_in_endblock', + COMPLETE_UNDEL_IMMEDIATE = 'complete_undel_immediate', + SET_UNDEL_HOLD_FALSE = 'set_undel_hold_false', + INSUFFICIENT_SHARES = 'insufficient_shares', + SLASH_UNDEL = 'slash_undel', + JAIL = 'jail', + SEND_DOWNTIME_SLASH_REQUEST = 'send_downtime_slash_request', + RECEIVE_DOWNTIME_SLASH_REQUEST = 'receive_downtime_slash_request', + RECEIVE_DOWNTIME_SLASH_ACK = 'receive_downtime_slash_ack', + SEND_DOUBLE_SIGN_SLASH_REQUEST = 'send_double_sign_slash_request', + CONSUMER_SEND_MATURATION = 'consumer_send_maturation', + SEND_VSC_NOT_BECAUSE_CHANGE = 'send_vsc_not_because_change', + SEND_VSC_WITH_DOWNTIME_ACK = 'send_vsc_with_downtime_ack', + SEND_VSC_WITHOUT_DOWNTIME_ACK = 'send_vsc_without_downtime_ack', + SOME_UNDELS_EXPIRED_BUT_NOT_COMPLETED = 'some_undels_expired_but_not_completed', + RECEIVE_DOUBLE_SIGN_SLASH_REQUEST = 'receive_double_sign_slash_request', + RECEIVE_SLASH_REQUEST_UNBONDED = 'receive_slash_request_unbonded', + DOWNTIME_SLASH_REQUEST_OUTSTANDING = 'downtime_slash_request_outstanding', + CONSUMER_UPDATE_VAL = 'consumer_update_val', + CONSUMER_DEL_VAL = 'consumer_del_val', + CONSUMER_ADD_VAL = 'consumer_add_val', + MORE_THAN_ONE_THIRD_VAL_POWER_CHANGE = 'more_than_one_third_val_power_change', +} + +export { + P, + C, + UNBONDING_SECONDS_P, + UNBONDING_SECONDS_C, + TRUSTING_SECONDS, + NUM_VALIDATORS, + MAX_VALIDATORS, + SLASH_DOUBLESIGN, + SLASH_DOWNTIME, + JAIL_SECONDS, + BLOCK_SECONDS, + INITIAL_DELEGATOR_TOKENS, + DELEGATE_AMT_MIN, + DELEGATE_AMT_MAX, + UNDELEGATE_AMT_MIN, + UNDELEGATE_AMT_MAX, + ISDOWNTIME_PROBABILITY, + MAX_NUM_PACKETS_FOR_DELIVER, + Event, + MODEL_INIT_STATE, +}; diff --git a/diff-tests/core/model/src/main.ts b/diff-tests/core/model/src/main.ts new file mode 100644 index 0000000000..f982bd30f7 --- /dev/null +++ b/diff-tests/core/model/src/main.ts @@ -0,0 +1,444 @@ +import * as fs from 'fs'; +import _ from 'underscore'; +import timeSpan from 'time-span'; +import cloneDeep from 'clone-deep'; +import { + BlockHistory, + stakingWithoutSlashing, + bondBasedConsumerVotingPower, +} from './properties.js'; +import { Model } from './model.js'; +import { + createSmallSubsetOfCoveringTraces, + dumpTrace, + forceMakeEmptyDir, + logEventData, +} from './traceUtil.js'; + +import { + Action, + Undelegate, + Delegate, + ConsumerSlash, + UpdateClient, + Deliver, + EndAndBeginBlock, + TraceAction, + Chain, + Consequence, +} from './common.js'; + +import { + P, + C, + NUM_VALIDATORS, + DELEGATE_AMT_MIN, + DELEGATE_AMT_MAX, + UNDELEGATE_AMT_MIN, + UNDELEGATE_AMT_MAX, + ISDOWNTIME_PROBABILITY, + TRUSTING_SECONDS, + BLOCK_SECONDS, + MAX_NUM_PACKETS_FOR_DELIVER, + Event, + MODEL_INIT_STATE, +} from './constants.js'; + +class ActionGenerator { + model; + // was the validator slashed? + didSlash = new Array(NUM_VALIDATORS).fill(false); + // the timestamp contained in the latest trusted header + tLastTrustedHeader = { provider: 0, consumer: 0 }; + // the timestamp of the last committed block + tLastCommit = { provider: 0, consumer: 0 }; + + constructor(model: Model) { + this.model = model; + } + + create = (): Action => { + const kind = _.sample([ + 'Delegate', + 'Undelegate', + 'ConsumerSlash', + 'EndAndBeginBlock', + 'Deliver', + 'UpdateClient', + ]); + if (kind === 'Delegate') { + return { + kind, + val: _.random(0, NUM_VALIDATORS - 1), + amt: _.random(DELEGATE_AMT_MIN, DELEGATE_AMT_MAX), + } as Delegate; + } + if (kind === 'Undelegate') { + return { + kind, + val: _.random(0, NUM_VALIDATORS - 1), + amt: _.random(UNDELEGATE_AMT_MIN, UNDELEGATE_AMT_MAX), + } as Undelegate; + } + if (kind === 'ConsumerSlash') { + return { + kind, + val: _.random(0, NUM_VALIDATORS - 1), + infractionHeight: Math.floor(Math.random() * this.model.h[C]), + isDowntime: Math.random() < ISDOWNTIME_PROBABILITY, + } as ConsumerSlash; + } + if (kind === 'UpdateClient') { + return { kind, chain: _.sample([P, C]) as Chain } as UpdateClient; + } + if (kind === 'Deliver') { + return { + kind, + chain: _.sample([P, C]) as Chain, + numPackets: _.random(1, MAX_NUM_PACKETS_FOR_DELIVER), + } as Deliver; + } + if (kind === 'EndAndBeginBlock') { + return { + kind, + chain: _.sample([P, C]) as Chain, + } as EndAndBeginBlock; + } + throw `kind doesn't match`; + }; + + valid = (a: Action): boolean => { + if (a.kind === 'Delegate') { + return true; + } + if (a.kind === 'Undelegate') { + return true; + } + if (a.kind === 'ConsumerSlash') { + return 2 <= this.didSlash.filter((x) => !x).length; + } + if (a.kind === 'UpdateClient') { + return true; + } + if (a.kind === 'Deliver') { + return true; + } + if (a.kind === 'EndAndBeginBlock') { + const chain = (a as EndAndBeginBlock).chain; + return ( + this.model.t[chain] + BLOCK_SECONDS < + this.tLastTrustedHeader[chain] + TRUSTING_SECONDS + ); + } + throw `kind doesn't match`; + }; + + /** + * Update internal state to inform rule based action generation + * and prevent generating traces which over approximate the system. + * e.g. traces that expire the light clients or jail all validators. + * @param a action + */ + do = (a: Action) => { + // Update internal state to prevent jailing all validators + if (a.kind === 'ConsumerSlash') { + this.didSlash[(a as ConsumerSlash).val] = true; + } + // Update internal state to prevent expiring light clients + // Client is also updated for Deliver, because this is needed in practice + // for SUT. + if (a.kind === 'UpdateClient' || a.kind === 'Deliver') { + const chain = (a as UpdateClient).chain; + if ( + this.tLastTrustedHeader[chain] + TRUSTING_SECONDS <= + this.model.t[chain] + ) { + // Sanity check to make sure client cannot expire + throw 'Client expired (updateClient), model is not written correctly.'; + } + this.tLastTrustedHeader[chain] = + this.tLastCommit[chain == P ? C : P]; + } + // Update internal state to prevent expiring light clients + if (a.kind === 'EndAndBeginBlock') { + const chain = (a as EndAndBeginBlock).chain; + this.tLastCommit[chain] = this.model.t[chain]; + } + }; + + /** + * @returns A valid model action. + */ + get = () => { + /* eslint no-constant-condition: 1*/ + while (true) { + // Ok because some action is always valid + const a = this.create(); + if (this.valid(a)) { + this.do(a); + return a; + } + } + }; +} + +/** + * Executes an action against the model, thereby updating the model state. + * @param model The model instance + * @param action The action to be executed against the model + */ +function doAction(model: Model, action: Action): Consequence { + const kind = action.kind; + if (kind === 'Delegate') { + const a = action as Delegate; + model.delegate(a.val, a.amt); + return { + h: model.h[P], + t: model.t[P], + tokens: model.staking.tokens, + delegation: model.staking.delegation, + delegatorTokens: model.staking.delegatorTokens, + }; + } + if (kind === 'Undelegate') { + const a = action as Undelegate; + model.undelegate(a.val, a.amt); + return { + h: model.h[P], + t: model.t[P], + tokens: model.staking.tokens, + delegation: model.staking.delegation, + delegatorTokens: model.staking.delegatorTokens, + }; + } + if (kind === 'ConsumerSlash') { + const a = action as ConsumerSlash; + model.consumerSlash(a.val, a.infractionHeight, a.isDowntime); + return { + h: model.h[C], + t: model.t[C], + outstandingDowntime: model.ccvC.outstandingDowntime, + }; + } + if (kind === 'UpdateClient') { + const a = action as UpdateClient; + model.updateClient(a.chain); + return {}; + } + if (kind === 'Deliver') { + const a = action as Deliver; + model.deliver(a.chain, a.numPackets); + if (a.chain === P) { + return { + h: model.h[P], + t: model.t[P], + tokens: model.staking.tokens, + delegation: model.staking.delegation, + delegatorTokens: model.staking.delegatorTokens, + jailed: model.staking.jailed, + status: model.staking.status, + }; + } + if (a.chain === C) { + return { + h: model.h[C], + t: model.t[C], + consumerPower: model.ccvC.consumerPower, + outstandingDowntime: model.ccvC.outstandingDowntime, + }; + } + } + if (kind === 'EndAndBeginBlock') { + const a = action as EndAndBeginBlock; + model.endAndBeginBlock(a.chain); + if (a.chain === P) { + return { + h: model.h[P], + t: model.t[P], + tokens: model.staking.tokens, + delegation: model.staking.delegation, + delegatorTokens: model.staking.delegatorTokens, + jailed: model.staking.jailed, + status: model.staking.status, + }; + } + if (a.chain === C) { + return { + h: model.h[C], + t: model.t[C], + consumerPower: model.ccvC.consumerPower, + outstandingDowntime: model.ccvC.outstandingDowntime, + }; + } + } + throw 'Action kind not recognized'; +} + +/** + * Generates traces by repeatedly creating new model instances + * and executing randomly generated actions against them. + * The trace consists of data including the actions taken, and the + * successive model states that result from the actions. Additional + * data is included + * @param seconds Duration to generate traces. + * @param checkProperties If true, will check properties and only write trace + * if property is violated. + */ +function gen(seconds: number, checkProperties: boolean) { + // Compute millis run time + const runTimeMillis = seconds * 1000; + let elapsedMillis = 0; + // Number of actions to execute against each model instance + // Free parameter! + const NUM_ACTIONS = 200; + // Directory to output traces in json format + const DIR = 'traces/'; + forceMakeEmptyDir(DIR); + let i = 0; + // Track the model events that occur during the generation process + // this data is used to check that all events are emitted by some + // trace. + const allEvents = []; + while (elapsedMillis < runTimeMillis) { + i += 1; + const end = timeSpan(); + //////////////////////// + const hist = new BlockHistory(); + // Store all events emitted during trace execution + const events: Event[] = []; + const model = new Model(hist, events, cloneDeep(MODEL_INIT_STATE)); + const actionGenerator = new ActionGenerator(model); + const actions: TraceAction[] = []; + for (let j = 0; j < NUM_ACTIONS; j++) { + const a = actionGenerator.get(); + const consequence = doAction(model, a); + actions.push({ + ix: j, + // Store the action taken + action: a, + // Store the consequence of the action for model comparison + consequence: cloneDeep(consequence), + }); + if (checkProperties) { + // Checking properties is flagged because it is computationally + // expensive. + if (!bondBasedConsumerVotingPower(hist)) { + dumpTrace(`${DIR}trace_${i}.json`, actions, events); + throw 'bondBasedConsumerVotingPower property failure, trace written.'; + } + if (!stakingWithoutSlashing(hist)) { + dumpTrace(`${DIR}trace_${i}.json`, actions, events); + throw 'stakingWithoutSlashing property failure, trace written.'; + } + } + } + if (!checkProperties) { + // Write the trace to file, along with metadata. + dumpTrace(`${DIR}trace_${i}.json`, actions, events); + } + // Accumulate all events + allEvents.push(...events); + //////////////////////// + elapsedMillis += end.rounded(); + // Log progress stats + if (i % 10000 === 0) { + console.log( + `done ${i}, actions per second ${ + (i * NUM_ACTIONS) / (elapsedMillis / 1000) + }`, + ); + } + } + logEventData(allEvents); +} + +/** + * Replays a list of actions against a new model instance. + * This function is best used to debug the model or to debug + * a failing test against the SUT. In this manner it is possible + * to step through the model execution and the SUT execution of trace + * side-by-side. + * The model is deterministic, thus a fixed list of actions always + * results in the same behavior and model states. + * @param actions + */ +function replay(actions: TraceAction[]) { + const hist = new BlockHistory(); + const events: Event[] = []; + const model = new Model(hist, events, MODEL_INIT_STATE); + const actionGenerator = new ActionGenerator(model); + for (let i = 0; i < actions.length; i++) { + const a = actions[i]; + console.log(a); + actionGenerator.do(a.action); + doAction(model, a.action); + bondBasedConsumerVotingPower(hist); + } +} + +/** + * @param fn filename of the file containing the json traces + * @param ix the index of the trace in the json + * @param numActions The number of actions to replay from the trace. If numActions + * is less than the length of the trace, then execution will complete before + * the entire trace has been executed. This helps with debugging because it makes + * logs shorter. + */ +function replayFile(fn: string, ix: number, numActions: number) { + const traces = JSON.parse(fs.readFileSync(fn, 'utf8')); + const trace = ix !== undefined ? traces[ix] : traces[0]; + const traceActions = trace.actions as TraceAction[]; + const actions = traceActions.slice(0, numActions); + replay(actions); +} + +console.log(`running main`); + +/* + * Generate new traces and write them to files, for seconds. + * + * yarn start gen + */ +if (process.argv[2] === 'gen') { + console.log(`gen`); + const seconds = parseInt(process.argv[3]); + gen(seconds, false); +} else if (process.argv[2] === 'properties') { + /* + * Check properties of the model for seconds. + * + * yarn start properties + */ + console.log(`properties`); + const seconds = parseInt(process.argv[3]); + gen(seconds, true); +} else if (process.argv[2] === 'subset') { + /* + * Creates a trace file containing several traces, in a way that ensures + * each interesting model event is emitted by some trace. + * + * yarn start subset + */ + console.log(`createSmallSubsetOfCoveringTraces`); + const outFile = process.argv[3]; + let eventInstances = 20; + if (3 < process.argv.length) { + eventInstances = parseInt(process.argv[4]); + } + createSmallSubsetOfCoveringTraces(outFile, eventInstances); +} else if (process.argv[2] === 'replay') { + /* + * Replay a trace from a a file, up to a given number of actions. + * + * yarn start replay + */ + console.log(`replay`); + const [fn, traceNum, numActions] = process.argv.slice(3, 6); + replayFile(fn, parseInt(traceNum), parseInt(numActions)); +} else { + console.log(`did not execute any function`); +} + +console.log(`finished running main`); + +export { gen }; diff --git a/diff-tests/core/model/src/model.ts b/diff-tests/core/model/src/model.ts new file mode 100644 index 0000000000..4ba2e9f464 --- /dev/null +++ b/diff-tests/core/model/src/model.ts @@ -0,0 +1,706 @@ +import _ from 'underscore'; +import { BlockHistory } from './properties.js'; +import cloneDeep from 'clone-deep'; +import { strict as assert } from 'node:assert'; + +/** + * This model may need updating pending + * https://github.com/cosmos/ibc/issues/825 (model updated, spec has open PR) + * https://github.com/cosmos/ibc/issues/796 (model updated, spec awaiting PR) + * TODO: implement automatic commit hash comparison warning against spec + */ + +import { + P, + C, + UNBONDING_SECONDS_P, + UNBONDING_SECONDS_C, + NUM_VALIDATORS, + MAX_VALIDATORS, + JAIL_SECONDS, + BLOCK_SECONDS, + Event, +} from './constants.js'; + +import { + Undelegation, + Unval, + Vsc, + VscMatured, + Packet, + Chain, + Validator, + PacketData, + Slash, + InvariantSnapshot, + Status, + ModelInitState, +} from './common.js'; + +/** + * Store outbound packets in FIFO order from a given chain. + * The number of block commits for each packet is stored, + * and deliverable packets can be consumed once they are sufficiently + * committed. This mimics real IBC connections. + */ +class Outbox { + model; + chain; + // [packet, num commits] + fifo: [Packet, number][]; + + constructor(model: Model, chain: Chain) { + this.model = model; + this.chain = chain; + this.fifo = []; + } + + /** + * Adds a packet to the outbox, with 0 commits. + * @param data packet data + */ + add = (data: PacketData) => { + this.fifo.push([ + { + data, + sendHeight: this.model.h[this.chain], + }, + 0, + ]); + }; + + /** + * Get and internally delete deliverable packets from the outbox. + * @param num max num packets to consume + * @returns A list of deliverable packets + */ + consume = (num: number): Packet[] => { + const [available, unavailable] = _.partition( + this.fifo, + (e) => 1 < e[1], + ); + const take = available.slice(0, num); + this.fifo = available.slice(num).concat(unavailable); + return take.map((e) => e[0]); + }; + + /** + * Commit the packets in the outbox. Once a packet has been + * committed twice it is available for delivery, as per the + * ibc light-client functioning. + */ + commit = () => { + this.fifo = this.fifo.map((e) => [e[0], e[1] + 1]); + }; +} + +class Staking { + // Model handle + m; + // Validator delegations from the sole delegator account. + // A fixed descending order is used for the initial values to allow + // easy setup in the SUT. + delegation: number[]; + // Validator tokens. Tokens are equivalent to power, with a ratio 1:1. + // Validator tokens are not equal to delegation, because the validator + // may have tokens from delegation by OTHER delegators, and we model + // a single delegator. + tokens: number[]; + // Validator status + status: Status[]; + // Undelegation queue + undelegationQ: Undelegation[]; + // Unbonding validator queue + validatorQ: Unval[]; + // Validator jail timestamp + // Undefined if validator is not jailed + jailed: (number | undefined)[]; + // Initial balance of the sole delegator account. + // Only a single delegator is modelled, as this seems sufficient + // to exercise all Interchain Security logic. + delegatorTokens: number; + // Unique ID used to count unbonding and redelegation queue entries, + // as well as unbonding validators. + opID: number; + // maps validator id -> power + // used to compute validator set changes + changes: Record; + // The validators of the last block + lastVals: number[]; + // The number of tokens of the last block + // Used to compute validator power changes used in VSCs + lastTokens: number[]; + + constructor(model: Model, { staking }: ModelInitState) { + this.m = model; + Object.assign(this, staking); + } + + /** + * Compute the new set of active validators + */ + newVals = () => { + const valid = (i: number): boolean => + 1 <= this.tokens[i] && this.jailed[i] === undefined; + let vals = _.range(NUM_VALIDATORS); + // stable sort => breaks ties based on validator + // address numerical value. This mimics staking module. + vals = _.sortBy(vals, (i) => -this.tokens[i]); + vals = vals.filter(valid); + vals = vals.slice(0, MAX_VALIDATORS); + + assert( + 0 < vals.length, + 'EMPTY VAL SET - not supposed to happen. Model or action generation is wrong.', + ); + + { + // Is at least 2/3 of new active voting power held by + // old validators? + + // How much active power does the old val set have? + const newActivePowerOldVals = this.tokens.reduce( + (sum, x, i) => + // old val and new val + this.lastVals.includes(i) && vals.includes(i) ? sum + x : sum, + 0, + ); + // How much active power is there in total? + const newActivePowerTotal = this.tokens.reduce( + (sum, x, i) => (vals.includes(i) ? sum + x : sum), + 0, + ); + if (newActivePowerOldVals < (2 / 3) * newActivePowerTotal) { + this.m.events.push(Event.MORE_THAN_ONE_THIRD_VAL_POWER_CHANGE); + } + } + return vals; + }; + + endBlockComputeValUpdates = () => { + const oldVals = this.lastVals; + const newVals = this.newVals(); + // Bond new validators + newVals.forEach((i) => { + this.status[i] = Status.BONDED; + const before = this.validatorQ.length; + this.validatorQ = this.validatorQ.filter((e) => e.val != i); + if (this.validatorQ.length != before) { + this.m.events.push(Event.REBOND_UNVAL); + } + }); + // Start unbonding old validators + _.difference(oldVals, newVals) + // sort is necessary because order of iteration + // defines an implicit mapping of opID to unval. + // This must match SUT. + .sort((a, b) => a - b) + .forEach((i) => { + const unval: Unval = { + val: i, + unbondingHeight: this.m.h[P], + unbondingTime: this.m.t[P] + UNBONDING_SECONDS_P, + onHold: true, + opID: this.opID, + }; + this.validatorQ.push(unval); + this.m.ccvP.afterUnbondingInitiated(this.opID); + this.opID += 1; + this.status[i] = Status.UNBONDING; + }); + + // Compute updates + this.changes = {}; + newVals.forEach((i) => { + if (this.tokens[i] != this.lastTokens[i]) { + // validator power changed + this.changes[i] = this.tokens[i]; + } + }); + _.difference(newVals, oldVals).forEach((i) => { + // validator bonded + this.changes[i] = this.tokens[i]; + }); + _.difference(oldVals, newVals).forEach((i) => { + // validator no longer bonded + this.changes[i] = 0; + }); + + // Save the valset and their tokens + // (mimics block commit) + this.lastVals = newVals; + this.lastTokens = _.clone(this.tokens); + }; + + endBlockMaturation = () => { + // Process any unbonding validators that might have matured + const completedUnvals = this.validatorQ.filter( + (e: Unval) => + e.unbondingTime <= this.m.t[P] && + e.unbondingHeight <= this.m.h[P] && + !e.onHold, + ); + completedUnvals.forEach((e: Unval) => { + this.status[e.val] = Status.UNBONDED; + this.m.events.push(Event.COMPLETE_UNVAL_IN_ENDBLOCK); + }); + this.validatorQ = this.validatorQ.filter( + (e) => !completedUnvals.includes(e), + ); + + // Process any undelegations that might have matured + const processedUndels = this.undelegationQ.filter( + (e) => + e.completionTime <= this.m.t[P] && + e.willBeProcessedByStakingModule, + ); + processedUndels.forEach( + (e: Undelegation) => (e.willBeProcessedByStakingModule = false), + ); + const completedUndels = processedUndels.filter((e) => !e.onHold); + if (completedUndels.length < processedUndels.length) { + this.m.events.push(Event.SOME_UNDELS_EXPIRED_BUT_NOT_COMPLETED); + } + this.undelegationQ = this.undelegationQ.filter( + (e: Undelegation) => !completedUndels.includes(e), + ); + if (0 < completedUndels.length) { + this.m.events.push(Event.COMPLETE_UNDEL_IN_ENDBLOCK); + } + // Refund completed undelegations + this.delegatorTokens += completedUndels.reduce( + (x, e) => x + e.balance, + 0, + ); + }; + + endBlock = () => { + this.endBlockComputeValUpdates(); + this.endBlockMaturation(); + }; + + delegate = (val: Validator, amt: number) => { + this.delegatorTokens -= amt; + this.tokens[val] += amt; + this.delegation[val] += amt; + }; + + undelegate = (val: Validator, amt: number) => { + if (this.delegation[val] < amt) { + this.m.events.push(Event.INSUFFICIENT_SHARES); + return; + } + this.tokens[val] -= amt; + this.delegation[val] -= amt; + const und: Undelegation = { + val: val, + creationHeight: this.m.h[P], + completionTime: this.m.t[P] + UNBONDING_SECONDS_P, + balance: amt, + initialBalance: amt, + onHold: true, + opID: this.opID, + willBeProcessedByStakingModule: true, + }; + this.undelegationQ.push(und); + this.m.ccvP.afterUnbondingInitiated(this.opID); + this.opID += 1; + }; + + slash = (val: Validator, infractionHeight: number) => { + const valid = (e: Undelegation): boolean => + e.val === val && + infractionHeight <= e.creationHeight && + (this.m.t[P] < e.completionTime || e.onHold); + const ubds: Undelegation[] = this.undelegationQ.filter(valid); + if (infractionHeight < this.m.h[P]) { + ubds.forEach(() => { + this.m.events.push(Event.SLASH_UNDEL); + }); + } + }; + + jailUntil = (val: Validator, timestamp: number) => { + this.jailed[val] = timestamp; + this.m.events.push(Event.JAIL); + }; + + unbondingCanComplete = (opID: number) => { + { + // Allow maturity of relevant validator + const e = _.find(this.validatorQ, (e) => e.opID === opID); + if (e) { + e.onHold = false; + this.m.events.push(Event.SET_UNVAL_HOLD_FALSE); + return; + } + } + { + // Allow maturity of relevant unbonding delegation + const e = _.find(this.undelegationQ, (e) => e.opID === opID); + if (e) { + if (e.completionTime <= this.m.t[P]) { + this.delegatorTokens += e.balance; + this.undelegationQ = this.undelegationQ.filter((x) => x !== e); + this.m.events.push(Event.COMPLETE_UNDEL_IMMEDIATE); + } else { + e.onHold = false; + this.m.events.push(Event.SET_UNDEL_HOLD_FALSE); + } + } + } + }; + + valUpdates = () => { + return _.clone(this.changes); + }; +} + +class CCVProvider { + m; + // height of onChanOpenConfirm event + initialHeight: number; + // next id to use + vscID: number; + // map ids to height of sending + // used to calculate infraction height from consumer initiated slashing + vscIDtoH: Record; + // map ids to unbonding operation ids + // used to mature unbonding operations when receiving maturity packets + vscIDtoOpIDs: Map; + // validators who have been slashed since last VSC + downtimeSlashAcks: Validator[]; + // is a validator tombstoned? + tombstoned: boolean[]; + // unbonding operations to be completed in EndBlock + matureUnbondingOps: number[]; + + constructor(model: Model, { ccvP }: ModelInitState) { + this.m = model; + Object.assign(this, ccvP); + } + + endBlockCIS = () => { + this.vscIDtoH[this.vscID] = this.m.h[P] + 1; + }; + + endBlockVSU = () => { + // notify staking module that unbonding operations can complete + this.matureUnbondingOps.forEach((opID) => { + this.m.staking.unbondingCanComplete(opID); + }); + this.matureUnbondingOps = []; + const valUpdates = this.m.staking.valUpdates(); + if ( + 0 < _.keys(valUpdates).length || + this.vscIDtoOpIDs.has(this.vscID) + ) { + if (0 === _.keys(valUpdates).length) { + this.m.events.push(Event.SEND_VSC_NOT_BECAUSE_CHANGE); + } + if (0 < this.downtimeSlashAcks.length) { + this.m.events.push(Event.SEND_VSC_WITH_DOWNTIME_ACK); + } else { + this.m.events.push(Event.SEND_VSC_WITHOUT_DOWNTIME_ACK); + } + const data: Vsc = { + vscID: this.vscID, + updates: valUpdates, + downtimeSlashAcks: this.downtimeSlashAcks, + }; + this.downtimeSlashAcks = []; + this.m.outbox[P].add(data); + } + this.vscID += 1; + }; + + endBlock = () => { + this.endBlockCIS(); + this.endBlockVSU(); + }; + + onReceive = (data: PacketData) => { + // It's sufficient to use isDowntime field as differentiator + if ('isDowntime' in data) { + this.onReceiveSlash(data); + } else { + this.onReceiveVSCMatured(data); + } + }; + + onReceiveVSCMatured = (data: VscMatured) => { + if (this.vscIDtoOpIDs.has(data.vscID)) { + this.vscIDtoOpIDs.get(data.vscID)!.forEach((opID: number) => { + this.matureUnbondingOps.push(opID); + }); + this.vscIDtoOpIDs.delete(data.vscID); + } + }; + + onReceiveSlash = (data: Slash) => { + let infractionHeight = undefined; + + if (data.vscID === 0) { + infractionHeight = this.initialHeight; + } else { + infractionHeight = this.vscIDtoH[data.vscID]; + } + + if (this.m.staking.status[data.val] === Status.UNBONDED) { + this.m.events.push(Event.RECEIVE_SLASH_REQUEST_UNBONDED); + return; + } + + if (data.isDowntime) { + this.m.events.push(Event.RECEIVE_DOWNTIME_SLASH_REQUEST); + } else { + this.m.events.push(Event.RECEIVE_DOUBLE_SIGN_SLASH_REQUEST); + } + + if (this.tombstoned[data.val]) { + return; + } + + this.m.staking.slash(data.val, infractionHeight); + this.m.staking.jailUntil(data.val, this.m.t[P] + JAIL_SECONDS); + if (data.isDowntime) { + this.downtimeSlashAcks.push(data.val); + } else { + this.tombstoned[data.val] = true; + } + }; + + afterUnbondingInitiated = (opID: number) => { + if (!this.vscIDtoOpIDs.has(this.vscID)) { + this.vscIDtoOpIDs.set(this.vscID, []); + } + this.vscIDtoOpIDs.get(this.vscID)!.push(opID); + }; +} + +class CCVConsumer { + m; + // maps consumer height h to the id of the last vscid + // received at height h-1 + hToVscID: Record; + // validator power changes pending aggregation + pendingChanges: Record[]; + // maps vscid to earliest timestamp to mature + maturingVscs: Map; + // is there an outstanding downtime operation for a validator? + outstandingDowntime: boolean[]; + // array of validators to power + // value undefined if validator is not known to consumer + consumerPower: (number | undefined)[]; + + constructor(model: Model, { ccvC }: ModelInitState) { + this.m = model; + Object.assign(this, ccvC); + } + + beginBlock = () => { + this.hToVscID[this.m.h[C] + 1] = this.hToVscID[this.m.h[C]]; + }; + + endBlockVSU = () => { + // Gather all matured VSCs + const matured = (() => { + const ret: number[] = []; + this.maturingVscs.forEach((time, vscID) => { + if (time <= this.m.t[C]) { + ret.push(vscID); + } + }); + return ret; + })(); + // Send a maturity packet for each matured VSC + matured.forEach((vscID) => { + const data: VscMatured = { vscID }; + this.m.events.push(Event.CONSUMER_SEND_MATURATION); + this.m.outbox[C].add(data); + this.maturingVscs.delete(vscID); + }); + + // Aggregate and apply validator voting power changes + const changes = (() => { + const ret: Map = new Map(); + this.pendingChanges.forEach((updates) => { + Object.entries(updates).forEach(([val, power]) => + ret.set(parseInt(val), power), + ); + }); + return ret; + })(); + + this.pendingChanges = []; + + changes.forEach((power, val) => { + if (0 < power) { + if (this.consumerPower[val] === undefined) { + this.m.events.push(Event.CONSUMER_ADD_VAL); + } else { + this.m.events.push(Event.CONSUMER_UPDATE_VAL); + } + this.consumerPower[val] = power; + } else { + this.consumerPower[val] = undefined; + this.m.events.push(Event.CONSUMER_DEL_VAL); + } + }); + }; + + endBlock = () => { + this.endBlockVSU(); + }; + + onReceive = (data: PacketData) => { + this.onReceiveVSC(data as Vsc); // TODO: remove type assumption + }; + + onReceiveVSC = (data: Vsc) => { + this.hToVscID[this.m.h[C] + 1] = data.vscID; + this.pendingChanges.push(data.updates); + this.maturingVscs.set(data.vscID, this.m.t[C] + UNBONDING_SECONDS_C); + data.downtimeSlashAcks.forEach((val: Validator) => { + this.m.events.push(Event.RECEIVE_DOWNTIME_SLASH_ACK); + this.outstandingDowntime[val] = false; + }); + }; + + sendSlashRequest = ( + val: Validator, + infractionHeight: number, + isDowntime: boolean, + ) => { + if (isDowntime && this.outstandingDowntime[val]) { + this.m.events.push(Event.DOWNTIME_SLASH_REQUEST_OUTSTANDING); + return; + } + const data: Slash = { + val, + vscID: this.hToVscID[infractionHeight], + isDowntime, + }; + this.m.outbox[C].add(data); + if (isDowntime) { + this.m.events.push(Event.SEND_DOWNTIME_SLASH_REQUEST); + this.outstandingDowntime[val] = true; + } else { + this.m.events.push(Event.SEND_DOUBLE_SIGN_SLASH_REQUEST); + } + }; +} + +class Model { + h; + t; + outbox: Record = { + provider: new Outbox(this, P), + consumer: new Outbox(this, C), + }; + staking: Staking; + ccvP: CCVProvider; + ccvC: CCVConsumer; + blocks: BlockHistory; + events: Event[]; + + constructor( + blocks: BlockHistory, + events: Event[], + state: ModelInitState, + ) { + this.blocks = blocks; + this.events = events; + this.h = state.h; + this.t = state.t; + this.staking = new Staking(this, state); + this.ccvP = new CCVProvider(this, state); + this.ccvC = new CCVConsumer(this, state); + // Implicitly, there is already a partial order between + // model initial blocks on P and C because C starts with + // the same validator set as P (and thus must have received + // a packet from P). + this.blocks.partialOrder.deliver(C, 0, 0); + this.blocks.commitBlock(P, this.invariantSnapshot()); + this.blocks.commitBlock(C, this.invariantSnapshot()); + this.beginBlock(P); + this.beginBlock(C); + } + + invariantSnapshot = (): InvariantSnapshot => { + return cloneDeep({ + h: this.h, + t: this.t, + tokens: this.staking.tokens, + status: this.staking.status, + undelegationQ: this.staking.undelegationQ, + delegatorTokens: this.staking.delegatorTokens, + consumerPower: this.ccvC.consumerPower, + }); + }; + + delegate = (val: number, amt: number) => { + this.staking.delegate(val, amt); + }; + + undelegate = (val: number, amt: number) => { + this.staking.undelegate(val, amt); + }; + + consumerSlash = ( + val: number, + infractionHeight: number, + isDowntime: boolean, + ) => { + this.ccvC.sendSlashRequest(val, infractionHeight, isDowntime); + }; + + updateClient = (_: Chain) => { + // noop + }; + + deliver = (chain: Chain, num: number) => { + if (chain === P) { + this.outbox[C].consume(num).forEach((p) => { + this.blocks.partialOrder.deliver(P, p.sendHeight, this.h[P]); + this.ccvP.onReceive(p.data); + }); + } + if (chain === C) { + this.outbox[P].consume(num).forEach((p) => { + this.blocks.partialOrder.deliver(C, p.sendHeight, this.h[C]); + this.ccvC.onReceive(p.data); + }); + } + }; + + endAndBeginBlock = (chain: Chain) => { + this.endBlock(chain); + this.beginBlock(chain); + }; + + endBlock = (chain: Chain) => { + if (chain === P) { + this.staking.endBlock(); + this.ccvP.endBlock(); + } + if (chain === C) { + this.ccvC.endBlock(); + } + this.outbox[chain].commit(); + this.blocks.commitBlock(chain, this.invariantSnapshot()); + }; + + beginBlock = (chain: Chain) => { + this.h[chain] += 1; + this.t[chain] += BLOCK_SECONDS; + if (chain === P) { + // No op + } + if (chain === C) { + this.ccvC.beginBlock(); + } + }; +} + +export { Outbox, Model }; diff --git a/diff-tests/core/model/src/properties.ts b/diff-tests/core/model/src/properties.ts new file mode 100644 index 0000000000..951298d44a --- /dev/null +++ b/diff-tests/core/model/src/properties.ts @@ -0,0 +1,280 @@ +import { strict as assert } from 'node:assert'; +import _ from 'underscore'; +import { + P, + C, + UNBONDING_SECONDS_C, + NUM_VALIDATORS, +} from './constants.js'; +import { + InvariantSnapshot, + Chain, + CommittedBlock, + Status, +} from './common.js'; + +/** + * Queries and data structures in this file are currently naive + * and below optimal. + * Partial order queries and other total order queries in + * bond-based-consumer-voting-power can be done with binary searches. + */ + +/** + * Data structure used to store a partial order of blocks. The partial order + * is induced by packet delivery for blocks on different chains, and by height + * for blocks on the same chain. + * See https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/system_model_and_properties.md#system-properties + */ +class PartialOrder { + // map chain -> block height in chain -> block height in counterparty chain + greatestPred: Record> = { + provider: new Map(), + consumer: new Map(), + }; + // map chain -> block height in chain -> block height in counterparty chain + leastSucc: Record> = { + provider: new Map(), + consumer: new Map(), + }; + + /** + * Mark the delivery of a packet. Induces a partial order between blocks + * on different chains. + * @param receiverChain chain receiving packet + * @param sendHeight send height on sending chain + * @param receiveHeight receive height on receiving chain + */ + deliver = ( + receiverChain: Chain, + sendHeight: number, + receiveHeight: number, + ) => { + let h = sendHeight; + if (this.greatestPred[receiverChain].has(receiveHeight)) { + h = Math.max( + this.greatestPred[receiverChain].get(receiveHeight) as number, + h, + ); + } + this.greatestPred[receiverChain].set(receiveHeight, h); + const senderChain = receiverChain === P ? C : P; + h = receiveHeight; + if (this.leastSucc[senderChain].has(sendHeight)) { + h = Math.min( + this.leastSucc[senderChain].get(sendHeight) as number, + h, + ); + } + this.leastSucc[senderChain].set(sendHeight, h); + }; + + /** + * @param chain chain of block + * @param height height of block + * @returns Returns the height greatest predecessor block on the counterparty + * chain if it exists, else undefined. + */ + getGreatestPred = ( + chain: Chain, + height: number, + ): number | undefined => { + const it = this.greatestPred[chain].keys(); + let bestH = -1; + let bestV = -1; + let result = it.next(); + while (!result.done) { + const h = result.value; + if (bestH < h && h <= height) { + bestH = h; + bestV = this.greatestPred[chain].get(h) as number; + } + result = it.next(); + } + if (bestV === -1) { + // No greatest predecessor exists. + return undefined; + } + return bestV; + }; + + /** + * + * @param chain chain of block + * @param height height of block + * @returns Returns the height of the least successing block on the counterparty + * chain if it exists, else undefined. + */ + getLeastSucc = (chain: Chain, height: number): number | undefined => { + const it = this.leastSucc[chain].keys(); + let bestH = 100000000000000; // Infinity + let bestAnswer = -1; + let result = it.next(); + while (!result.done) { + const h = result.value; + if (h < bestH && height <= h) { + bestH = h; + bestAnswer = this.leastSucc[chain].get(h) as number; + } + result = it.next(); + } + if (bestAnswer === -1) { + // No least successor exists. + return undefined; + } + return bestAnswer; + }; +} + +class BlockHistory { + partialOrder = new PartialOrder(); + blocks: Record> = { + provider: new Map(), + consumer: new Map(), + }; + /** + * Mark state as permanently committed to the blockchain. + * @param chain + * @param invariantSnapshot + */ + commitBlock = (chain: Chain, invariantSnapshot: InvariantSnapshot) => { + const h = invariantSnapshot.h[chain]; + const b: CommittedBlock = { + chain, + invariantSnapshot, + }; + this.blocks[chain].set(h, b); + }; +} + +function sum(arr: number[]): number { + return arr.reduce((sum: number, x: number) => sum + x, 0); +} + +/** + * Is the total fund value in the system constant? + * It only makes sense to check this if slashing with non-zero + * slash factors never occurs, because slashing with non-zero + * slash factors burns funds. + * + * @param hist A history of blocks. + * @returns Is the property satisfied? + */ +function stakingWithoutSlashing(hist: BlockHistory): boolean { + const blocks = Array.from(hist.blocks[P].entries()) + .sort((a, b) => a[0] - b[0]) + .map((e) => e[1]) + .map((b) => b.invariantSnapshot); + + function value(e: InvariantSnapshot) { + let x = e.delegatorTokens; + x += sum(e.tokens); + x += sum(e.undelegationQ.map((e) => e.balance)); + return x; + } + + const v = value(blocks[0]); + for (let i = 1; i < blocks.length; i++) { + if (value(blocks[i]) !== v) { + return false; + } + } + return true; +} + +/** + * Checks the bond-based consumer voting power property as defined + * in https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/system_model_and_properties.md#system-properties + * but modified to account for finite executions and always zero slash factors. + * + * @param hist A history of blocks. + * @returns Is the property satisfied? + */ +function bondBasedConsumerVotingPower(hist: BlockHistory): boolean { + const partialOrder = hist.partialOrder; + const blocks = hist.blocks; + /** + * @param block to compute validator voting powers for + * @param hp is the earliest height for unbondings to account for + * @returns burnable voting power for each validator on the Provider chain + */ + function powerProvider(block: CommittedBlock, hp: number): number[] { + return _.range(NUM_VALIDATORS).map((i) => { + let x = 0; + if (block.invariantSnapshot.status[i] !== Status.UNBONDED) { + x += block.invariantSnapshot.tokens[i]; + } + x += sum( + block.invariantSnapshot.undelegationQ + .filter((e) => e.val === i) + .filter((e) => hp <= e.creationHeight) + .map((e) => e.initialBalance), + ); + return x; + }); + } + function powerConsumer(block: CommittedBlock) { + return block.invariantSnapshot.consumerPower; + } + function inner(hc: number): boolean { + const hp = partialOrder.getGreatestPred(C, hc); + assert(hp !== undefined, 'this should never happen.'); + function getHC_() { + const tsHC = (blocks[C].get(hc) as CommittedBlock).invariantSnapshot + .t[C]; + // Get earliest height on consumer + // that a VSC received at hc could mature + const heights = Array.from(blocks[C].keys()).sort((a, b) => a - b); + for (let i = 0; i < heights.length; i++) { + const hc_ = heights[i]; + if ( + tsHC + UNBONDING_SECONDS_C <= + (blocks[C].get(hc_) as CommittedBlock).invariantSnapshot.t[C] + ) { + return hc_; + } + } + return undefined; + } + const hc_ = getHC_(); + let hp_ = undefined; + if (hc_ !== undefined) { + hp_ = partialOrder.getLeastSucc(C, hc_); + } + let limit = Math.max(...Array.from(blocks[P].keys())) + 1; + if (hp_ !== undefined) { + // If provider receives maturation + // only check property up to and not including + // the block at which received. + limit = hp_; + } + for (let h = hp; h < limit; h++) { + for (let i = 0; i < NUM_VALIDATORS; i++) { + const powerP = powerProvider( + blocks[P].get(h) as CommittedBlock, + hp + 1, + ); + const powerC = powerConsumer(blocks[C].get(hc) as CommittedBlock); + if (powerC[i] !== undefined) { + if (powerP[i] < (powerC[i] as number)) { + return false; + } + } + } + } + return true; + } + return _.reduce( + Array.from(blocks[C].keys()), + (good, hc) => good && inner(hc), + true, + ); +} + +export { + PartialOrder, + CommittedBlock, + BlockHistory, + stakingWithoutSlashing, + bondBasedConsumerVotingPower, +}; diff --git a/diff-tests/core/model/src/traceUtil.ts b/diff-tests/core/model/src/traceUtil.ts new file mode 100644 index 0000000000..9e070ce890 --- /dev/null +++ b/diff-tests/core/model/src/traceUtil.ts @@ -0,0 +1,232 @@ +import * as fs from 'fs'; +import * as childProcess from 'child_process'; +import _ from 'underscore'; +import { TraceAction } from './common.js'; +import { Event } from './constants.js'; + +import { + P, + C, + UNBONDING_SECONDS_P, + UNBONDING_SECONDS_C, + TRUSTING_SECONDS, + NUM_VALIDATORS, + MAX_VALIDATORS, + SLASH_DOUBLESIGN, + SLASH_DOWNTIME, + JAIL_SECONDS, + BLOCK_SECONDS, + INITIAL_DELEGATOR_TOKENS, + DELEGATE_AMT_MIN, + DELEGATE_AMT_MAX, + UNDELEGATE_AMT_MIN, + UNDELEGATE_AMT_MAX, + ISDOWNTIME_PROBABILITY, + MAX_NUM_PACKETS_FOR_DELIVER, +} from './constants.js'; + +/** + * Record meta data to written traces. + */ +const meta = { + // Commit of interchain-security/ that the trace was generated. + commit: childProcess.execSync('git rev-parse HEAD').toString().trim(), + // Diff between the working tree and the commit. + diff: childProcess.execSync('git diff').toString().trim(), +}; + +/** + * Forcibly ensure an empty directory exists. + * @param dir directory name + */ +function forceMakeEmptyDir(dir: string) { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + return; + } + fs.rmSync(dir, { recursive: true }); + forceMakeEmptyDir(dir); +} + +/** + * Write the trace data to file, with accompanying metadata. + * + * @param fn Filename + * @param events Events included in trace + * @param actions Actions included in trace + * @param blocks Block snapshots included in trace + */ +function dumpTrace(fn: string, actions: TraceAction[], events: Event[]) { + const toDump = { + // Record metadata + meta, + // Record values of model constants + constants: { + P, + C, + UNBONDING_SECONDS_P, + UNBONDING_SECONDS_C, + TRUSTING_SECONDS, + NUM_VALIDATORS, + MAX_VALIDATORS, + SLASH_DOUBLESIGN, + SLASH_DOWNTIME, + JAIL_SECONDS, + BLOCK_SECONDS, + INITIAL_DELEGATOR_TOKENS, + DELEGATE_AMT_MIN, + DELEGATE_AMT_MAX, + UNDELEGATE_AMT_MIN, + UNDELEGATE_AMT_MAX, + ISDOWNTIME_PROBABILITY, + MAX_NUM_PACKETS_FOR_DELIVER, + }, + // Record which actions occurred + actions, + // Events which occurred + events, + }; + // Write human readable JSON + const json = JSON.stringify([toDump], null); + fs.writeFileSync(fn, json); +} + +/** + * Reads all json traces in traces/ and creates a new trace file + * consisting of a list of several traces. The traces in the new + * trace file are chosen in such a way to ensure a covering of + * each model event. + * The traces are selected according to a greedy algorithm, ensuring + * that each event occurs EVENT_INSTANCES times while somewhat + * minimizing the number of traces included. + * In this way, it is possible to obtain a concise set of traces which + * test many model behaviors, reducing the time needed to test the SUT. + * + * @param outFilePrefix absolute filepath to write output to + * @param targetCntForEventMax max number of times to it each event + */ +function createSmallSubsetOfCoveringTraces( + outFilePrefix: string, + targetCntForEventMax: number, +) { + // directory to read traces from + const DIR = 'traces/'; + // file to write the new traces to + const inputFilenames: string[] = []; + + fs.readdirSync(DIR).forEach((fn) => { + inputFilenames.push(`${DIR}${fn}`); + }); + + const eventNameToCounterIx: Record = {}; + + for (const evt in Event) { + const stringRepr = Event[evt as keyof typeof Event]; + eventNameToCounterIx[stringRepr] = Object.keys(Event).indexOf(evt); + } + + const NUM_EVENTS = Object.keys(Event).length; + + const maxPossibleCntForEvent = new Array(NUM_EVENTS).fill(0); + + const eventCntsByTrace: [string, number[]][] = []; + // For each trace file + inputFilenames.forEach((fn) => { + const trace = JSON.parse(fs.readFileSync(fn, 'utf8'))[0]; + const traceEventCnt: number[] = new Array( + Object.keys(Event).length, + ).fill(0); + // for each event that occurred in the trace + trace.events.forEach((evtName: string) => { + const i = eventNameToCounterIx[evtName]; + // cnt the occurrences in this trace + traceEventCnt[i] += 1; + // cnt the global occurrences + maxPossibleCntForEvent[i] += 1; + }); + eventCntsByTrace.push([fn, traceEventCnt]); + }); + + const targetCntForEvent = maxPossibleCntForEvent.map((x) => + Math.min(x, targetCntForEventMax), + ); + + const accumulatedCntForEvent = new Array(NUM_EVENTS).fill(0); + /** + * Computes greedy score for a event frequency cnt vector + * @param v vector representing event counts + * @returns + */ + function score(v: number[]): number { + let x = 0; + for (let i = 0; i < v.length; i++) { + // How many events of this type are still desired? + const need = Math.max( + targetCntForEvent[i] - accumulatedCntForEvent[i], + 0, + ); + // How many events of this type does this trace have? + const get = v[i]; + x += Math.min(need, get); + } + return x; + } + + const outputFilenames: string[] = []; + // While we have not reached the target occurrence count for all events + while ( + accumulatedCntForEvent.some((x, i) => x < targetCntForEvent[i]) + ) { + // Sort by score descending + eventCntsByTrace.sort((a, b) => score(b[1]) - score(a[1])); + const [fn, traceEventCnt] = eventCntsByTrace.shift()!; + for (let i = 0; i < traceEventCnt.length; i++) { + accumulatedCntForEvent[i] += traceEventCnt[i]; + } + // Use this trace + outputFilenames.push(fn); + } + + // Diagnostic //////////////////////////////////////////// + console.log(`num traces used: `, outputFilenames.length); + for (let i = 0; i < accumulatedCntForEvent.length; i++) { + const evtName = Object.keys(Event)[i]; + const cnt = accumulatedCntForEvent[i]; + console.log(evtName, cnt); + } + ////////////////////////////////////////////////////////// + + const allTraces: any[] = []; + outputFilenames.forEach((fn) => { + const traceJson = JSON.parse(fs.readFileSync(fn, 'utf8')); + const trace = traceJson[0]; + allTraces.push(trace); + }); + fs.writeFileSync(outFilePrefix, JSON.stringify(allTraces)); +} + +/** + * Pretty print the number of times each event occurs. + * @param allEvents A map of event type to number of occurrences + */ +function logEventData(allEvents: Event[]) { + const eventCnt = _.countBy(allEvents, _.identity); + for (const evt in Event) { + const evtName = (Event as any)[evt]; + if (!_.has(eventCnt, evtName)) { + eventCnt[evtName] = 0; + } + } + const cnts = _.chain(eventCnt) + .pairs() + .sortBy((pair) => -pair[1]); + + console.log(cnts); +} + +export { + createSmallSubsetOfCoveringTraces, + dumpTrace, + forceMakeEmptyDir, + logEventData, +}; diff --git a/diff-tests/core/model/tsconfig.json b/diff-tests/core/model/tsconfig.json new file mode 100644 index 0000000000..2a6e221b27 --- /dev/null +++ b/diff-tests/core/model/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "node16", + "lib": [ + "ES2022" + ], + "moduleResolution": "Node16", + "rootDir": ".", + "outDir": "build", + "allowSyntheticDefaultImports": true, + "importHelpers": true, + "alwaysStrict": true, + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + }, + "include": [ + "src/**/*", + "__tests__/**/*" + ] +} \ No newline at end of file diff --git a/diff-tests/core/model/tsconfig.release.json b/diff-tests/core/model/tsconfig.release.json new file mode 100644 index 0000000000..f08638c215 --- /dev/null +++ b/diff-tests/core/model/tsconfig.release.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": false, + "removeComments": true + }, + "include": ["src/**/*"] +} diff --git a/diff-tests/core/model/yarn.lock b/diff-tests/core/model/yarn.lock new file mode 100644 index 0000000000..77abc940b0 --- /dev/null +++ b/diff-tests/core/model/yarn.lock @@ -0,0 +1,2862 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.6.tgz#8b37d24e88e8e21c499d4328db80577d8882fa53" + integrity sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.6.tgz#54a107a3c298aee3fe5e1947a6464b9b6faca03d" + integrity sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.6" + "@babel/helper-compilation-targets" "^7.18.6" + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helpers" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.6" + "@babel/types" "^7.18.6" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/generator@^7.18.6", "@babel/generator@^7.7.2": + version "7.18.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.7.tgz#2aa78da3c05aadfc82dbac16c99552fc802284bd" + integrity sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A== + dependencies: + "@babel/types" "^7.18.7" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz#18d35bfb9f83b1293c22c55b3d576c1315b6ed96" + integrity sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg== + dependencies: + "@babel/compat-data" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.20.2" + semver "^6.3.0" + +"@babel/helper-environment-visitor@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz#b7eee2b5b9d70602e59d1a6cad7dd24de7ca6cd7" + integrity sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q== + +"@babel/helper-function-name@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz#8334fecb0afba66e6d87a7e8c6bb7fed79926b83" + integrity sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw== + dependencies: + "@babel/template" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.6.tgz#57e3ca669e273d55c3cda55e6ebf552f37f483c8" + integrity sha512-L//phhB4al5uucwzlimruukHB3jRd5JGClwRMD/ROrVjXfLqovYnvQrK/JK36WYyVwGGO7OD3kMyVTjx+WVPhw== + dependencies: + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.8.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz#9448974dd4fb1d80fefe72e8a0af37809cd30d6d" + integrity sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg== + +"@babel/helper-simple-access@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" + integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helpers@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.6.tgz#4c966140eaa1fcaa3d5a8c09d7db61077d4debfd" + integrity sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ== + dependencies: + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.6.tgz#845338edecad65ebffef058d3be851f1d28a63bc" + integrity sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz#1c09cd25795c7c2b8a4ba9ae49394576d4133285" + integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/template@^7.18.6", "@babel/template@^7.3.3": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" + integrity sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/traverse@^7.18.6", "@babel/traverse@^7.7.2": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.6.tgz#a228562d2f46e89258efa4ddd0416942e2fd671d" + integrity sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-function-name" "^7.18.6" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/types" "^7.18.6" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.18.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.7.tgz#a4a2c910c15040ea52cdd1ddb1614a65c8041726" + integrity sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@eslint/eslintrc@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" + integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.3.2" + globals "^13.15.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" + +"@humanwhocodes/config-array@^0.9.2": + version "0.9.5" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" + integrity sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-28.1.1.tgz#305f8ca50b6e70413839f54c0e002b60a0f2fd7d" + integrity sha512-0RiUocPVFEm3WRMOStIHbRWllG6iW6E3/gUPnf4lkrVFyXIIDeCe+vlKeYyFOMhB2EPE6FLFCNADSOOQMaqvyA== + dependencies: + "@jest/types" "^28.1.1" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^28.1.1" + jest-util "^28.1.1" + slash "^3.0.0" + +"@jest/core@^28.1.2": + version "28.1.2" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-28.1.2.tgz#eac519b9acbd154313854b8823a47b5c645f785a" + integrity sha512-Xo4E+Sb/nZODMGOPt2G3cMmCBqL4/W2Ijwr7/mrXlq4jdJwcFQ/9KrrJZT2adQRk2otVBXXOz1GRQ4Z5iOgvRQ== + dependencies: + "@jest/console" "^28.1.1" + "@jest/reporters" "^28.1.2" + "@jest/test-result" "^28.1.1" + "@jest/transform" "^28.1.2" + "@jest/types" "^28.1.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^28.0.2" + jest-config "^28.1.2" + jest-haste-map "^28.1.1" + jest-message-util "^28.1.1" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.1" + jest-resolve-dependencies "^28.1.2" + jest-runner "^28.1.2" + jest-runtime "^28.1.2" + jest-snapshot "^28.1.2" + jest-util "^28.1.1" + jest-validate "^28.1.1" + jest-watcher "^28.1.1" + micromatch "^4.0.4" + pretty-format "^28.1.1" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^28.1.2": + version "28.1.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-28.1.2.tgz#94a052c0c5f9f8c8e6d13ea6da78dbc5d7d9b85b" + integrity sha512-I0CR1RUMmOzd0tRpz10oUfaChBWs+/Hrvn5xYhMEF/ZqrDaaeHwS8yDBqEWCrEnkH2g+WE/6g90oBv3nKpcm8Q== + dependencies: + "@jest/fake-timers" "^28.1.2" + "@jest/types" "^28.1.1" + "@types/node" "*" + jest-mock "^28.1.1" + +"@jest/expect-utils@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-28.1.1.tgz#d84c346025b9f6f3886d02c48a6177e2b0360587" + integrity sha512-n/ghlvdhCdMI/hTcnn4qV57kQuV9OTsZzH1TTCVARANKhl6hXJqLKUkwX69ftMGpsbpt96SsDD8n8LD2d9+FRw== + dependencies: + jest-get-type "^28.0.2" + +"@jest/expect@^28.1.2": + version "28.1.2" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-28.1.2.tgz#0b25acedff46e1e1e5606285306c8a399c12534f" + integrity sha512-HBzyZBeFBiOelNbBKN0pilWbbrGvwDUwAqMC46NVJmWm8AVkuE58NbG1s7DR4cxFt4U5cVLxofAoHxgvC5MyOw== + dependencies: + expect "^28.1.1" + jest-snapshot "^28.1.2" + +"@jest/fake-timers@^28.1.2": + version "28.1.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-28.1.2.tgz#d49e8ee4e02ba85a6e844a52a5e7c59c23e3b76f" + integrity sha512-xSYEI7Y0D5FbZN2LsCUj/EKRR1zfQYmGuAUVh6xTqhx7V5JhjgMcK5Pa0iR6WIk0GXiHDe0Ke4A+yERKE9saqg== + dependencies: + "@jest/types" "^28.1.1" + "@sinonjs/fake-timers" "^9.1.2" + "@types/node" "*" + jest-message-util "^28.1.1" + jest-mock "^28.1.1" + jest-util "^28.1.1" + +"@jest/globals@^28.1.2": + version "28.1.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-28.1.2.tgz#92fab296e337c7309c25e4202fb724f62249d83f" + integrity sha512-cz0lkJVDOtDaYhvT3Fv2U1B6FtBnV+OpEyJCzTHM1fdoTsU4QNLAt/H4RkiwEUU+dL4g/MFsoTuHeT2pvbo4Hg== + dependencies: + "@jest/environment" "^28.1.2" + "@jest/expect" "^28.1.2" + "@jest/types" "^28.1.1" + +"@jest/reporters@^28.1.2": + version "28.1.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-28.1.2.tgz#0327be4ce4d0d9ae49e7908656f89669d0c2a260" + integrity sha512-/whGLhiwAqeCTmQEouSigUZJPVl7sW8V26EiboImL+UyXznnr1a03/YZ2BX8OlFw0n+Zlwu+EZAITZtaeRTxyA== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^28.1.1" + "@jest/test-result" "^28.1.1" + "@jest/transform" "^28.1.2" + "@jest/types" "^28.1.1" + "@jridgewell/trace-mapping" "^0.3.13" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^28.1.1" + jest-util "^28.1.1" + jest-worker "^28.1.1" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + terminal-link "^2.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^28.0.2": + version "28.0.2" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-28.0.2.tgz#08c30df6a8d07eafea0aef9fb222c5e26d72e613" + integrity sha512-YVDJZjd4izeTDkij00vHHAymNXQ6WWsdChFRK86qck6Jpr3DCL5W3Is3vslviRlP+bLuMYRLbdp98amMvqudhA== + dependencies: + "@sinclair/typebox" "^0.23.3" + +"@jest/source-map@^28.1.2": + version "28.1.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.1.2.tgz#7fe832b172b497d6663cdff6c13b0a920e139e24" + integrity sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww== + dependencies: + "@jridgewell/trace-mapping" "^0.3.13" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-28.1.1.tgz#c6f18d1bbb01aa88925dd687872a75f8414b317a" + integrity sha512-hPmkugBktqL6rRzwWAtp1JtYT4VHwv8OQ+9lE5Gymj6dHzubI/oJHMUpPOt8NrdVWSrz9S7bHjJUmv2ggFoUNQ== + dependencies: + "@jest/console" "^28.1.1" + "@jest/types" "^28.1.1" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-28.1.1.tgz#f594ee2331df75000afe0d1ae3237630ecec732e" + integrity sha512-nuL+dNSVMcWB7OOtgb0EGH5AjO4UBCt68SLP08rwmC+iRhyuJWS9MtZ/MpipxFwKAlHFftbMsydXqWre8B0+XA== + dependencies: + "@jest/test-result" "^28.1.1" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.1" + slash "^3.0.0" + +"@jest/transform@^28.1.2": + version "28.1.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.2.tgz#b367962c53fd53821269bde050ce373e111327c1" + integrity sha512-3o+lKF6iweLeJFHBlMJysdaPbpoMmtbHEFsjzSv37HIq/wWt5ijTeO2Yf7MO5yyczCopD507cNwNLeX8Y/CuIg== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^28.1.1" + "@jridgewell/trace-mapping" "^0.3.13" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.1" + jest-regex-util "^28.0.2" + jest-util "^28.1.1" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.1" + +"@jest/types@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.1.tgz#d059bbc80e6da6eda9f081f293299348bd78ee0b" + integrity sha512-vRXVqSg1VhDnB8bWcmvLzmg0Bt9CRKVgHPXqYwvWMX3TvAjeO+nRuK6+VdTKCtWOvYlmkF/HqNAL/z+N3B53Kw== + dependencies: + "@jest/schemas" "^28.0.2" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz#687cc2bbf243f4e9a868ecf2262318e2658873a1" + integrity sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w== + +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@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", "@nodelib/fs.stat@^2.0.2": + 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.3": + 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" + +"@sinclair/typebox@^0.23.3": + version "0.23.5" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.23.5.tgz#93f7b9f4e3285a7a9ade7557d9a8d36809cbc47d" + integrity sha512-AFBVi/iT4g20DHoujvMH1aEDn8fGJh4xsRGCP6d8RpLPMqsNPvW01Jcn0QysXTsg++/xj25NmJsGyH9xug/wKg== + +"@sinonjs/commons@^1.7.0": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^9.1.2": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" + integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@types/babel__core@^7.1.14": + version "7.1.19" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" + integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.17.1" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" + integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== + dependencies: + "@babel/types" "^7.3.0" + +"@types/clone-deep@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/clone-deep/-/clone-deep-4.0.1.tgz#7c488443ab9f571cd343d774551b78e9264ea990" + integrity sha512-bdkCSkyVHsgl3Goe1y16T9k6JuQx7SiDREkq728QjKmTZkGJZuS8R3gGcnGzVuGBP0mssKrzM/GlMOQxtip9cg== + +"@types/graceful-fs@^4.1.3": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^28.1.4": + version "28.1.4" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-28.1.4.tgz#a11ee6c8fd0b52c19c9c18138b78bbcc201dad5a" + integrity sha512-telv6G5N7zRJiLcI3Rs3o+ipZ28EnE+7EvF0pSrt2pZOMnAVI/f+6/LucDxOvcBcTeTL3JMF744BbVQAVBUQRA== + dependencies: + jest-matcher-utils "^28.0.0" + pretty-format "^28.0.0" + +"@types/json-schema@^7.0.9": + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + +"@types/node@*": + version "18.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a" + integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA== + +"@types/node@~16": + version "16.11.42" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.42.tgz#d2a75c58e9b0902b82dc54bd4c13f8ef12bd1020" + integrity sha512-iwLrPOopPy6V3E+1yHTpJea3bdsNso0b0utLOJJwaa/PLzqBt3GZl3stMcakc/gr89SfcNk2ki3z7Gvue9hYGQ== + +"@types/prettier@^2.1.5": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.3.tgz#68ada76827b0010d0db071f739314fa429943d0a" + integrity sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg== + +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/underscore@^1.11.4": + version "1.11.4" + resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.11.4.tgz#62e393f8bc4bd8a06154d110c7d042a93751def3" + integrity sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg== + +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^17.0.8": + version "17.0.10" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.10.tgz#591522fce85d8739bca7b8bb90d048e4478d186a" + integrity sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@~5.26": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.26.0.tgz#c1f98ccba9d345e38992975d3ca56ed6260643c2" + integrity sha512-oGCmo0PqnRZZndr+KwvvAUvD3kNE4AfyoGCwOZpoCncSh4MVD06JTE8XQa2u9u+NX5CsyZMBTEc2C72zx38eYA== + dependencies: + "@typescript-eslint/scope-manager" "5.26.0" + "@typescript-eslint/type-utils" "5.26.0" + "@typescript-eslint/utils" "5.26.0" + debug "^4.3.4" + functional-red-black-tree "^1.0.1" + ignore "^5.2.0" + regexpp "^3.2.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@~5.26": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.26.0.tgz#a61b14205fe2ab7533deb4d35e604add9a4ceee2" + integrity sha512-n/IzU87ttzIdnAH5vQ4BBDnLPly7rC5VnjN3m0xBG82HK6rhRxnCb3w/GyWbNDghPd+NktJqB/wl6+YkzZ5T5Q== + dependencies: + "@typescript-eslint/scope-manager" "5.26.0" + "@typescript-eslint/types" "5.26.0" + "@typescript-eslint/typescript-estree" "5.26.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.26.0.tgz#44209c7f649d1a120f0717e0e82da856e9871339" + integrity sha512-gVzTJUESuTwiju/7NiTb4c5oqod8xt5GhMbExKsCTp6adU3mya6AGJ4Pl9xC7x2DX9UYFsjImC0mA62BCY22Iw== + dependencies: + "@typescript-eslint/types" "5.26.0" + "@typescript-eslint/visitor-keys" "5.26.0" + +"@typescript-eslint/scope-manager@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.30.0.tgz#bf585ee801ab4ad84db2f840174e171a6bb002c7" + integrity sha512-3TZxvlQcK5fhTBw5solQucWSJvonXf5yua5nx8OqK94hxdrT7/6W3/CS42MLd/f1BmlmmbGEgQcTHHCktUX5bQ== + dependencies: + "@typescript-eslint/types" "5.30.0" + "@typescript-eslint/visitor-keys" "5.30.0" + +"@typescript-eslint/type-utils@5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.26.0.tgz#937dee97702361744a3815c58991acf078230013" + integrity sha512-7ccbUVWGLmcRDSA1+ADkDBl5fP87EJt0fnijsMFTVHXKGduYMgienC/i3QwoVhDADUAPoytgjbZbCOMj4TY55A== + dependencies: + "@typescript-eslint/utils" "5.26.0" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.26.0.tgz#cb204bb154d3c103d9cc4d225f311b08219469f3" + integrity sha512-8794JZFE1RN4XaExLWLI2oSXsVImNkl79PzTOOWt9h0UHROwJedNOD2IJyfL0NbddFllcktGIO2aOu10avQQyA== + +"@typescript-eslint/types@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.30.0.tgz#db7d81d585a3da3801432a9c1d2fafbff125e110" + integrity sha512-vfqcBrsRNWw/LBXyncMF/KrUTYYzzygCSsVqlZ1qGu1QtGs6vMkt3US0VNSQ05grXi5Yadp3qv5XZdYLjpp8ag== + +"@typescript-eslint/typescript-estree@5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.26.0.tgz#16cbceedb0011c2ed4f607255f3ee1e6e43b88c3" + integrity sha512-EyGpw6eQDsfD6jIqmXP3rU5oHScZ51tL/cZgFbFBvWuCwrIptl+oueUZzSmLtxFuSOQ9vDcJIs+279gnJkfd1w== + dependencies: + "@typescript-eslint/types" "5.26.0" + "@typescript-eslint/visitor-keys" "5.26.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/typescript-estree@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.0.tgz#4565ee8a6d2ac368996e20b2344ea0eab1a8f0bb" + integrity sha512-hDEawogreZB4n1zoqcrrtg/wPyyiCxmhPLpZ6kmWfKF5M5G0clRLaEexpuWr31fZ42F96SlD/5xCt1bT5Qm4Nw== + dependencies: + "@typescript-eslint/types" "5.30.0" + "@typescript-eslint/visitor-keys" "5.30.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.26.0.tgz#896b8480eb124096e99c8b240460bb4298afcfb4" + integrity sha512-PJFwcTq2Pt4AMOKfe3zQOdez6InIDOjUJJD3v3LyEtxHGVVRK3Vo7Dd923t/4M9hSH2q2CLvcTdxlLPjcIk3eg== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.26.0" + "@typescript-eslint/types" "5.26.0" + "@typescript-eslint/typescript-estree" "5.26.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/utils@^5.10.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.30.0.tgz#1dac771fead5eab40d31860716de219356f5f754" + integrity sha512-0bIgOgZflLKIcZsWvfklsaQTM3ZUbmtH0rJ1hKyV3raoUYyeZwcjQ8ZUJTzS7KnhNcsVT1Rxs7zeeMHEhGlltw== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.30.0" + "@typescript-eslint/types" "5.30.0" + "@typescript-eslint/typescript-estree" "5.30.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/visitor-keys@5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.26.0.tgz#7195f756e367f789c0e83035297c45b417b57f57" + integrity sha512-wei+ffqHanYDOQgg/fS6Hcar6wAWv0CUPQ3TZzOWd2BLfgP539rb49bwua8WRAs7R6kOSLn82rfEu2ro6Llt8Q== + dependencies: + "@typescript-eslint/types" "5.26.0" + eslint-visitor-keys "^3.3.0" + +"@typescript-eslint/visitor-keys@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.0.tgz#07721d23daca2ec4c2da7f1e660d41cd78bacac3" + integrity sha512-6WcIeRk2DQ3pHKxU1Ni0qMXJkjO/zLjBymlYBy/53qxe7yjEFSvzKLDToJjURUhSl2Fzhkl4SMXQoETauF74cw== + dependencies: + "@typescript-eslint/types" "5.30.0" + eslint-visitor-keys "^3.3.0" + +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.7.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + +ajv@^6.10.0, 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-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +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@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, 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" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +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== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +babel-jest@^28.1.2: + version "28.1.2" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.2.tgz#2b37fb81439f14d34d8b2cc4a4bd7efabf9acbfe" + integrity sha512-pfmoo6sh4L/+5/G2OOfQrGJgvH7fTa1oChnuYH2G/6gA+JwDvO8PELwvwnofKBMNrQsam0Wy/Rw+QSrBNewq2Q== + dependencies: + "@jest/transform" "^28.1.2" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^28.1.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.1.tgz#5e055cdcc47894f28341f87f5e35aad2df680b11" + integrity sha512-NovGCy5Hn25uMJSAU8FaHqzs13cFoOI4lhIujiepssjCKRsAo3TA734RDWSGxuFTsUJXerYOqQQodlxgmtqbzw== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-28.1.1.tgz#5b6e5e69f963eb2d70f739c607b8f723c0ee75e4" + integrity sha512-FCq9Oud0ReTeWtcneYf/48981aTfXYuB9gbU4rBNNJVBSQ6ssv7E6v/qvbBxtOWwZFXjLZwpg+W3q7J6vhH25g== + dependencies: + babel-plugin-jest-hoist "^28.1.1" + babel-preset-current-node-syntax "^1.0.0" + +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" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.20.2: + version "4.21.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.1.tgz#c9b9b0a54c7607e8dc3e01a0d311727188011a00" + integrity sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ== + dependencies: + caniuse-lite "^1.0.30001359" + electron-to-chromium "^1.4.172" + node-releases "^2.0.5" + update-browserslist-db "^1.0.4" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +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== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001359: + version "1.0.30001361" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001361.tgz#ba2adb2527566fb96f3ac7c67698ae7fc495a28d" + integrity sha512-ybhCrjNtkFji1/Wto6SSJKkWk6kZgVQsDq5QI83SafsF6FXv2JB4df9eEdH6g8sdGgqTXrFLjAxqBGgYoU3azQ== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +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" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.2.tgz#6d2967ffa407466481c6c90b6e16b3098f080128" + integrity sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg== + +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +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.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +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== + +convert-hrtime@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/convert-hrtime/-/convert-hrtime-5.0.0.tgz#f2131236d4598b95de856926a67100a0a97e9fa3" + integrity sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg== + +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +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.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: + 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" + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + +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== + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6" + integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +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" + +electron-to-chromium@^1.4.172: + version "1.4.176" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.176.tgz#61ab2a1de3b5072ee31881a937c08ac6780d1cfa" + integrity sha512-92JdgyRlcNDwuy75MjuFSb3clt6DGJ2IXSpg0MCjKd3JV9eSmuUAIyWiGAp/EtT0z2D4rqbYqThQLV90maH3Zw== + +emittery@^0.10.2: + version "0.10.2" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" + integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +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@~8.5: + version "8.5.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" + integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== + +eslint-plugin-jest@~26.2: + version "26.2.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-26.2.2.tgz#74e000544259f1ef0462a609a3fc9e5da3768f6c" + integrity sha512-etSFZ8VIFX470aA6kTqDPhIq7YWe0tjBcboFNV3WeiC18PJ/AVonGhuTwlmuz2fBkH8FJHA7JQ4k7GsQIj1Gew== + dependencies: + "@typescript-eslint/utils" "^5.10.0" + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint@~8.16: + version "8.16.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.16.0.tgz#6d936e2d524599f2a86c708483b4c372c5d3bbae" + integrity sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA== + dependencies: + "@eslint/eslintrc" "^1.3.0" + "@humanwhocodes/config-array" "^0.9.2" + ajv "^6.10.0" + 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.1.1" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.3.0" + espree "^9.3.2" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^6.0.1" + globals "^13.15.0" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + 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.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^9.3.2: + version "9.3.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596" + integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA== + dependencies: + acorn "^8.7.1" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.3.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + 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@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +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.0.0: + 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" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-28.1.1.tgz#ca6fff65f6517cf7220c2e805a49c19aea30b420" + integrity sha512-/AANEwGL0tWBwzLNOvO0yUdy2D52jVdNXppOqswC49sxMN2cPWsGCQdzuIf9tj6hHoBQzNvx75JUYuQAckPo3w== + dependencies: + "@jest/expect-utils" "^28.1.1" + jest-get-type "^28.0.2" + jest-matcher-utils "^28.1.1" + jest-message-util "^28.1.1" + jest-util "^28.1.1" + +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-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@2.x, 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.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +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" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.6.tgz#022e9218c637f9f3fc9c35ab9c9193f05add60b2" + integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ== + +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== + +fsevents@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +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@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1: + 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, glob@^7.1.4: + 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@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.15.0: + version "13.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" + integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +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== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +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: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +import-fresh@^3.0.0, 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" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.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-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" + integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== + dependencies: + has "^1.0.3" + +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-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, 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-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +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== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" + integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.4" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" + integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-28.0.2.tgz#7d7810660a5bd043af9e9cfbe4d58adb05e91531" + integrity sha512-QX9u+5I2s54ZnGoMEjiM2WeBvJR2J7w/8ZUmH2um/WLAuGAYFQcsVXY9+1YL6k0H/AGUdH8pXUAv6erDqEsvIA== + dependencies: + execa "^5.0.0" + throat "^6.0.1" + +jest-circus@^28.1.2: + version "28.1.2" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-28.1.2.tgz#0d5a5623eccb244efe87d1edc365696e4fcf80ce" + integrity sha512-E2vdPIJG5/69EMpslFhaA46WkcrN74LI5V/cSJ59L7uS8UNoXbzTxmwhpi9XrIL3zqvMt5T0pl5k2l2u2GwBNQ== + dependencies: + "@jest/environment" "^28.1.2" + "@jest/expect" "^28.1.2" + "@jest/test-result" "^28.1.1" + "@jest/types" "^28.1.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + is-generator-fn "^2.0.0" + jest-each "^28.1.1" + jest-matcher-utils "^28.1.1" + jest-message-util "^28.1.1" + jest-runtime "^28.1.2" + jest-snapshot "^28.1.2" + jest-util "^28.1.1" + pretty-format "^28.1.1" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + +jest-cli@^28.1.2: + version "28.1.2" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-28.1.2.tgz#b89012e5bad14135e71b1628b85475d3773a1bbc" + integrity sha512-l6eoi5Do/IJUXAFL9qRmDiFpBeEJAnjJb1dcd9i/VWfVWbp3mJhuH50dNtX67Ali4Ecvt4eBkWb4hXhPHkAZTw== + dependencies: + "@jest/core" "^28.1.2" + "@jest/test-result" "^28.1.1" + "@jest/types" "^28.1.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^28.1.2" + jest-util "^28.1.1" + jest-validate "^28.1.1" + prompts "^2.0.1" + yargs "^17.3.1" + +jest-config@^28.1.2: + version "28.1.2" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-28.1.2.tgz#ba00ad30caf62286c86e7c1099e915218a0ac8c6" + integrity sha512-g6EfeRqddVbjPVBVY4JWpUY4IvQoFRIZcv4V36QkqzE0IGhEC/VkugFeBMAeUE7PRgC8KJF0yvJNDeQRbamEVA== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^28.1.1" + "@jest/types" "^28.1.1" + babel-jest "^28.1.2" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^28.1.2" + jest-environment-node "^28.1.2" + jest-get-type "^28.0.2" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.1" + jest-runner "^28.1.2" + jest-util "^28.1.1" + jest-validate "^28.1.1" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^28.1.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-28.1.1.tgz#1a3eedfd81ae79810931c63a1d0f201b9120106c" + integrity sha512-/MUUxeR2fHbqHoMMiffe/Afm+U8U4olFRJ0hiVG2lZatPJcnGxx292ustVu7bULhjV65IYMxRdploAKLbcrsyg== + dependencies: + chalk "^4.0.0" + diff-sequences "^28.1.1" + jest-get-type "^28.0.2" + pretty-format "^28.1.1" + +jest-docblock@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8" + integrity sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA== + dependencies: + detect-newline "^3.0.0" + +jest-each@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-28.1.1.tgz#ba5238dacf4f31d9fe23ddc2c44c01e7c23885c4" + integrity sha512-A042rqh17ZvEhRceDMi784ppoXR7MWGDEKTXEZXb4svt0eShMZvijGxzKsx+yIjeE8QYmHPrnHiTSQVhN4nqaw== + dependencies: + "@jest/types" "^28.1.1" + chalk "^4.0.0" + jest-get-type "^28.0.2" + jest-util "^28.1.1" + pretty-format "^28.1.1" + +jest-environment-node@^28.1.2: + version "28.1.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-28.1.2.tgz#3e2eb47f6d173b0648d5f7c717cb1c26651d5c8a" + integrity sha512-oYsZz9Qw27XKmOgTtnl0jW7VplJkN2oeof+SwAwKFQacq3CLlG9u4kTGuuLWfvu3J7bVutWlrbEQMOCL/jughw== + dependencies: + "@jest/environment" "^28.1.2" + "@jest/fake-timers" "^28.1.2" + "@jest/types" "^28.1.1" + "@types/node" "*" + jest-mock "^28.1.1" + jest-util "^28.1.1" + +jest-get-type@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203" + integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== + +jest-haste-map@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.1.tgz#471685f1acd365a9394745bb97c8fc16289adca3" + integrity sha512-ZrRSE2o3Ezh7sb1KmeLEZRZ4mgufbrMwolcFHNRSjKZhpLa8TdooXOOFlSwoUzlbVs1t0l7upVRW2K7RWGHzbQ== + dependencies: + "@jest/types" "^28.1.1" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^28.0.2" + jest-util "^28.1.1" + jest-worker "^28.1.1" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.1.tgz#537f37afd610a4b3f4cab15e06baf60484548efb" + integrity sha512-4jvs8V8kLbAaotE+wFR7vfUGf603cwYtFf1/PYEsyX2BAjSzj8hQSVTP6OWzseTl0xL6dyHuKs2JAks7Pfubmw== + dependencies: + jest-get-type "^28.0.2" + pretty-format "^28.1.1" + +jest-matcher-utils@^28.0.0, jest-matcher-utils@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-28.1.1.tgz#a7c4653c2b782ec96796eb3088060720f1e29304" + integrity sha512-NPJPRWrbmR2nAJ+1nmnfcKKzSwgfaciCCrYZzVnNoxVoyusYWIjkBMNvu0RHJe7dNj4hH3uZOPZsQA+xAYWqsw== + dependencies: + chalk "^4.0.0" + jest-diff "^28.1.1" + jest-get-type "^28.0.2" + pretty-format "^28.1.1" + +jest-message-util@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.1.tgz#60aa0b475cfc08c8a9363ed2fb9108514dd9ab89" + integrity sha512-xoDOOT66fLfmTRiqkoLIU7v42mal/SqwDKvfmfiWAdJMSJiU+ozgluO7KbvoAgiwIrrGZsV7viETjc8GNrA/IQ== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^28.1.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^28.1.1" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-28.1.1.tgz#37903d269427fa1ef5b2447be874e1c62a39a371" + integrity sha512-bDCb0FjfsmKweAvE09dZT59IMkzgN0fYBH6t5S45NoJfd2DHkS3ySG2K+hucortryhO3fVuXdlxWcbtIuV/Skw== + dependencies: + "@jest/types" "^28.1.1" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" + integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== + +jest-resolve-dependencies@^28.1.2: + version "28.1.2" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.2.tgz#ca528858e0c6642d5a1dda8fc7cda10230c275bc" + integrity sha512-OXw4vbOZuyRTBi3tapWBqdyodU+T33ww5cPZORuTWkg+Y8lmsxQlVu3MWtJh6NMlKRTHQetF96yGPv01Ye7Mbg== + dependencies: + jest-regex-util "^28.0.2" + jest-snapshot "^28.1.2" + +jest-resolve@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-28.1.1.tgz#bc2eaf384abdcc1aaf3ba7c50d1adf01e59095e5" + integrity sha512-/d1UbyUkf9nvsgdBildLe6LAD4DalgkgZcKd0nZ8XUGPyA/7fsnaQIlKVnDiuUXv/IeZhPEDrRJubVSulxrShA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.1" + jest-pnp-resolver "^1.2.2" + jest-util "^28.1.1" + jest-validate "^28.1.1" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^28.1.2: + version "28.1.2" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-28.1.2.tgz#f293409592a62234285a71237e38499a3554e350" + integrity sha512-6/k3DlAsAEr5VcptCMdhtRhOoYClZQmxnVMZvZ/quvPGRpN7OBQYPIC32tWSgOnbgqLXNs5RAniC+nkdFZpD4A== + dependencies: + "@jest/console" "^28.1.1" + "@jest/environment" "^28.1.2" + "@jest/test-result" "^28.1.1" + "@jest/transform" "^28.1.2" + "@jest/types" "^28.1.1" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.10.2" + graceful-fs "^4.2.9" + jest-docblock "^28.1.1" + jest-environment-node "^28.1.2" + jest-haste-map "^28.1.1" + jest-leak-detector "^28.1.1" + jest-message-util "^28.1.1" + jest-resolve "^28.1.1" + jest-runtime "^28.1.2" + jest-util "^28.1.1" + jest-watcher "^28.1.1" + jest-worker "^28.1.1" + source-map-support "0.5.13" + throat "^6.0.1" + +jest-runtime@^28.1.2: + version "28.1.2" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-28.1.2.tgz#d68f34f814a848555a345ceda23289f14d59a688" + integrity sha512-i4w93OsWzLOeMXSi9epmakb2+3z0AchZtUQVF1hesBmcQQy4vtaql5YdVe9KexdJaVRyPDw8DoBR0j3lYsZVYw== + dependencies: + "@jest/environment" "^28.1.2" + "@jest/fake-timers" "^28.1.2" + "@jest/globals" "^28.1.2" + "@jest/source-map" "^28.1.2" + "@jest/test-result" "^28.1.1" + "@jest/transform" "^28.1.2" + "@jest/types" "^28.1.1" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.1" + jest-message-util "^28.1.1" + jest-mock "^28.1.1" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.1" + jest-snapshot "^28.1.2" + jest-util "^28.1.1" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^28.1.2: + version "28.1.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-28.1.2.tgz#93d31b87b11b384f5946fe0767541496135f8d52" + integrity sha512-wzrieFttZYfLvrCVRJxX+jwML2YTArOUqFpCoSVy1QUapx+LlV9uLbV/mMEhYj4t7aMeE9aSQFHSvV/oNoDAMA== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^28.1.1" + "@jest/transform" "^28.1.2" + "@jest/types" "^28.1.1" + "@types/babel__traverse" "^7.0.6" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^28.1.1" + graceful-fs "^4.2.9" + jest-diff "^28.1.1" + jest-get-type "^28.0.2" + jest-haste-map "^28.1.1" + jest-matcher-utils "^28.1.1" + jest-message-util "^28.1.1" + jest-util "^28.1.1" + natural-compare "^1.4.0" + pretty-format "^28.1.1" + semver "^7.3.5" + +jest-util@^28.0.0, jest-util@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.1.tgz#ff39e436a1aca397c0ab998db5a51ae2b7080d05" + integrity sha512-FktOu7ca1DZSyhPAxgxB6hfh2+9zMoJ7aEQA759Z6p45NuO8mWcqujH+UdHlCm/V6JTWwDztM2ITCzU1ijJAfw== + dependencies: + "@jest/types" "^28.1.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-28.1.1.tgz#59b7b339b3c85b5144bd0c06ad3600f503a4acc8" + integrity sha512-Kpf6gcClqFCIZ4ti5++XemYJWUPCFUW+N2gknn+KgnDf549iLul3cBuKVe1YcWRlaF8tZV8eJCap0eECOEE3Ug== + dependencies: + "@jest/types" "^28.1.1" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^28.0.2" + leven "^3.1.0" + pretty-format "^28.1.1" + +jest-watcher@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-28.1.1.tgz#533597fb3bfefd52b5cd115cd916cffd237fb60c" + integrity sha512-RQIpeZ8EIJMxbQrXpJQYIIlubBnB9imEHsxxE41f54ZwcqWLysL/A0ZcdMirf+XsMn3xfphVQVV4EW0/p7i7Ug== + dependencies: + "@jest/test-result" "^28.1.1" + "@jest/types" "^28.1.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.10.2" + jest-util "^28.1.1" + string-length "^4.0.1" + +jest-worker@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.1.tgz#3480c73247171dfd01eda77200f0063ab6a3bf28" + integrity sha512-Au7slXB08C6h+xbJPp7VIb6U0XX5Kc9uel/WFc6/rcTzGiaVCBRngBExSYuXSLFPULPSYU3cJ3ybS988lNFQhQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^28.1.1: + version "28.1.2" + resolved "https://registry.yarnpkg.com/jest/-/jest-28.1.2.tgz#451ff24081ce31ca00b07b60c61add13aa96f8eb" + integrity sha512-Tuf05DwLeCh2cfWCQbcz9UxldoDyiR1E9Igaei5khjonKncYdc6LDfynKCEWozK0oLE3GD+xKAo2u8x/0s6GOg== + dependencies: + "@jest/core" "^28.1.2" + "@jest/types" "^28.1.1" + import-local "^3.0.2" + jest-cli "^28.1.2" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +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" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +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== + +json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +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" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +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" + +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +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== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +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.4, 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" + +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== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" + integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +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.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +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" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +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== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +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@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +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@~2.6: + version "2.6.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" + integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== + +pretty-format@^28.0.0, pretty-format@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-28.1.1.tgz#f731530394e0f7fcd95aba6b43c50e02d86b95cb" + integrity sha512-wwJbVTGFHeucr5Jw2bQ9P+VYHyLdAqedFLEkdQUVaBF/eiidDwH5OpilINq4mEfhbCjLnirt6HTTDhv1HaTIQw== + dependencies: + "@jest/schemas" "^28.0.2" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +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== + +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +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== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + +resolve@^1.20.0: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +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.0, rimraf@^3.0.2, rimraf@~3.0: + 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" + +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +semver@7.x, semver@^7.3.5, semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +semver@^6.0.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +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, signal-exit@^3.0.7: + 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== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@^0.5.21: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, 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-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +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.0, 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@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, 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" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +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== + +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== + +time-span@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/time-span/-/time-span-5.1.0.tgz#80c76cf5a0ca28e0842d3f10a4e99034ce94b90d" + integrity sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA== + dependencies: + convert-hrtime "^5.0.0" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-jest@^28.0.5: + version "28.0.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-28.0.5.tgz#31776f768fba6dfc8c061d488840ed0c8eeac8b9" + integrity sha512-Sx9FyP9pCY7pUzQpy4FgRZf2bhHY3za576HMKJFs+OnQ9jS96Du5vNsDKkyedQkik+sEabbKAnCliv9BEsHZgQ== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^28.0.0" + json5 "^2.2.1" + lodash.memoize "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "^21.0.1" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@~2.4: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +tsutils@^3.21.0, tsutils@~3.21: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +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-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +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== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typescript@~4.7: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== + +underscore@^1.13.4: + version "1.13.4" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.4.tgz#7886b46bbdf07f768e0052f1828e1dcab40c0dee" + integrity sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ== + +update-browserslist-db@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz#dbfc5a789caa26b1db8990796c2c8ebbce304824" + integrity sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +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" + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +v8-to-istanbul@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz#b6f994b0b5d4ef255e17a0d17dc444a9f5132fa4" + integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +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" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.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== + +write-file-atomic@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.1.tgz#9faa33a964c1c85ff6f849b80b42a88c2c537c8f" + integrity sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +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== + +yargs-parser@^21.0.0, yargs-parser@^21.0.1: + version "21.0.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" + integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== + +yargs@^17.3.1: + version "17.5.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e" + integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" diff --git a/sonar-project.properties b/sonar-project.properties index f9cf29932d..89902eae4c 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -11,10 +11,13 @@ sonar.organization=cosmos # Encoding of the source code. Default is default system encoding #sonar.sourceEncoding=UTF-8 +# All golang artifacts sonar.sources=. -sonar.exclusions=**/*_test.go,**/vendor/**,**/testdata/*,**/*.pb.go,**/*.pb.gw.go,**/vue/**/*,**/integration-tests/**/* - +# Do not calculate coverage metrics for statements in these files +sonar.exclusions=**/vendor/**,**/*.pb.go,**/*.pb.gw.go,proto,**/*_test.go,integration-tests/**,testutil/**,diff-tests/** sonar.tests=. +# Run unit and e2e tests, but not integration tests sonar.test.inclusions=**/*_test.go +# Do not run any vendor tests sonar.test.exclusions=**/vendor/** sonar.go.coverage.reportPaths=coverage.out diff --git a/testutil/simibc/ordered_link.go b/testutil/simibc/ordered_link.go new file mode 100644 index 0000000000..85beee8f75 --- /dev/null +++ b/testutil/simibc/ordered_link.go @@ -0,0 +1,93 @@ +package simibc + +import channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + +// Ack represents and ack committed to block state +type Ack struct { + Ack []byte + Packet channeltypes.Packet + Commits int +} + +// Packet represents a packet committed to block state +type Packet struct { + Packet channeltypes.Packet + Commits int +} + +// OrderedLink contains outboxes of packets and acknowledgements and +// allows fine-grained control over delivery of acks and packets +// to mimic a real relaying relationship between two chains. +type OrderedLink struct { + OutboxPackets map[string][]Packet + OutboxAcks map[string][]Ack +} + +// MakeOrderedLink creates a new empty network link. +func MakeOrderedLink() OrderedLink { + return OrderedLink{ + OutboxPackets: map[string][]Packet{}, + OutboxAcks: map[string][]Ack{}, + } +} + +// AddPacket adds an outbound packet from the sender to the counterparty. +func (n OrderedLink) AddPacket(sender string, packet channeltypes.Packet) { + n.OutboxPackets[sender] = append(n.OutboxPackets[sender], Packet{packet, 0}) +} + +// AddAck adds an outbound ack, for future delivery to the sender of the packet +// being acked. +func (n OrderedLink) AddAck(sender string, ack []byte, packet channeltypes.Packet) { + n.OutboxAcks[sender] = append(n.OutboxAcks[sender], Ack{ack, packet, 0}) +} + +// ConsumePackets returns and internally delets all packets with 2 or more commits. +func (n OrderedLink) ConsumePackets(sender string, num int) []Packet { + ret := []Packet{} + sz := len(n.OutboxPackets[sender]) + if sz < num { + num = sz + } + for _, p := range n.OutboxPackets[sender][:num] { + if 1 < p.Commits { + ret = append(ret, p) + } else { + break + } + } + n.OutboxPackets[sender] = n.OutboxPackets[sender][len(ret):] + return ret +} + +// ConsumeAcks returns and internally deletes all acks with 2 or more commits. +func (n OrderedLink) ConsumeAcks(sender string, num int) []Ack { + ret := []Ack{} + sz := len(n.OutboxAcks[sender]) + if sz < num { + num = sz + } + for _, a := range n.OutboxAcks[sender][:num] { + if 1 < a.Commits { + ret = append(ret, a) + } else { + break + } + } + n.OutboxAcks[sender] = n.OutboxAcks[sender][len(ret):] + return ret +} + +// Commit marks a block commit for a chain and will make its outbound +// packets visible as per the functioning of ibc. In practice it takes +// 2 commits for a packet to become available for delivery, due to the +// need for the light client to have block h + 1 for a packet in block +// h. +func (n OrderedLink) Commit(sender string) { + for i := range n.OutboxPackets[sender] { + n.OutboxPackets[sender][i].Commits += 1 + } + for i := range n.OutboxAcks[sender] { + n.OutboxAcks[sender][i].Commits += 1 + } +} diff --git a/testutil/simibc/relay_util.go b/testutil/simibc/relay_util.go new file mode 100644 index 0000000000..535ae948fc --- /dev/null +++ b/testutil/simibc/relay_util.go @@ -0,0 +1,166 @@ +package simibc + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v3/modules/core/24-host" + ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" + "github.com/cosmos/ibc-go/v3/testing/simapp" + "github.com/stretchr/testify/require" + tmtypes "github.com/tendermint/tendermint/types" +) + +// UpdateReceiverClient is used to send a header to the receiving endpoint and update +// the client of the respective chain. +func UpdateReceiverClient(sender *ibctesting.Endpoint, receiver *ibctesting.Endpoint, header *ibctmtypes.Header) (err error) { + + header, err = constructTMHeader(receiver.Chain, header, sender.Chain, receiver.ClientID, clienttypes.ZeroHeight()) + + if err != nil { + return err + } + + msg, err := clienttypes.NewMsgUpdateClient( + receiver.ClientID, header, + receiver.Chain.SenderAccount.GetAddress().String(), + ) + + require.NoError(receiver.Chain.T, err) + + _, _, err = simapp.SignAndDeliver( + receiver.Chain.T, + receiver.Chain.TxConfig, + receiver.Chain.App.GetBaseApp(), + receiver.Chain.GetContext().BlockHeader(), + []sdk.Msg{msg}, + receiver.Chain.ChainID, + []uint64{receiver.Chain.SenderAccount.GetAccountNumber()}, + []uint64{receiver.Chain.SenderAccount.GetSequence()}, + true, true, receiver.Chain.SenderPrivKey, + ) + + if err != nil { + return err + } + + err = receiver.Chain.SenderAccount.SetSequence(receiver.Chain.SenderAccount.GetSequence() + 1) + + if err != nil { + return err + } + + return nil +} + +// Try to receive a packet on receiver. Returns ack. +func TryRecvPacket(sender *ibctesting.Endpoint, receiver *ibctesting.Endpoint, packet channeltypes.Packet) (ack []byte, err error) { + packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + proof, proofHeight := sender.Chain.QueryProof(packetKey) + + RPmsg := channeltypes.NewMsgRecvPacket(packet, proof, proofHeight, receiver.Chain.SenderAccount.GetAddress().String()) + + _, resWithAck, err := simapp.SignAndDeliver( + receiver.Chain.T, + receiver.Chain.TxConfig, + receiver.Chain.App.GetBaseApp(), + receiver.Chain.GetContext().BlockHeader(), + []sdk.Msg{RPmsg}, + receiver.Chain.ChainID, + []uint64{receiver.Chain.SenderAccount.GetAccountNumber()}, + []uint64{receiver.Chain.SenderAccount.GetSequence()}, + true, true, receiver.Chain.SenderPrivKey, + ) + + if err != nil { + return nil, err + } + + err = receiver.Chain.SenderAccount.SetSequence(receiver.Chain.SenderAccount.GetSequence() + 1) + + if err != nil { + return nil, err + } + + ack, err = ibctesting.ParseAckFromEvents(resWithAck.GetEvents()) + + if err != nil { + return nil, err + } + + return ack, nil +} + +// Try to receive an ack on receiver. +func TryRecvAck(sender *ibctesting.Endpoint, receiver *ibctesting.Endpoint, packet channeltypes.Packet, ack []byte) (err error) { + p := packet + packetKey := host.PacketAcknowledgementKey(p.GetDestPort(), p.GetDestChannel(), p.GetSequence()) + proof, proofHeight := sender.Chain.QueryProof(packetKey) + + ackMsg := channeltypes.NewMsgAcknowledgement(p, ack, proof, proofHeight, receiver.Chain.SenderAccount.GetAddress().String()) + + _, _, err = simapp.SignAndDeliver( + receiver.Chain.T, + receiver.Chain.TxConfig, + receiver.Chain.App.GetBaseApp(), + receiver.Chain.GetContext().BlockHeader(), + []sdk.Msg{ackMsg}, + receiver.Chain.ChainID, + []uint64{receiver.Chain.SenderAccount.GetAccountNumber()}, + []uint64{receiver.Chain.SenderAccount.GetSequence()}, + true, true, receiver.Chain.SenderPrivKey, + ) + + if err != nil { + return err + } + + err = receiver.Chain.SenderAccount.SetSequence(receiver.Chain.SenderAccount.GetSequence() + 1) + + if err != nil { + return err + } + + return nil +} + +// constructTMHeader will augment a valid 07-tendermint Header with data needed to update +// light client. +func constructTMHeader(chain *ibctesting.TestChain, header *ibctmtypes.Header, counterparty *ibctesting.TestChain, clientID string, trustedHeight clienttypes.Height) (*ibctmtypes.Header, error) { + // Relayer must query for LatestHeight on client to get TrustedHeight if the trusted height is not set + if trustedHeight.IsZero() { + trustedHeight = chain.GetClientState(clientID).GetLatestHeight().(clienttypes.Height) + } + var ( + tmTrustedVals *tmtypes.ValidatorSet + ok bool + ) + // Once we get TrustedHeight from client, we must query the validators from the counterparty chain + // If the LatestHeight == LastHeader.Height, then TrustedValidators are current validators + // If LatestHeight < LastHeader.Height, we can query the historical validator set from HistoricalInfo + if trustedHeight == counterparty.LastHeader.GetHeight() { + tmTrustedVals = counterparty.Vals + } else { + // NOTE: We need to get validators from counterparty at height: trustedHeight+1 + // since the last trusted validators for a header at height h + // is the NextValidators at h+1 committed to in header h by + // NextValidatorsHash + tmTrustedVals, ok = counterparty.GetValsAtHeight(int64(trustedHeight.RevisionHeight + 1)) + if !ok { + return nil, sdkerrors.Wrapf(ibctmtypes.ErrInvalidHeaderHeight, "could not retrieve trusted validators at trustedHeight: %d", trustedHeight) + } + } + // inject trusted fields into last header + // for now assume revision number is 0 + header.TrustedHeight = trustedHeight + + trustedVals, err := tmTrustedVals.ToProto() + if err != nil { + return nil, err + } + header.TrustedValidators = trustedVals + + return header, nil +} diff --git a/testutil/simibc/relayed_path.go b/testutil/simibc/relayed_path.go new file mode 100644 index 0000000000..cd4822577d --- /dev/null +++ b/testutil/simibc/relayed_path.go @@ -0,0 +1,155 @@ +package simibc + +import ( + "time" + + "testing" + + channelkeeper "github.com/cosmos/ibc-go/v3/modules/core/04-channel/keeper" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" + abci "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) + +// RelayedPath augments ibctesting.Path, giving fine-grained control +// over delivery of client updates, packet and ack delivery, and +// chain time and height progression. +type RelayedPath struct { + t *testing.T + path *ibctesting.Path + clientHeaders map[string][]*ibctmtypes.Header + Link OrderedLink +} + +// MakeRelayedPath returns an initialized RelayedPath +func MakeRelayedPath(t *testing.T, path *ibctesting.Path) RelayedPath { + return RelayedPath{ + t: t, + clientHeaders: map[string][]*ibctmtypes.Header{}, + path: path, + Link: MakeOrderedLink(), + } +} + +// Chain returns the chain with ChainID +func (f *RelayedPath) Chain(chainID string) *ibctesting.TestChain { + if f.path.EndpointA.Chain.ChainID == chainID { + return f.path.EndpointA.Chain + } + if f.path.EndpointB.Chain.ChainID == chainID { + return f.path.EndpointB.Chain + } + f.t.Fatal("chain") + return nil +} + +// UpdateClient will update the chain light client with +// each header added to the counterparty chain since the last +// call. +func (f *RelayedPath) UpdateClient(chainID string) { + for _, header := range f.clientHeaders[f.other(chainID)] { + err := UpdateReceiverClient(f.endpoint(f.other(chainID)), f.endpoint(chainID), header) + if err != nil { + f.t.Fatal("UpdateClient") + } + } + f.clientHeaders[f.other(chainID)] = []*ibctmtypes.Header{} +} + +// DeliverPackets delivers packets to chain +// A real relayer will relay packets from one chain to another chain +// in two steps. First it will observe sufficiently committed outbound +// packets on the sender chain. Second, it will submit transactions +// containing those packets to the receiver chain. +// This method simulates the second step: sufficiently committed +// packets that have been already added to the OrderedLink will be +// delivered. It is necessary to add outbound packets to the link +// separately. +func (f *RelayedPath) DeliverPackets(chainID string, num int) { + for _, p := range f.Link.ConsumePackets(f.other(chainID), num) { + ack, err := TryRecvPacket(f.endpoint(f.other(chainID)), f.endpoint(chainID), p.Packet) + if err != nil { + f.t.Fatal("deliver") + } + f.Link.AddAck(chainID, ack, p.Packet) + } +} + +// DeliverAcks delivers acks to chain +func (f *RelayedPath) DeliverAcks(chainID string, num int) { + for _, ack := range f.Link.ConsumeAcks(f.other(chainID), num) { + err := TryRecvAck(f.endpoint(f.other(chainID)), f.endpoint(chainID), ack.Packet, ack.Ack) + if err != nil { + f.t.Fatal("deliverAcks") + } + } +} + +// EndAndBeginBlock calls EndBlock and commits block state, and then increments time and calls +// BeginBlock, for the chain. preCommitCallback is called after EndBlock and before Commit, +// allowing access to the sdk.Context after EndBlock. +func (f *RelayedPath) EndAndBeginBlock(chainID string, dt time.Duration, preCommitCallback func()) { + c := f.Chain(chainID) + + ebRes := c.App.EndBlock(abci.RequestEndBlock{Height: c.CurrentHeader.Height}) + + preCommitCallback() + + c.App.Commit() + + c.Vals = c.NextVals + + c.NextVals = ibctesting.ApplyValSetChanges(c.T, c.Vals, ebRes.ValidatorUpdates) + + c.LastHeader = c.CurrentTMClientHeader() + + // Store header to be used in UpdateClient + f.clientHeaders[chainID] = append(f.clientHeaders[chainID], c.LastHeader) + + for _, e := range ebRes.Events { + if e.Type == channeltypes.EventTypeSendPacket { + packet, _ := channelkeeper.ReconstructPacketFromEvent(e) + // Collect packets + f.Link.AddPacket(chainID, packet) + } + } + + // Commit packets emmitted up to this point + f.Link.Commit(chainID) + + // increment the current header + c.CurrentHeader = tmproto.Header{ + ChainID: c.ChainID, + Height: c.App.LastBlockHeight() + 1, + AppHash: c.App.LastCommitID().Hash, + Time: c.CurrentHeader.Time.Add(dt), + ValidatorsHash: c.Vals.Hash(), + NextValidatorsHash: c.NextVals.Hash(), + } + + _ = c.App.BeginBlock(abci.RequestBeginBlock{Header: c.CurrentHeader}) +} + +func (f *RelayedPath) other(chainID string) string { + if f.path.EndpointA.Chain.ChainID == chainID { + return f.path.EndpointB.Chain.ChainID + } + if f.path.EndpointB.Chain.ChainID == chainID { + return f.path.EndpointA.Chain.ChainID + } + f.t.Fatal("other") + return "" +} + +func (f *RelayedPath) endpoint(chainID string) *ibctesting.Endpoint { + if chainID == f.path.EndpointA.Chain.ChainID { + return f.path.EndpointA + } + if chainID == f.path.EndpointB.Chain.ChainID { + return f.path.EndpointB + } + f.t.Fatal("endpoint") + return nil +}