From fb08485ae337e796a442b781632ae2123c4f4444 Mon Sep 17 00:00:00 2001 From: Leszek Stachowski Date: Wed, 13 Nov 2024 16:59:18 +0100 Subject: [PATCH] L2 Hotfix safe support (#420) --- .changeset/lemon-gorillas-explain.md | 5 + .changeset/sharp-shirts-count.md | 5 + .changeset/warm-papayas-smile.md | 5 + docs/command-line-interface/governance.md | 4 +- .../classes/celo_provider.CeloProvider.md | 37 +- .../interfaces/types.Eip1193Provider.md | 31 + .../types.Eip1193RequestArguments.md | 32 + docs/sdk/connect/modules/celo_provider.md | 2 +- docs/sdk/connect/modules/index.md | 14 + docs/sdk/connect/modules/types.md | 2 + packages/cli/package.json | 2 + .../commands/governance/approve-l2.test.ts | 941 +++++++++++------- .../cli/src/commands/governance/approve.ts | 35 +- packages/cli/src/test-utils/constants.ts | 25 + packages/cli/src/test-utils/multisigUtils.ts | 26 +- packages/cli/src/utils/cli.ts | 36 +- packages/cli/src/utils/safe.ts | 88 ++ packages/dev-utils/src/anvil-test.ts | 32 +- packages/sdk/connect/src/celo-provider.ts | 26 + packages/sdk/connect/src/types.ts | 10 + yarn.lock | 146 ++- 21 files changed, 1122 insertions(+), 382 deletions(-) create mode 100644 .changeset/lemon-gorillas-explain.md create mode 100644 .changeset/sharp-shirts-count.md create mode 100644 .changeset/warm-papayas-smile.md create mode 100644 docs/sdk/connect/interfaces/types.Eip1193Provider.md create mode 100644 docs/sdk/connect/interfaces/types.Eip1193RequestArguments.md create mode 100644 packages/cli/src/utils/safe.ts diff --git a/.changeset/lemon-gorillas-explain.md b/.changeset/lemon-gorillas-explain.md new file mode 100644 index 000000000..3bf059217 --- /dev/null +++ b/.changeset/lemon-gorillas-explain.md @@ -0,0 +1,5 @@ +--- +'@celo/celocli': minor +--- + +Adds support for safe integration for L2 hotfix security council approvals diff --git a/.changeset/sharp-shirts-count.md b/.changeset/sharp-shirts-count.md new file mode 100644 index 000000000..38f28f2e5 --- /dev/null +++ b/.changeset/sharp-shirts-count.md @@ -0,0 +1,5 @@ +--- +'@celo/dev-utils': patch +--- + +Adds actual Celo chain id when running anvil diff --git a/.changeset/warm-papayas-smile.md b/.changeset/warm-papayas-smile.md new file mode 100644 index 000000000..714615872 --- /dev/null +++ b/.changeset/warm-papayas-smile.md @@ -0,0 +1,5 @@ +--- +'@celo/connect': minor +--- + +Now CeloProvider can be wrapped in EIP-1193 partially compatible object (request + args) diff --git a/docs/command-line-interface/governance.md b/docs/command-line-interface/governance.md index 885fd870f..d568b18b8 100644 --- a/docs/command-line-interface/governance.md +++ b/docs/command-line-interface/governance.md @@ -33,7 +33,7 @@ Approve a dequeued governance proposal (or hotfix) USAGE $ celocli governance:approvehotfix --from 0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d [--gasCurrency 0x1234567890123456789012345678901234567890] [--globalHelp] - [--proposalID | --hotfix ] [--useMultiSig] [--type + [--proposalID | --hotfix ] [--useMultiSig | --useSafe] [--type approver|securityCouncil ] FLAGS @@ -55,6 +55,8 @@ FLAGS approver|securityCouncil> --useMultiSig True means the request will be sent through multisig. + --useSafe True means the request will + be sent through safe. DESCRIPTION Approve a dequeued governance proposal (or hotfix) diff --git a/docs/sdk/connect/classes/celo_provider.CeloProvider.md b/docs/sdk/connect/classes/celo_provider.CeloProvider.md index 2f313f62a..8aa763c5f 100644 --- a/docs/sdk/connect/classes/celo_provider.CeloProvider.md +++ b/docs/sdk/connect/classes/celo_provider.CeloProvider.md @@ -32,6 +32,7 @@ - [send](celo_provider.CeloProvider.md#send) - [stop](celo_provider.CeloProvider.md#stop) - [supportsSubscriptions](celo_provider.CeloProvider.md#supportssubscriptions) +- [toEip1193Provider](celo_provider.CeloProvider.md#toeip1193provider) ## Constructors @@ -52,7 +53,7 @@ #### Defined in -[packages/sdk/connect/src/celo-provider.ts:54](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L54) +[packages/sdk/connect/src/celo-provider.ts:56](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L56) ## Properties @@ -62,7 +63,7 @@ #### Defined in -[packages/sdk/connect/src/celo-provider.ts:54](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L54) +[packages/sdk/connect/src/celo-provider.ts:56](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L56) ___ @@ -72,7 +73,7 @@ ___ #### Defined in -[packages/sdk/connect/src/celo-provider.ts:54](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L54) +[packages/sdk/connect/src/celo-provider.ts:56](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L56) ## Accessors @@ -86,7 +87,7 @@ ___ #### Defined in -[packages/sdk/connect/src/celo-provider.ts:261](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L261) +[packages/sdk/connect/src/celo-provider.ts:287](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L287) ## Methods @@ -106,7 +107,7 @@ ___ #### Defined in -[packages/sdk/connect/src/celo-provider.ts:59](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L59) +[packages/sdk/connect/src/celo-provider.ts:61](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L61) ___ @@ -120,7 +121,7 @@ ___ #### Defined in -[packages/sdk/connect/src/celo-provider.ts:69](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L69) +[packages/sdk/connect/src/celo-provider.ts:71](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L71) ___ @@ -140,7 +141,7 @@ ___ #### Defined in -[packages/sdk/connect/src/celo-provider.ts:73](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L73) +[packages/sdk/connect/src/celo-provider.ts:75](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L75) ___ @@ -160,7 +161,7 @@ ___ #### Defined in -[packages/sdk/connect/src/celo-provider.ts:64](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L64) +[packages/sdk/connect/src/celo-provider.ts:66](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L66) ___ @@ -187,7 +188,7 @@ Send method as expected by web3.js #### Defined in -[packages/sdk/connect/src/celo-provider.ts:80](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L80) +[packages/sdk/connect/src/celo-provider.ts:82](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L82) ___ @@ -201,7 +202,7 @@ ___ #### Defined in -[packages/sdk/connect/src/celo-provider.ts:159](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L159) +[packages/sdk/connect/src/celo-provider.ts:161](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L161) ___ @@ -215,4 +216,18 @@ ___ #### Defined in -[packages/sdk/connect/src/celo-provider.ts:265](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L265) +[packages/sdk/connect/src/celo-provider.ts:291](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L291) + +___ + +### toEip1193Provider + +▸ **toEip1193Provider**(): [`Eip1193Provider`](../interfaces/types.Eip1193Provider.md) + +#### Returns + +[`Eip1193Provider`](../interfaces/types.Eip1193Provider.md) + +#### Defined in + +[packages/sdk/connect/src/celo-provider.ts:173](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L173) diff --git a/docs/sdk/connect/interfaces/types.Eip1193Provider.md b/docs/sdk/connect/interfaces/types.Eip1193Provider.md new file mode 100644 index 000000000..bfa7c16f8 --- /dev/null +++ b/docs/sdk/connect/interfaces/types.Eip1193Provider.md @@ -0,0 +1,31 @@ +[@celo/connect](../README.md) / [Exports](../modules.md) / [types](../modules/types.md) / Eip1193Provider + +# Interface: Eip1193Provider + +[types](../modules/types.md).Eip1193Provider + +## Table of contents + +### Methods + +- [request](types.Eip1193Provider.md#request) + +## Methods + +### request + +▸ **request**(`args`): `Promise`\<`unknown`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `args` | [`Eip1193RequestArguments`](types.Eip1193RequestArguments.md) | + +#### Returns + +`Promise`\<`unknown`\> + +#### Defined in + +[packages/sdk/connect/src/types.ts:158](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/types.ts#L158) diff --git a/docs/sdk/connect/interfaces/types.Eip1193RequestArguments.md b/docs/sdk/connect/interfaces/types.Eip1193RequestArguments.md new file mode 100644 index 000000000..c2a60c430 --- /dev/null +++ b/docs/sdk/connect/interfaces/types.Eip1193RequestArguments.md @@ -0,0 +1,32 @@ +[@celo/connect](../README.md) / [Exports](../modules.md) / [types](../modules/types.md) / Eip1193RequestArguments + +# Interface: Eip1193RequestArguments + +[types](../modules/types.md).Eip1193RequestArguments + +## Table of contents + +### Properties + +- [method](types.Eip1193RequestArguments.md#method) +- [params](types.Eip1193RequestArguments.md#params) + +## Properties + +### method + +• `Readonly` **method**: `string` + +#### Defined in + +[packages/sdk/connect/src/types.ts:153](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/types.ts#L153) + +___ + +### params + +• `Optional` `Readonly` **params**: `object` \| readonly `unknown`[] + +#### Defined in + +[packages/sdk/connect/src/types.ts:154](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/types.ts#L154) diff --git a/docs/sdk/connect/modules/celo_provider.md b/docs/sdk/connect/modules/celo_provider.md index e10e08a1f..cb1f48557 100644 --- a/docs/sdk/connect/modules/celo_provider.md +++ b/docs/sdk/connect/modules/celo_provider.md @@ -30,4 +30,4 @@ asserts provider is CeloProvider #### Defined in -[packages/sdk/connect/src/celo-provider.ts:35](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L35) +[packages/sdk/connect/src/celo-provider.ts:37](https://github.com/celo-org/developer-tooling/blob/master/packages/sdk/connect/src/celo-provider.ts#L37) diff --git a/docs/sdk/connect/modules/index.md b/docs/sdk/connect/modules/index.md index 0d8dc7b92..d095c7b2f 100644 --- a/docs/sdk/connect/modules/index.md +++ b/docs/sdk/connect/modules/index.md @@ -30,6 +30,8 @@ - [DecodedParamsArray](index.md#decodedparamsarray) - [DecodedParamsObject](index.md#decodedparamsobject) - [EIP1559TXProperties](index.md#eip1559txproperties) +- [Eip1193Provider](index.md#eip1193provider) +- [Eip1193RequestArguments](index.md#eip1193requestarguments) - [EncodedTransaction](index.md#encodedtransaction) - [Error](index.md#error) - [EthereumLegacyTXProperties](index.md#ethereumlegacytxproperties) @@ -226,6 +228,18 @@ Re-exports [EIP1559TXProperties](../interfaces/types.EIP1559TXProperties.md) ___ +### Eip1193Provider + +Re-exports [Eip1193Provider](../interfaces/types.Eip1193Provider.md) + +___ + +### Eip1193RequestArguments + +Re-exports [Eip1193RequestArguments](../interfaces/types.Eip1193RequestArguments.md) + +___ + ### EncodedTransaction Re-exports [EncodedTransaction](../interfaces/types.EncodedTransaction.md) diff --git a/docs/sdk/connect/modules/types.md b/docs/sdk/connect/modules/types.md index 65631debf..141edea0a 100644 --- a/docs/sdk/connect/modules/types.md +++ b/docs/sdk/connect/modules/types.md @@ -25,6 +25,8 @@ - [CeloParams](../interfaces/types.CeloParams.md) - [CeloTxObject](../interfaces/types.CeloTxObject.md) - [EIP1559TXProperties](../interfaces/types.EIP1559TXProperties.md) +- [Eip1193Provider](../interfaces/types.Eip1193Provider.md) +- [Eip1193RequestArguments](../interfaces/types.Eip1193RequestArguments.md) - [EncodedTransaction](../interfaces/types.EncodedTransaction.md) - [Error](../interfaces/types.Error.md) - [EthereumLegacyTXProperties](../interfaces/types.EthereumLegacyTXProperties.md) diff --git a/packages/cli/package.json b/packages/cli/package.json index 2d1f450bb..300bf4800 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -62,6 +62,8 @@ "@oclif/plugin-not-found": "^3.2.15", "@oclif/plugin-plugins": "^4.3.10", "@oclif/plugin-warn-if-update-available": "^3.1.11", + "@safe-global/protocol-kit": "^5.0.4", + "@safe-global/types-kit": "^1.0.0", "@types/command-exists": "^1.2.3", "bignumber.js": "9.0.0", "chalk": "^2.4.2", diff --git a/packages/cli/src/commands/governance/approve-l2.test.ts b/packages/cli/src/commands/governance/approve-l2.test.ts index fc52bed75..7a6a54e04 100644 --- a/packages/cli/src/commands/governance/approve-l2.test.ts +++ b/packages/cli/src/commands/governance/approve-l2.test.ts @@ -1,71 +1,81 @@ import { hexToBuffer, StrongAddress } from '@celo/base' +import { CeloProvider } from '@celo/connect/lib/celo-provider' import { newKitFromWeb3 } from '@celo/contractkit' import { DEFAULT_OWNER_ADDRESS, + setBalance, testWithAnvilL2, withImpersonatedAccount, } from '@celo/dev-utils/lib/anvil-test' import { ux } from '@oclif/core' +import Safe, { + getSafeAddressFromDeploymentTx, + PredictedSafeProps, + SafeAccountConfig, +} from '@safe-global/protocol-kit' import Web3 from 'web3' import { changeMultiSigOwner } from '../../test-utils/chain-setup' import { stripAnsiCodesAndTxHashes, testLocallyWithWeb3Node } from '../../test-utils/cliUtils' +import { setupSafeContracts } from '../../test-utils/multisigUtils' import Approve from './approve' process.env.NO_SYNCCHECK = 'true' -testWithAnvilL2('governance:approve cmd', (web3: Web3) => { - const HOTFIX_HASH = '0xbf670baa773b342120e1af45433a465bbd6fa289a5cf72763d63d95e4e22482d' - const HOTFIX_BUFFER = hexToBuffer(HOTFIX_HASH) - - describe('hotfix', () => { - it('fails when address is not security council multisig signatory', async () => { - const kit = newKitFromWeb3(web3) - const accounts = await web3.eth.getAccounts() - const governance = await kit.contracts.getGovernance() - const writeMock = jest.spyOn(ux.write, 'stdout') - const logMock = jest.spyOn(console, 'log') - const multisig = await governance.getApproverMultisig() - - await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { - // setApprover to 0x5409ED021D9299bf6814279A6A1411A7e866A631 to avoid "Council cannot be approver" error - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - data: '0x3156560e0000000000000000000000005409ed021d9299bf6814279a6a1411a7e866a631', - }) - ).waitReceipt() - - // setSecurityCouncil to multisig address - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - // cast calldata "setSecurityCouncil(address)" - data: `0x1c1083e2000000000000000000000000${multisig.address - .replace('0x', '') - .toLowerCase()}`, - }) - ).waitReceipt() - }) - - await expect( - testLocallyWithWeb3Node( - Approve, - [ - '--from', - accounts[0], - '--hotfix', - HOTFIX_HASH, - '--useMultiSig', - '--type', - 'securityCouncil', - ], - web3 - ) - ).rejects.toThrow("Some checks didn't pass!") - - expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` +testWithAnvilL2( + 'governance:approve cmd', + (web3: Web3) => { + const HOTFIX_HASH = '0xbf670baa773b342120e1af45433a465bbd6fa289a5cf72763d63d95e4e22482d' + const HOTFIX_BUFFER = hexToBuffer(HOTFIX_HASH) + + describe('hotfix', () => { + it('fails when address is not security council multisig signatory', async () => { + const kit = newKitFromWeb3(web3) + const accounts = await web3.eth.getAccounts() + const governance = await kit.contracts.getGovernance() + const writeMock = jest.spyOn(ux.write, 'stdout') + const logMock = jest.spyOn(console, 'log') + const multisig = await governance.getApproverMultisig() + + await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { + // setApprover to 0x5409ED021D9299bf6814279A6A1411A7e866A631 to avoid "Council cannot be approver" error + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + data: '0x3156560e0000000000000000000000005409ed021d9299bf6814279a6a1411a7e866a631', + }) + ).waitReceipt() + + // setSecurityCouncil to multisig address + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + // cast calldata "setSecurityCouncil(address)" + data: `0x1c1083e2000000000000000000000000${multisig.address + .replace('0x', '') + .toLowerCase()}`, + }) + ).waitReceipt() + }) + + await expect( + testLocallyWithWeb3Node( + Approve, + [ + '--from', + accounts[0], + '--hotfix', + HOTFIX_HASH, + '--useMultiSig', + '--type', + 'securityCouncil', + ], + web3 + ) + ).rejects.toThrow("Some checks didn't pass!") + + expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` { "approved": false, "councilApproved": false, @@ -73,8 +83,8 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { "executionTimeLimit": "0", } `) - expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) - .toMatchInlineSnapshot(` + expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) + .toMatchInlineSnapshot(` [ [ "Running Checks:", @@ -93,25 +103,25 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { ], ] `) - expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) - }) - - it('fails when address is not approver multisig signatory', async () => { - const kit = newKitFromWeb3(web3) - const accounts = await web3.eth.getAccounts() - const governance = await kit.contracts.getGovernance() - const writeMock = jest.spyOn(ux.write, 'stdout') - const logMock = jest.spyOn(console, 'log') - - await expect( - testLocallyWithWeb3Node( - Approve, - ['--from', accounts[0], '--hotfix', HOTFIX_HASH, '--useMultiSig'], - web3 - ) - ).rejects.toThrow("Some checks didn't pass!") + expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) + }) - expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` + it('fails when address is not approver multisig signatory', async () => { + const kit = newKitFromWeb3(web3) + const accounts = await web3.eth.getAccounts() + const governance = await kit.contracts.getGovernance() + const writeMock = jest.spyOn(ux.write, 'stdout') + const logMock = jest.spyOn(console, 'log') + + await expect( + testLocallyWithWeb3Node( + Approve, + ['--from', accounts[0], '--hotfix', HOTFIX_HASH, '--useMultiSig'], + web3 + ) + ).rejects.toThrow("Some checks didn't pass!") + + expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` { "approved": false, "councilApproved": false, @@ -119,8 +129,8 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { "executionTimeLimit": "0", } `) - expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) - .toMatchInlineSnapshot(` + expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) + .toMatchInlineSnapshot(` [ [ "Running Checks:", @@ -139,47 +149,47 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { ], ] `) - expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) - }) - - it('fails when address is not security council', async () => { - const [approver, securityCouncil, account] = await web3.eth.getAccounts() - const kit = newKitFromWeb3(web3) - const governance = await kit.contracts.getGovernance() - const writeMock = jest.spyOn(ux.write, 'stdout') - const logMock = jest.spyOn(console, 'log') - - await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { - // setApprover to approver value - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - data: `0x3156560e000000000000000000000000${approver.replace('0x', '').toLowerCase()}`, - }) - ).waitReceipt() - - // setSecurityCouncil to securityCouncil value - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - data: `0x1c1083e2000000000000000000000000${securityCouncil - .replace('0x', '') - .toLowerCase()}`, - }) - ).waitReceipt() + expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) }) - await expect( - testLocallyWithWeb3Node( - Approve, - ['--from', account, '--hotfix', HOTFIX_HASH, '--type', 'securityCouncil'], - web3 - ) - ).rejects.toThrow("Some checks didn't pass!") - - expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` + it('fails when address is not security council', async () => { + const [approver, securityCouncil, account] = await web3.eth.getAccounts() + const kit = newKitFromWeb3(web3) + const governance = await kit.contracts.getGovernance() + const writeMock = jest.spyOn(ux.write, 'stdout') + const logMock = jest.spyOn(console, 'log') + + await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { + // setApprover to approver value + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + data: `0x3156560e000000000000000000000000${approver.replace('0x', '').toLowerCase()}`, + }) + ).waitReceipt() + + // setSecurityCouncil to securityCouncil value + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + data: `0x1c1083e2000000000000000000000000${securityCouncil + .replace('0x', '') + .toLowerCase()}`, + }) + ).waitReceipt() + }) + + await expect( + testLocallyWithWeb3Node( + Approve, + ['--from', account, '--hotfix', HOTFIX_HASH, '--type', 'securityCouncil'], + web3 + ) + ).rejects.toThrow("Some checks didn't pass!") + + expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` { "approved": false, "councilApproved": false, @@ -187,8 +197,8 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { "executionTimeLimit": "0", } `) - expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) - .toMatchInlineSnapshot(` + expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) + .toMatchInlineSnapshot(` [ [ "Running Checks:", @@ -204,43 +214,43 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { ], ] `) - expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) - }) - - it('fails when address is not approver', async () => { - const kit = newKitFromWeb3(web3) - const [approver, securityCouncil, account] = await web3.eth.getAccounts() - const governance = await kit.contracts.getGovernance() - const writeMock = jest.spyOn(ux.write, 'stdout') - const logMock = jest.spyOn(console, 'log') - - await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { - // setApprover to approver value - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - data: `0x3156560e000000000000000000000000${approver.replace('0x', '').toLowerCase()}`, - }) - ).waitReceipt() - - // setSecurityCouncil to securityCouncil value - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - data: `0x1c1083e2000000000000000000000000${securityCouncil - .replace('0x', '') - .toLowerCase()}`, - }) - ).waitReceipt() + expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) }) - await expect( - testLocallyWithWeb3Node(Approve, ['--from', account, '--hotfix', HOTFIX_HASH], web3) - ).rejects.toThrow("Some checks didn't pass!") - - expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` + it('fails when address is not approver', async () => { + const kit = newKitFromWeb3(web3) + const [approver, securityCouncil, account] = await web3.eth.getAccounts() + const governance = await kit.contracts.getGovernance() + const writeMock = jest.spyOn(ux.write, 'stdout') + const logMock = jest.spyOn(console, 'log') + + await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { + // setApprover to approver value + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + data: `0x3156560e000000000000000000000000${approver.replace('0x', '').toLowerCase()}`, + }) + ).waitReceipt() + + // setSecurityCouncil to securityCouncil value + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + data: `0x1c1083e2000000000000000000000000${securityCouncil + .replace('0x', '') + .toLowerCase()}`, + }) + ).waitReceipt() + }) + + await expect( + testLocallyWithWeb3Node(Approve, ['--from', account, '--hotfix', HOTFIX_HASH], web3) + ).rejects.toThrow("Some checks didn't pass!") + + expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` { "approved": false, "councilApproved": false, @@ -248,8 +258,8 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { "executionTimeLimit": "0", } `) - expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) - .toMatchInlineSnapshot(` + expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) + .toMatchInlineSnapshot(` [ [ "Running Checks:", @@ -265,45 +275,45 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { ], ] `) - expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) - }) - - it('succeeds when address is a direct security council', async () => { - const [approver, securityCouncil] = await web3.eth.getAccounts() - const kit = newKitFromWeb3(web3) - const governance = await kit.contracts.getGovernance() - const writeMock = jest.spyOn(ux.write, 'stdout') - const logMock = jest.spyOn(console, 'log') - - await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { - // setApprover to approver value - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - data: `0x3156560e000000000000000000000000${approver.replace('0x', '').toLowerCase()}`, - }) - ).waitReceipt() - - // setSecurityCouncil to securityCouncil value - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - data: `0x1c1083e2000000000000000000000000${securityCouncil - .replace('0x', '') - .toLowerCase()}`, - }) - ).waitReceipt() + expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) }) - await testLocallyWithWeb3Node( - Approve, - ['--from', securityCouncil, '--hotfix', HOTFIX_HASH, '--type', 'securityCouncil'], - web3 - ) + it('succeeds when address is a direct security council', async () => { + const [approver, securityCouncil] = await web3.eth.getAccounts() + const kit = newKitFromWeb3(web3) + const governance = await kit.contracts.getGovernance() + const writeMock = jest.spyOn(ux.write, 'stdout') + const logMock = jest.spyOn(console, 'log') + + await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { + // setApprover to approver value + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + data: `0x3156560e000000000000000000000000${approver.replace('0x', '').toLowerCase()}`, + }) + ).waitReceipt() + + // setSecurityCouncil to securityCouncil value + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + data: `0x1c1083e2000000000000000000000000${securityCouncil + .replace('0x', '') + .toLowerCase()}`, + }) + ).waitReceipt() + }) + + await testLocallyWithWeb3Node( + Approve, + ['--from', securityCouncil, '--hotfix', HOTFIX_HASH, '--type', 'securityCouncil'], + web3 + ) - expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` + expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` { "approved": false, "councilApproved": true, @@ -311,8 +321,8 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { "executionTimeLimit": "0", } `) - expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) - .toMatchInlineSnapshot(` + expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) + .toMatchInlineSnapshot(` [ [ "Running Checks:", @@ -344,41 +354,41 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { ], ] `) - expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) - }) - - it('succeeds when address is a direct approver', async () => { - const kit = newKitFromWeb3(web3) - const [approver, securityCouncil] = await web3.eth.getAccounts() - const governance = await kit.contracts.getGovernance() - const writeMock = jest.spyOn(ux.write, 'stdout') - const logMock = jest.spyOn(console, 'log') - - await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { - // setApprover to approver value - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - data: `0x3156560e000000000000000000000000${approver.replace('0x', '').toLowerCase()}`, - }) - ).waitReceipt() - - // setSecurityCouncil to securityCouncil value - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - data: `0x1c1083e2000000000000000000000000${securityCouncil - .replace('0x', '') - .toLowerCase()}`, - }) - ).waitReceipt() + expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) }) - await testLocallyWithWeb3Node(Approve, ['--from', approver, '--hotfix', HOTFIX_HASH], web3) - - expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` + it('succeeds when address is a direct approver', async () => { + const kit = newKitFromWeb3(web3) + const [approver, securityCouncil] = await web3.eth.getAccounts() + const governance = await kit.contracts.getGovernance() + const writeMock = jest.spyOn(ux.write, 'stdout') + const logMock = jest.spyOn(console, 'log') + + await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { + // setApprover to approver value + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + data: `0x3156560e000000000000000000000000${approver.replace('0x', '').toLowerCase()}`, + }) + ).waitReceipt() + + // setSecurityCouncil to securityCouncil value + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + data: `0x1c1083e2000000000000000000000000${securityCouncil + .replace('0x', '') + .toLowerCase()}`, + }) + ).waitReceipt() + }) + + await testLocallyWithWeb3Node(Approve, ['--from', approver, '--hotfix', HOTFIX_HASH], web3) + + expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` { "approved": true, "councilApproved": false, @@ -386,8 +396,8 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { "executionTimeLimit": "0", } `) - expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) - .toMatchInlineSnapshot(` + expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) + .toMatchInlineSnapshot(` [ [ "Running Checks:", @@ -419,62 +429,62 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { ], ] `) - expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) - }) - - it('succeeds when address is security council multisig signatory', async () => { - const kit = newKitFromWeb3(web3) - const accounts = (await web3.eth.getAccounts()) as StrongAddress[] - const governance = await kit.contracts.getGovernance() - const writeMock = jest.spyOn(ux.write, 'stdout') - const logMock = jest.spyOn(console, 'log') - const multisig = await governance.getApproverMultisig() - - await changeMultiSigOwner(kit, accounts[0]) - - await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { - // setApprover to 0x5409ED021D9299bf6814279A6A1411A7e866A631 to avoid "Council cannot be approver" error - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - // cast calldata "setApprover(address)" "0x5409ED021D9299bf6814279A6A1411A7e866A631" - data: '0x3156560e0000000000000000000000005409ed021d9299bf6814279a6a1411a7e866a631', - }) - ).waitReceipt() - - // setSecurityCouncil to multisig address - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - // cast calldata "setSecurityCouncil(address)" - data: `0x1c1083e2000000000000000000000000${multisig.address - .replace('0x', '') - .toLowerCase()}`, - }) - ).waitReceipt() + expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) }) - // Sanity checks - expect(await governance.getApprover()).toBe(accounts[0]) - expect(await governance.getSecurityCouncil()).toEqual(multisig.address) + it('succeeds when address is security council multisig signatory', async () => { + const kit = newKitFromWeb3(web3) + const accounts = (await web3.eth.getAccounts()) as StrongAddress[] + const governance = await kit.contracts.getGovernance() + const writeMock = jest.spyOn(ux.write, 'stdout') + const logMock = jest.spyOn(console, 'log') + const multisig = await governance.getApproverMultisig() + + await changeMultiSigOwner(kit, accounts[0]) + + await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { + // setApprover to 0x5409ED021D9299bf6814279A6A1411A7e866A631 to avoid "Council cannot be approver" error + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + // cast calldata "setApprover(address)" "0x5409ED021D9299bf6814279A6A1411A7e866A631" + data: '0x3156560e0000000000000000000000005409ed021d9299bf6814279a6a1411a7e866a631', + }) + ).waitReceipt() + + // setSecurityCouncil to multisig address + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + // cast calldata "setSecurityCouncil(address)" + data: `0x1c1083e2000000000000000000000000${multisig.address + .replace('0x', '') + .toLowerCase()}`, + }) + ).waitReceipt() + }) + + // Sanity checks + expect(await governance.getApprover()).toBe(accounts[0]) + expect(await governance.getSecurityCouncil()).toEqual(multisig.address) + + await testLocallyWithWeb3Node( + Approve, + [ + '--from', + accounts[0], + '--hotfix', + HOTFIX_HASH, + '--useMultiSig', + '--type', + 'securityCouncil', + ], + web3 + ) - await testLocallyWithWeb3Node( - Approve, - [ - '--from', - accounts[0], - '--hotfix', - HOTFIX_HASH, - '--useMultiSig', - '--type', - 'securityCouncil', - ], - web3 - ) - - expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` + expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` { "approved": false, "councilApproved": true, @@ -483,8 +493,8 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { } `) - expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) - .toMatchInlineSnapshot(` + expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) + .toMatchInlineSnapshot(` [ [ "Running Checks:", @@ -512,26 +522,241 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { ], ] `) - expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) - }) + expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) + }) + + it('succeeds when address is security council safe signatory', async () => { + await setupSafeContracts(web3) + + const kit = newKitFromWeb3(web3) + const [approver, securityCouncilSafeSignatory1] = + (await web3.eth.getAccounts()) as StrongAddress[] + const securityCouncilSafeSignatory2: StrongAddress = + '0x6C666E57A5E8715cFE93f92790f98c4dFf7b69e2' + const securityCouncilSafeSignatory2PrivateKey = + '0xe99303048756f2eac145377ebffdeec6747b8de31c1d34e004e1ee62f2b3d7a5' + const governance = await kit.contracts.getGovernance() + const writeMock = jest.spyOn(ux.write, 'stdout') + const logMock = jest.spyOn(console, 'log') + + const safeAccountConfig: SafeAccountConfig = { + owners: [securityCouncilSafeSignatory1, securityCouncilSafeSignatory2], + threshold: 2, + } + + const predictSafe: PredictedSafeProps = { + safeAccountConfig, + } + + const protocolKit = await Safe.init({ + predictedSafe: predictSafe, + provider: (web3.currentProvider as any as CeloProvider).toEip1193Provider(), + signer: securityCouncilSafeSignatory1, + }) + + const deploymentTransaction = await protocolKit.createSafeDeploymentTransaction() + + const receipt = await web3.eth.sendTransaction({ + from: securityCouncilSafeSignatory1, + ...deploymentTransaction, + }) + + // @ts-expect-error the function is able to extract safe adddress without having + const safeAddress = getSafeAddressFromDeploymentTx(receipt, '1.3.0') + + protocolKit.connect({ safeAddress }) + + await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { + // setApprover to 0x5409ED021D9299bf6814279A6A1411A7e866A631 to avoid "Council cannot be approver" error + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + // cast calldata "setApprover(address)" "0x5409ED021D9299bf6814279A6A1411A7e866A631" + data: '0x3156560e0000000000000000000000005409ed021d9299bf6814279a6a1411a7e866a631', + }) + ).waitReceipt() + + // setSecurityCouncil to safe address + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + // cast calldata "setSecurityCouncil(address)" + data: `0x1c1083e2000000000000000000000000${safeAddress + .replace('0x', '') + .toLowerCase()}`, + }) + ).waitReceipt() + }) + + // Sanity checks + expect(await governance.getApprover()).toBe(approver) + expect(await governance.getSecurityCouncil()).toEqual(safeAddress) + expect(await protocolKit.getOwners()).toEqual([ + securityCouncilSafeSignatory1, + securityCouncilSafeSignatory2, + ]) + + expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` + { + "approved": false, + "councilApproved": false, + "executed": false, + "executionTimeLimit": "0", + } + `) + + await testLocallyWithWeb3Node( + Approve, + [ + '--from', + securityCouncilSafeSignatory1, + '--hotfix', + HOTFIX_HASH, + '--useSafe', + '--type', + 'securityCouncil', + ], + web3 + ) + + // Run the same command twice with same arguments to make sure it doesn't have any effect + await testLocallyWithWeb3Node( + Approve, + [ + '--from', + securityCouncilSafeSignatory1, + '--hotfix', + HOTFIX_HASH, + '--useSafe', + '--type', + 'securityCouncil', + ], + web3 + ) + + expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` + { + "approved": false, + "councilApproved": false, + "executed": false, + "executionTimeLimit": "0", + } + `) - it('succeeds when address is approver multisig signatory', async () => { - const kit = newKitFromWeb3(web3) - const accounts = (await web3.eth.getAccounts()) as StrongAddress[] + // Make sure the account has enough balance to pay for the transaction + await setBalance( + web3, + securityCouncilSafeSignatory2, + BigInt(web3.utils.toWei('1', 'ether')) + ) + await testLocallyWithWeb3Node( + Approve, + [ + '--from', + securityCouncilSafeSignatory2, + '--hotfix', + HOTFIX_HASH, + '--useSafe', + '--type', + 'securityCouncil', + // We want to test if integration works for accounts that are not added to the node + '--privateKey', + securityCouncilSafeSignatory2PrivateKey, + ], + web3 + ) - await changeMultiSigOwner(kit, accounts[0]) + expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` + { + "approved": false, + "councilApproved": true, + "executed": false, + "executionTimeLimit": "0", + } + `) - const governance = await kit.contracts.getGovernance() - const writeMock = jest.spyOn(ux.write, 'stdout') - const logMock = jest.spyOn(console, 'log') + expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) + .toMatchInlineSnapshot(` + [ + [ + "Running Checks:", + ], + [ + " ✔ 0x6Ecbe1DB9EF729CBe972C83Fb886247691Fb6beb is security council safe signatory ", + ], + [ + " ✔ Hotfix 0xbf670baa773b342120e1af45433a465bbd6fa289a5cf72763d63d95e4e22482d is not already approved by security council ", + ], + [ + " ✔ Hotfix 0xbf670baa773b342120e1af45433a465bbd6fa289a5cf72763d63d95e4e22482d is not already executed ", + ], + [ + "All checks passed", + ], + [ + "txHash: 0xtxhash", + ], + [ + "Running Checks:", + ], + [ + " ✔ 0x6Ecbe1DB9EF729CBe972C83Fb886247691Fb6beb is security council safe signatory ", + ], + [ + " ✔ Hotfix 0xbf670baa773b342120e1af45433a465bbd6fa289a5cf72763d63d95e4e22482d is not already approved by security council ", + ], + [ + " ✔ Hotfix 0xbf670baa773b342120e1af45433a465bbd6fa289a5cf72763d63d95e4e22482d is not already executed ", + ], + [ + "All checks passed", + ], + [ + "Running Checks:", + ], + [ + " ✔ 0x6C666E57A5E8715cFE93f92790f98c4dFf7b69e2 is security council safe signatory ", + ], + [ + " ✔ Hotfix 0xbf670baa773b342120e1af45433a465bbd6fa289a5cf72763d63d95e4e22482d is not already approved by security council ", + ], + [ + " ✔ Hotfix 0xbf670baa773b342120e1af45433a465bbd6fa289a5cf72763d63d95e4e22482d is not already executed ", + ], + [ + "All checks passed", + ], + [ + "txHash: 0xtxhash", + ], + [ + "txHash: 0xtxhash", + ], + ] + `) + + expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) + }) + + it('succeeds when address is approver multisig signatory', async () => { + const kit = newKitFromWeb3(web3) + const accounts = (await web3.eth.getAccounts()) as StrongAddress[] - await testLocallyWithWeb3Node( - Approve, - ['--from', accounts[0], '--hotfix', HOTFIX_HASH, '--useMultiSig'], - web3 - ) + await changeMultiSigOwner(kit, accounts[0]) - expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` + const governance = await kit.contracts.getGovernance() + const writeMock = jest.spyOn(ux.write, 'stdout') + const logMock = jest.spyOn(console, 'log') + + await testLocallyWithWeb3Node( + Approve, + ['--from', accounts[0], '--hotfix', HOTFIX_HASH, '--useMultiSig'], + web3 + ) + + expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` { "approved": true, "councilApproved": false, @@ -539,8 +764,8 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { "executionTimeLimit": "0", } `) - expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) - .toMatchInlineSnapshot(` + expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) + .toMatchInlineSnapshot(` [ [ "Running Checks:", @@ -568,58 +793,58 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { ], ] `) - expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) - }) - - it('succeeds when address is security council multisig signatory', async () => { - const kit = newKitFromWeb3(web3) - const accounts = (await web3.eth.getAccounts()) as StrongAddress[] - - await changeMultiSigOwner(kit, accounts[0]) - - const governance = await kit.contracts.getGovernance() - const writeMock = jest.spyOn(ux.write, 'stdout') - const logMock = jest.spyOn(console, 'log') - const multisig = await governance.getApproverMultisig() - - await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { - // setApprover to 0x5409ED021D9299bf6814279A6A1411A7e866A631 to avoid "Council cannot be approver" error - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - data: '0x3156560e0000000000000000000000005409ed021d9299bf6814279a6a1411a7e866a631', - }) - ).waitReceipt() - - // setSecurityCouncil to multisig address - await ( - await kit.sendTransaction({ - to: governance.address, - from: DEFAULT_OWNER_ADDRESS, - // cast calldata "setSecurityCouncil(address)" - data: `0x1c1083e2000000000000000000000000${multisig.address - .replace('0x', '') - .toLowerCase()}`, - }) - ).waitReceipt() + expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) }) - await testLocallyWithWeb3Node( - Approve, - [ - '--from', - accounts[0], - '--hotfix', - HOTFIX_HASH, - '--useMultiSig', - '--type', - 'securityCouncil', - ], - web3 - ) - - expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` + it('succeeds when address is security council multisig signatory', async () => { + const kit = newKitFromWeb3(web3) + const accounts = (await web3.eth.getAccounts()) as StrongAddress[] + + await changeMultiSigOwner(kit, accounts[0]) + + const governance = await kit.contracts.getGovernance() + const writeMock = jest.spyOn(ux.write, 'stdout') + const logMock = jest.spyOn(console, 'log') + const multisig = await governance.getApproverMultisig() + + await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => { + // setApprover to 0x5409ED021D9299bf6814279A6A1411A7e866A631 to avoid "Council cannot be approver" error + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + data: '0x3156560e0000000000000000000000005409ed021d9299bf6814279a6a1411a7e866a631', + }) + ).waitReceipt() + + // setSecurityCouncil to multisig address + await ( + await kit.sendTransaction({ + to: governance.address, + from: DEFAULT_OWNER_ADDRESS, + // cast calldata "setSecurityCouncil(address)" + data: `0x1c1083e2000000000000000000000000${multisig.address + .replace('0x', '') + .toLowerCase()}`, + }) + ).waitReceipt() + }) + + await testLocallyWithWeb3Node( + Approve, + [ + '--from', + accounts[0], + '--hotfix', + HOTFIX_HASH, + '--useMultiSig', + '--type', + 'securityCouncil', + ], + web3 + ) + + expect(await governance.getHotfixRecord(HOTFIX_BUFFER)).toMatchInlineSnapshot(` { "approved": false, "councilApproved": true, @@ -627,8 +852,8 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { "executionTimeLimit": "0", } `) - expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) - .toMatchInlineSnapshot(` + expect(logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))) + .toMatchInlineSnapshot(` [ [ "Running Checks:", @@ -656,10 +881,14 @@ testWithAnvilL2('governance:approve cmd', (web3: Web3) => { ], ] `) - expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) + expect(writeMock.mock.calls).toMatchInlineSnapshot(`[]`) + }) + }) + afterEach(() => { + jest.clearAllMocks() }) - }) - afterEach(() => { - jest.clearAllMocks() - }) -}) + }, + { + chainId: 42220, + } +) diff --git a/packages/cli/src/commands/governance/approve.ts b/packages/cli/src/commands/governance/approve.ts index 1d05ad3c3..9ec3a78fb 100644 --- a/packages/cli/src/commands/governance/approve.ts +++ b/packages/cli/src/commands/governance/approve.ts @@ -4,10 +4,16 @@ import { GovernanceWrapper } from '@celo/contractkit/lib/wrappers/Governance' import { MultiSigWrapper } from '@celo/contractkit/lib/wrappers/MultiSig' import { toBuffer } from '@ethereumjs/util' import { Flags } from '@oclif/core' +import Web3 from 'web3' import { BaseCommand } from '../../base' import { newCheckBuilder } from '../../utils/checks' import { displaySendTx, failWith } from '../../utils/cli' import { CustomFlags } from '../../utils/command' +import { + createSafeFromWeb3, + performSafeTransaction, + safeTransactionMetadataFromCeloTransactionObject, +} from '../../utils/safe' enum HotfixApprovalType { APPROVER = 'approver', @@ -31,6 +37,11 @@ export default class Approve extends BaseCommand { from: CustomFlags.address({ required: true, description: "Approver's address" }), useMultiSig: Flags.boolean({ description: 'True means the request will be sent through multisig.', + exclusive: ['useSafe'], + }), + useSafe: Flags.boolean({ + description: 'True means the request will be sent through safe.', + exclusive: ['useMultiSig'], }), hotfix: Flags.string({ exclusive: ['proposalID'], @@ -59,6 +70,7 @@ export default class Approve extends BaseCommand { const res = await this.parse(Approve) const account = res.flags.from const useMultiSig = res.flags.useMultiSig + const useSafe = res.flags.useSafe const id = res.flags.proposalID const hotfix = res.flags.hotfix const approvalType = res.flags.type @@ -73,11 +85,13 @@ export default class Approve extends BaseCommand { const approver = useMultiSig ? governanceApproverMultiSig!.address : account await addDefaultChecks( + await this.getWeb3(), checkBuilder, governance, isCel2, !!hotfix, useMultiSig, + useSafe, approvalType as HotfixApprovalType, hotfix as string, approver, @@ -115,7 +129,14 @@ export default class Approve extends BaseCommand { failWith('Proposal ID or hotfix must be provided') } - if ( + if (isCel2 && approvalType === 'securityCouncil' && useSafe) { + await performSafeTransaction( + await this.getWeb3(), + await governance.getSecurityCouncil(), + account, + await safeTransactionMetadataFromCeloTransactionObject(governanceTx, governance.address) + ) + } else if ( isCel2 && approvalType === 'securityCouncil' && useMultiSig && @@ -140,11 +161,13 @@ export default class Approve extends BaseCommand { } const addDefaultChecks = async ( + web3: Web3, checkBuilder: ReturnType, governance: GovernanceWrapper, isCel2: boolean, isHotfix: boolean, useMultiSig: boolean, + useSafe: boolean, approvalType: HotfixApprovalType, hotfix: string, approver: StrongAddress, @@ -178,6 +201,16 @@ const addDefaultChecks = async ( .addCheck(`${account} is security council multisig signatory`, async () => { return await securityCouncilMultisig.isOwner(account) }) + } else if (useSafe) { + checkBuilder.addCheck(`${account} is security council safe signatory`, async () => { + const protocolKit = await createSafeFromWeb3( + web3, + account, + await governance.getSecurityCouncil() + ) + + return await protocolKit.isOwner(account) + }) } else { checkBuilder.isSecurityCouncil(account) } diff --git a/packages/cli/src/test-utils/constants.ts b/packages/cli/src/test-utils/constants.ts index f2a725b2a..ec8a4c2c0 100644 --- a/packages/cli/src/test-utils/constants.ts +++ b/packages/cli/src/test-utils/constants.ts @@ -17,3 +17,28 @@ export const multiSigBytecode = export const PROOF_OF_POSSESSION_SIGNATURE = '0x1b9fca4bbb5bfb1dbe69ef1cddbd9b4202dcb6b134c5170611e1e36ecfa468d7b46c85328d504934fce6c2a1571603a50ae224d2b32685e84d4d1a1eebad8452eb' + +/** + * Those are all addresses and bytecodes for contracts required by + * @safe-global/protocol-kit to work for the test cases based on mainnet implementation + */ + +export const SAFE_MULTISEND_ADDRESS = '0x998739BFdAAdde7C933B942a68053933098f9EDa' +export const SAFE_MULTISEND_CODE = + '0x60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100dc6004803603602081101561003957600080fd5b810190808035906020019064010000000081111561005657600080fd5b82018360208201111561006857600080fd5b8035906020019184600183028401116401000000008311171561008a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506100de565b005b7f000000000000000000000000998739bfdaadde7c933b942a68053933098f9eda73ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff161415610183576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260308152602001806102106030913960400191505060405180910390fd5b805160205b8181101561020a578083015160f81c6001820184015160601c6015830185015160358401860151605585018701600085600081146101cd57600181146101dd576101e8565b6000808585888a5af191506101e8565b6000808585895af491505b5060008114156101f757600080fd5b8260550187019650505050505050610188565b50505056fe4d756c746953656e642073686f756c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6ca26469706673582212205c784303626eec02b71940b551976170b500a8a36cc5adcbeb2c19751a76d05464736f6c63430007060033' + +export const SAFE_MULTISEND_CALL_ONLY_ADDRESS = '0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B' +export const SAFE_MULTISEND_CALL_ONLY_CODE = + '0x60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100dc6004803603602081101561003957600080fd5b810190808035906020019064010000000081111561005657600080fd5b82018360208201111561006857600080fd5b8035906020019184600183028401116401000000008311171561008a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506100de565b005b805160205b8181101561015f578083015160f81c6001820184015160601c60158301850151603584018601516055850187016000856000811461012857600181146101385761013d565b6000808585888a5af1915061013d565b600080fd5b50600081141561014c57600080fd5b82605501870196505050505050506100e3565b50505056fea264697066735822122035246402746c96964495cae5b36461fd44dfb89f8e6cf6f6b8d60c0aa89f414864736f6c63430007060033' + +export const SAFE_PROXY_FACTORY_ADDRESS = '0xC22834581EbC8527d974F8a1c97E1bEA4EF910BC' +export const SAFE_PROXY_FACTORY_CODE = + '0x608060405234801561001057600080fd5b50600436106100625760003560e01c80631688f0b9146100675780632500510e1461017657806353e5d9351461024357806361b69abd146102c6578063addacc0f146103cb578063d18af54d1461044e575b600080fd5b61014a6004803603606081101561007d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156100ba57600080fd5b8201836020820111156100cc57600080fd5b803590602001918460018302840111640100000000831117156100ee57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019092919050505061057d565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6102176004803603606081101561018c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156101c957600080fd5b8201836020820111156101db57600080fd5b803590602001918460018302840111640100000000831117156101fd57600080fd5b909192939192939080359060200190929190505050610624565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61024b610751565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561028b578082015181840152602081019050610270565b50505050905090810190601f1680156102b85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61039f600480360360408110156102dc57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019064010000000081111561031957600080fd5b82018360208201111561032b57600080fd5b8035906020019184600183028401116401000000008311171561034d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061077c565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6103d3610861565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156104135780820151818401526020810190506103f8565b50505050905090810190601f1680156104405780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6105516004803603608081101561046457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156104a157600080fd5b8201836020820111156104b357600080fd5b803590602001918460018302840111640100000000831117156104d557600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061088c565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600061058a848484610a3b565b90506000835111156105b25760008060008551602087016000865af114156105b157600080fd5b5b7f4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e2358185604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a19392505050565b60006106758585858080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505084610a3b565b905080604051602001808273ffffffffffffffffffffffffffffffffffffffff1660601b81526014019150506040516020818303038152906040526040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b838110156107165780820151818401526020810190506106fb565b50505050905090810190601f1680156107435780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b60606040518060200161076390610bde565b6020820181038252601f19601f82011660405250905090565b60008260405161078b90610bde565b808273ffffffffffffffffffffffffffffffffffffffff168152602001915050604051809103906000f0801580156107c7573d6000803e3d6000fd5b5090506000825111156107f05760008060008451602086016000865af114156107ef57600080fd5b5b7f4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e2358184604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a192915050565b60606040518060200161087390610beb565b6020820181038252601f19601f82011660405250905090565b6000808383604051602001808381526020018273ffffffffffffffffffffffffffffffffffffffff1660601b8152601401925050506040516020818303038152906040528051906020012060001c90506108e786868361057d565b9150600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614610a32578273ffffffffffffffffffffffffffffffffffffffff16631e52b518838888886040518563ffffffff1660e01b8152600401808573ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff16815260200180602001838152602001828103825284818151815260200191508051906020019080838360005b838110156109ca5780820151818401526020810190506109af565b50505050905090810190601f1680156109f75780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b158015610a1957600080fd5b505af1158015610a2d573d6000803e3d6000fd5b505050505b50949350505050565b6000808380519060200120836040516020018083815260200182815260200192505050604051602081830303815290604052805190602001209050600060405180602001610a8890610bde565b6020820181038252601f19601f820116604052508673ffffffffffffffffffffffffffffffffffffffff166040516020018083805190602001908083835b60208310610ae95780518252602082019150602081019050602083039250610ac6565b6001836020036101000a038019825116818451168082178552505050505050905001828152602001925050506040516020818303038152906040529050818151826020016000f59250600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610bd5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f437265617465322063616c6c206661696c65640000000000000000000000000081525060200191505060405180910390fd5b50509392505050565b6101e680610bf883390190565b60ab80610dde8339019056fe608060405234801561001057600080fd5b506040516101e63803806101e68339818101604052602081101561003357600080fd5b8101908080519060200190929190505050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156100ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806101c46022913960400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060ab806101196000396000f3fe608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea2646970667358221220d1429297349653a4918076d650332de1a1068c5f3e07c5c82360c277770b955264736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f7669646564608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea2646970667358221220d1429297349653a4918076d650332de1a1068c5f3e07c5c82360c277770b955264736f6c63430007060033a26469706673582212200c75fe2196b9f752c82794253f2ebce0d821afef5997e1d5a35ec316ce592f6664736f6c63430007060033' + +export const SAFE_PROXY_ADDRESS = '0xfb1bffC9d739B8D520DaF37dF666da4C687191EA' +export const SAFE_PROXY_CODE = + '0x6080604052600436106101dc5760003560e01c8063affed0e011610102578063e19a9dd911610095578063f08a032311610064578063f08a032314611647578063f698da2514611698578063f8dc5dd9146116c3578063ffa1ad741461173e57610231565b8063e19a9dd91461139b578063e318b52b146113ec578063e75235b81461147d578063e86637db146114a857610231565b8063cc2f8452116100d1578063cc2f8452146110e8578063d4d9bdcd146111b5578063d8d11f78146111f0578063e009cfde1461132a57610231565b8063affed0e014610d94578063b4faba0914610dbf578063b63e800d14610ea7578063c4ca3a9c1461101757610231565b80635624b25b1161017a5780636a761202116101495780636a761202146109945780637d83297414610b50578063934f3a1114610bbf578063a0e67e2b14610d2857610231565b80635624b25b146107fb5780635ae6bd37146108b9578063610b592514610908578063694e80c31461095957610231565b80632f54bf6e116101b65780632f54bf6e146104d35780633408e4701461053a578063468721a7146105655780635229073f1461067a57610231565b80630d582f131461029e57806312fb68e0146102f95780632d9ad53d1461046c57610231565b36610231573373ffffffffffffffffffffffffffffffffffffffff167f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d346040518082815260200191505060405180910390a2005b34801561023d57600080fd5b5060007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b905080548061027257600080f35b36600080373360601b365260008060143601600080855af13d6000803e80610299573d6000fd5b3d6000f35b3480156102aa57600080fd5b506102f7600480360360408110156102c157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506117ce565b005b34801561030557600080fd5b5061046a6004803603608081101561031c57600080fd5b81019080803590602001909291908035906020019064010000000081111561034357600080fd5b82018360208201111561035557600080fd5b8035906020019184600183028401116401000000008311171561037757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803590602001906401000000008111156103da57600080fd5b8201836020820111156103ec57600080fd5b8035906020019184600183028401116401000000008311171561040e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050611bbe565b005b34801561047857600080fd5b506104bb6004803603602081101561048f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612440565b60405180821515815260200191505060405180910390f35b3480156104df57600080fd5b50610522600480360360208110156104f657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612512565b60405180821515815260200191505060405180910390f35b34801561054657600080fd5b5061054f6125e4565b6040518082815260200191505060405180910390f35b34801561057157600080fd5b506106626004803603608081101561058857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156105cf57600080fd5b8201836020820111156105e157600080fd5b8035906020019184600183028401116401000000008311171561060357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506125f1565b60405180821515815260200191505060405180910390f35b34801561068657600080fd5b506107776004803603608081101561069d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156106e457600080fd5b8201836020820111156106f657600080fd5b8035906020019184600183028401116401000000008311171561071857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506126fc565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b838110156107bf5780820151818401526020810190506107a4565b50505050905090810190601f1680156107ec5780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b34801561080757600080fd5b5061083e6004803603604081101561081e57600080fd5b810190808035906020019092919080359060200190929190505050612732565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561087e578082015181840152602081019050610863565b50505050905090810190601f1680156108ab5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156108c557600080fd5b506108f2600480360360208110156108dc57600080fd5b81019080803590602001909291905050506127b9565b6040518082815260200191505060405180910390f35b34801561091457600080fd5b506109576004803603602081101561092b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506127d1565b005b34801561096557600080fd5b506109926004803603602081101561097c57600080fd5b8101908080359060200190929190505050612b63565b005b610b3860048036036101408110156109ab57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156109f257600080fd5b820183602082011115610a0457600080fd5b80359060200191846001830284011164010000000083111715610a2657600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610ab257600080fd5b820183602082011115610ac457600080fd5b80359060200191846001830284011164010000000083111715610ae657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612c9d565b60405180821515815260200191505060405180910390f35b348015610b5c57600080fd5b50610ba960048036036040811015610b7357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050612edc565b6040518082815260200191505060405180910390f35b348015610bcb57600080fd5b50610d2660048036036060811015610be257600080fd5b810190808035906020019092919080359060200190640100000000811115610c0957600080fd5b820183602082011115610c1b57600080fd5b80359060200191846001830284011164010000000083111715610c3d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190640100000000811115610ca057600080fd5b820183602082011115610cb257600080fd5b80359060200191846001830284011164010000000083111715610cd457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612f01565b005b348015610d3457600080fd5b50610d3d612f90565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b83811015610d80578082015181840152602081019050610d65565b505050509050019250505060405180910390f35b348015610da057600080fd5b50610da9613139565b6040518082815260200191505060405180910390f35b348015610dcb57600080fd5b50610ea560048036036040811015610de257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610e1f57600080fd5b820183602082011115610e3157600080fd5b80359060200191846001830284011164010000000083111715610e5357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061313f565b005b348015610eb357600080fd5b506110156004803603610100811015610ecb57600080fd5b8101908080359060200190640100000000811115610ee857600080fd5b820183602082011115610efa57600080fd5b80359060200191846020830284011164010000000083111715610f1c57600080fd5b909192939192939080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610f6757600080fd5b820183602082011115610f7957600080fd5b80359060200191846001830284011164010000000083111715610f9b57600080fd5b9091929391929390803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613161565b005b34801561102357600080fd5b506110d26004803603608081101561103a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561108157600080fd5b82018360208201111561109357600080fd5b803590602001918460018302840111640100000000831117156110b557600080fd5b9091929391929390803560ff16906020019092919050505061331f565b6040518082815260200191505060405180910390f35b3480156110f457600080fd5b506111416004803603604081101561110b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050613447565b60405180806020018373ffffffffffffffffffffffffffffffffffffffff168152602001828103825284818151815260200191508051906020019060200280838360005b838110156111a0578082015181840152602081019050611185565b50505050905001935050505060405180910390f35b3480156111c157600080fd5b506111ee600480360360208110156111d857600080fd5b8101908080359060200190929190505050613639565b005b3480156111fc57600080fd5b50611314600480360361014081101561121457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561125b57600080fd5b82018360208201111561126d57600080fd5b8035906020019184600183028401116401000000008311171561128f57600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506137d8565b6040518082815260200191505060405180910390f35b34801561133657600080fd5b506113996004803603604081101561134d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613805565b005b3480156113a757600080fd5b506113ea600480360360208110156113be57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613b96565b005b3480156113f857600080fd5b5061147b6004803603606081101561140f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613c1a565b005b34801561148957600080fd5b5061149261428c565b6040518082815260200191505060405180910390f35b3480156114b457600080fd5b506115cc60048036036101408110156114cc57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561151357600080fd5b82018360208201111561152557600080fd5b8035906020019184600183028401116401000000008311171561154757600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050614296565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561160c5780820151818401526020810190506115f1565b50505050905090810190601f1680156116395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561165357600080fd5b506116966004803603602081101561166a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061443e565b005b3480156116a457600080fd5b506116ad61449f565b6040518082815260200191505060405180910390f35b3480156116cf57600080fd5b5061173c600480360360608110156116e657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061451d565b005b34801561174a57600080fd5b50611753614950565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015611793578082015181840152602081019050611778565b50505050905090810190601f1680156117c05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6117d6614989565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156118405750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b801561187857503073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b6118ea576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146119eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600081548092919060010191905055507f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2682604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18060045414611bba57611bb981612b63565b5b5050565b611bd2604182614a2c90919063ffffffff16565b82511015611c48576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808060008060005b8681101561243457611c648882614a66565b80945081955082965050505060008460ff16141561206d578260001c9450611c96604188614a2c90919063ffffffff16565b8260001c1015611d0e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8751611d2760208460001c614a9590919063ffffffff16565b1115611d9b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006020838a01015190508851611dd182611dc360208760001c614a9590919063ffffffff16565b614a9590919063ffffffff16565b1115611e45576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60606020848b010190506320c13b0b60e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168773ffffffffffffffffffffffffffffffffffffffff166320c13b0b8d846040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019080838360005b83811015611ee7578082015181840152602081019050611ecc565b50505050905090810190601f168015611f145780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b83811015611f4d578082015181840152602081019050611f32565b50505050905090810190601f168015611f7a5780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b158015611f9957600080fd5b505afa158015611fad573d6000803e3d6000fd5b505050506040513d6020811015611fc357600080fd5b81019080805190602001909291905050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612066576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b50506122b2565b60018460ff161415612181578260001c94508473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061210a57506000600860008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008c81526020019081526020016000205414155b61217c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6122b1565b601e8460ff1611156122495760018a60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012060048603858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015612238573d6000803e3d6000fd5b5050506020604051035194506122b0565b60018a85858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156122a3573d6000803e3d6000fd5b5050506020604051035194505b5b5b8573ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161180156123795750600073ffffffffffffffffffffffffffffffffffffffff16600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b80156123b25750600173ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614155b612424576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323600000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8495508080600101915050611c52565b50505050505050505050565b60008173ffffffffffffffffffffffffffffffffffffffff16600173ffffffffffffffffffffffffffffffffffffffff161415801561250b5750600073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156125dd5750600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000804690508091505090565b60007fb648d3644f584ed1c2232d53c46d87e693586486ad0d1175f8656013110b714e3386868686604051808673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff1681526020018481526020018060200183600181111561266b57fe5b8152602001828103825284818151815260200191508051906020019080838360005b838110156126a857808201518184015260208101905061268d565b50505050905090810190601f1680156126d55780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390a16126f285858585614ab4565b9050949350505050565b6000606061270c868686866125f1565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b606060006020830267ffffffffffffffff8111801561275057600080fd5b506040519080825280601f01601f1916602001820160405280156127835781602001600182028036833780820191505090505b50905060005b838110156127ae57808501548060208302602085010152508080600101915050612789565b508091505092915050565b60076020528060005260406000206000915090505481565b6127d9614989565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156128435750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b6128b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146129b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f844081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b612b6b614989565b600354811115612be3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001811015612c5a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b806004819055507f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c936004546040518082815260200191505060405180910390a150565b6000606060055433600454604051602001808481526020018373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405160208183030381529060405290507f66753cd2356569ee081232e3be8909b950e0a76c1f8460c3a5e3c2be32b11bed8d8d8d8d8d8d8d8d8d8d8d8c604051808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115612d5057fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018060200184810384528e8e82818152602001925080828437600081840152601f19601f820116905080830192505050848103835286818151815260200191508051906020019080838360005b83811015612e0a578082015181840152602081019050612def565b50505050905090810190601f168015612e375780820380516001836020036101000a031916815260200191505b50848103825285818151815260200191508051906020019080838360005b83811015612e70578082015181840152602081019050612e55565b50505050905090810190601f168015612e9d5780820380516001836020036101000a031916815260200191505b509f5050505050505050505050505050505060405180910390a1612eca8d8d8d8d8d8d8d8d8d8d8d614c9a565b9150509b9a5050505050505050505050565b6008602052816000526040600020602052806000526040600020600091509150505481565b6000600454905060008111612f7e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b612f8a84848484611bbe565b50505050565b6060600060035467ffffffffffffffff81118015612fad57600080fd5b50604051908082528060200260200182016040528015612fdc5781602001602082028036833780820191505090505b50905060008060026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614613130578083838151811061308757fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508180600101925050613046565b82935050505090565b60055481565b600080825160208401855af4806000523d6020523d600060403e60403d016000fd5b6131ac8a8a80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050896151d7565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16146131ea576131e9846156d7565b5b6132388787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050615706565b60008211156132525761325082600060018685615941565b505b3373ffffffffffffffffffffffffffffffffffffffff167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b8960405180806020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281038252878782818152602001925060200280828437600081840152601f19601f820116905080830192505050965050505050505060405180910390a250505050505050505050565b6000805a9050613376878787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050865a615b47565b61337f57600080fd5b60005a8203905080604051602001808281526020019150506040516020818303038152906040526040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561340c5780820151818401526020810190506133f1565b50505050905090810190601f1680156134395780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b606060008267ffffffffffffffff8111801561346257600080fd5b506040519080825280602002602001820160405280156134915781602001602082028036833780820191505090505b509150600080600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156135645750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561356f57508482105b1561362a578084838151811061358157fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081806001019250506134fa565b80925081845250509250929050565b600073ffffffffffffffffffffffffffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561373b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001600860003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000838152602001908152602001600020819055503373ffffffffffffffffffffffffffffffffffffffff16817ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c60405160405180910390a350565b60006137ed8c8c8c8c8c8c8c8c8c8c8c614296565b8051906020012090509b9a5050505050505050505050565b61380d614989565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156138775750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b6138e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146139e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace405427681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613b9e614989565b60007f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b90508181557f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa282604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613c22614989565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614158015613c8c5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b8015613cc457503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b613d36576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614613e37576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614158015613ea15750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b613f13576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614013576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a17f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1505050565b6000600454905090565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d60405180838380828437808301925050509250505060405180910390208c8c8c8c8c8c8c604051602001808c81526020018b73ffffffffffffffffffffffffffffffffffffffff1681526020018a815260200189815260200188600181111561432757fe5b81526020018781526020018681526020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019b505050505050505050505050604051602081830303815290604052805190602001209050601960f81b600160f81b6143b361449f565b8360405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600101847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526001018381526020018281526020019450505050506040516020818303038152906040529150509b9a5050505050505050505050565b614446614989565b61444f816156d7565b7f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a7946921860001b6144cd6125e4565b30604051602001808481526020018381526020018273ffffffffffffffffffffffffffffffffffffffff168152602001935050505060405160208183030381529060405280519060200120905090565b614525614989565b8060016003540310156145a0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415801561460a5750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b61467c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461477c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600360008154809291906001900391905055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1806004541461494b5761494a81612b63565b5b505050565b6040518060400160405280600581526020017f312e332e3000000000000000000000000000000000000000000000000000000081525081565b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614614a2a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b600080831415614a3f5760009050614a60565b6000828402905082848281614a5057fe5b0414614a5b57600080fd5b809150505b92915050565b60008060008360410260208101860151925060408101860151915060ff60418201870151169350509250925092565b600080828401905083811015614aaa57600080fd5b8091505092915050565b6000600173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614158015614b7f5750600073ffffffffffffffffffffffffffffffffffffffff16600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b614bf1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b614bfe858585855a615b47565b90508015614c4e573373ffffffffffffffffffffffffffffffffffffffff167f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb860405160405180910390a2614c92565b3373ffffffffffffffffffffffffffffffffffffffff167facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37560405160405180910390a25b949350505050565b6000806000614cb48e8e8e8e8e8e8e8e8e8e600554614296565b905060056000815480929190600101919050555080805190602001209150614cdd828286612f01565b506000614ce8615b93565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614614ece578073ffffffffffffffffffffffffffffffffffffffff166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115614d8b57fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018473ffffffffffffffffffffffffffffffffffffffff16815260200183810383528d8d82818152602001925080828437600081840152601f19601f820116905080830192505050838103825285818151815260200191508051906020019080838360005b83811015614e5d578082015181840152602081019050614e42565b50505050905090810190601f168015614e8a5780820380516001836020036101000a031916815260200191505b509e505050505050505050505050505050600060405180830381600087803b158015614eb557600080fd5b505af1158015614ec9573d6000803e3d6000fd5b505050505b6101f4614ef56109c48b01603f60408d0281614ee657fe5b04615bc490919063ffffffff16565b015a1015614f6b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60005a9050614fd48f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e60008d14614fc9578e614fcf565b6109c45a035b615b47565b9350614fe95a82615bde90919063ffffffff16565b90508380614ff8575060008a14155b80615004575060008814155b615076576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808911156150905761508d828b8b8b8b615941565b90505b84156150da577f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e8482604051808381526020018281526020019250505060405180910390a161511a565b7f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d238482604051808381526020018281526020019250505060405180910390a15b5050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146151c6578073ffffffffffffffffffffffffffffffffffffffff16639327136883856040518363ffffffff1660e01b815260040180838152602001821515815260200192505050600060405180830381600087803b1580156151ad57600080fd5b505af11580156151c1573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b60006004541461524f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b81518111156152c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600181101561533d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006001905060005b835181101561564357600084828151811061535d57fe5b60200260200101519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156153d15750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561540957503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561544157508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614155b6154b3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146155b4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550809250508080600101915050615346565b506001600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550825160038190555081600481905550505050565b60007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b90508181555050565b600073ffffffffffffffffffffffffffffffffffffffff1660016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614615808576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001806000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161461593d576158ca8260008360015a615b47565b61593c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5050565b600080600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161461597e5782615980565b325b9050600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415615a98576159ea3a86106159c7573a6159c9565b855b6159dc888a614a9590919063ffffffff16565b614a2c90919063ffffffff16565b91508073ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050615a93576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b615b3d565b615abd85615aaf888a614a9590919063ffffffff16565b614a2c90919063ffffffff16565b9150615aca848284615bfe565b615b3c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5095945050505050565b6000600180811115615b5557fe5b836001811115615b6157fe5b1415615b7a576000808551602087018986f49050615b8a565b600080855160208701888a87f190505b95945050505050565b6000807f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b9050805491505090565b600081831015615bd45781615bd6565b825b905092915050565b600082821115615bed57600080fd5b600082840390508091505092915050565b60008063a9059cbb8484604051602401808373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506040516020818303038152906040529060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050602060008251602084016000896127105a03f13d60008114615ca55760208114615cad5760009350615cb8565b819350615cb8565b600051158215171593505b505050939250505056fea2646970667358221220047fac33099ca576d1c4f1ac6a8abdb0396e42ad6a397d2cb2f4dc1624cc0c5b64736f6c63430007060033' + +export const SAFE_FALLBACK_HANDLER_ADDRESS = '0x017062a1dE2FE6b99BE3d9d37841FeD19F573804' +export const SAFE_FALLBACK_HANDLER_CODE = + '0x608060405234801561001057600080fd5b50600436106100ce5760003560e01c80636ac247841161008c578063bc197c8111610066578063bc197c81146107bb578063bd61951d14610951578063f23a6e6114610a63578063ffa1ad7414610b63576100ce565b80636ac24784146105ea578063a3f4df7e146106d9578063b2494df31461075c576100ce565b806223de29146100d357806301ffc9a71461020b5780630a1028c41461026e578063150b7a021461033d5780631626ba7e1461043357806320c13b0b146104e9575b600080fd5b610209600480360360c08110156100e957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561017057600080fd5b82018360208201111561018257600080fd5b803590602001918460018302840111640100000000831117156101a457600080fd5b9091929391929390803590602001906401000000008111156101c557600080fd5b8201836020820111156101d757600080fd5b803590602001918460018302840111640100000000831117156101f957600080fd5b9091929391929390505050610be6565b005b6102566004803603602081101561022157600080fd5b8101908080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19169060200190929190505050610bf0565b60405180821515815260200191505060405180910390f35b6103276004803603602081101561028457600080fd5b81019080803590602001906401000000008111156102a157600080fd5b8201836020820111156102b357600080fd5b803590602001918460018302840111640100000000831117156102d557600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610d2a565b6040518082815260200191505060405180910390f35b6103fe6004803603608081101561035357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156103ba57600080fd5b8201836020820111156103cc57600080fd5b803590602001918460018302840111640100000000831117156103ee57600080fd5b9091929391929390505050610d3d565b60405180827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060405180910390f35b6104b46004803603604081101561044957600080fd5b81019080803590602001909291908035906020019064010000000081111561047057600080fd5b82018360208201111561048257600080fd5b803590602001918460018302840111640100000000831117156104a457600080fd5b9091929391929390505050610d52565b60405180827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060405180910390f35b6105b5600480360360408110156104ff57600080fd5b810190808035906020019064010000000081111561051c57600080fd5b82018360208201111561052e57600080fd5b8035906020019184600183028401116401000000008311171561055057600080fd5b90919293919293908035906020019064010000000081111561057157600080fd5b82018360208201111561058357600080fd5b803590602001918460018302840111640100000000831117156105a557600080fd5b9091929391929390505050610f0a565b60405180827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060405180910390f35b6106c36004803603604081101561060057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019064010000000081111561063d57600080fd5b82018360208201111561064f57600080fd5b8035906020019184600183028401116401000000008311171561067157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061115b565b6040518082815260200191505060405180910390f35b6106e16112cd565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610721578082015181840152602081019050610706565b50505050905090810190601f16801561074e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610764611306565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156107a757808201518184015260208101905061078c565b505050509050019250505060405180910390f35b61091c600480360360a08110156107d157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019064010000000081111561082e57600080fd5b82018360208201111561084057600080fd5b8035906020019184602083028401116401000000008311171561086257600080fd5b90919293919293908035906020019064010000000081111561088357600080fd5b82018360208201111561089557600080fd5b803590602001918460208302840111640100000000831117156108b757600080fd5b9091929391929390803590602001906401000000008111156108d857600080fd5b8201836020820111156108ea57600080fd5b8035906020019184600183028401116401000000008311171561090c57600080fd5b909192939192939050505061146d565b60405180827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060405180910390f35b6109e86004803603604081101561096757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156109a457600080fd5b8201836020820111156109b657600080fd5b803590602001918460018302840111640100000000831117156109d857600080fd5b9091929391929390505050611485565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610a28578082015181840152602081019050610a0d565b50505050905090810190601f168015610a555780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610b2e600480360360a0811015610a7957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019092919080359060200190640100000000811115610aea57600080fd5b820183602082011115610afc57600080fd5b80359060200191846001830284011164010000000083111715610b1e57600080fd5b90919293919293905050506114ef565b60405180827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060405180910390f35b610b6b611505565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610bab578082015181840152602081019050610b90565b50505050905090810190601f168015610bd85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b5050505050505050565b60007f4e2312e0000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161480610cbb57507f150b7a02000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b80610d2357507f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b9050919050565b6000610d36338361115b565b9050919050565b600063150b7a0260e01b905095945050505050565b60008033905060008173ffffffffffffffffffffffffffffffffffffffff166320c13b0b876040516020018082815260200191505060405160208183030381529060405287876040518463ffffffff1660e01b8152600401808060200180602001838103835286818151815260200191508051906020019080838360005b83811015610deb578082015181840152602081019050610dd0565b50505050905090810190601f168015610e185780820380516001836020036101000a031916815260200191505b508381038252858582818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060206040518083038186803b158015610e6357600080fd5b505afa158015610e77573d6000803e3d6000fd5b505050506040513d6020811015610e8d57600080fd5b810190808051906020019092919050505090506320c13b0b60e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614610ef657600060e01b610eff565b631626ba7e60e01b5b925050509392505050565b6000803390506000610f608288888080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505061115b565b905060008585905014156110755760008273ffffffffffffffffffffffffffffffffffffffff16635ae6bd37836040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015610fc157600080fd5b505afa158015610fd5573d6000803e3d6000fd5b505050506040513d6020811015610feb57600080fd5b81019080805190602001909291905050501415611070576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260118152602001807f48617368206e6f7420617070726f76656400000000000000000000000000000081525060200191505060405180910390fd5b611147565b8173ffffffffffffffffffffffffffffffffffffffff1663934f3a1182898989896040518663ffffffff1660e01b81526004018086815260200180602001806020018381038352878782818152602001925080828437600081840152601f19601f8201169050808301925050508381038252858582818152602001925080828437600081840152601f19601f82011690508083019250505097505050505050505060006040518083038186803b15801561112e57600080fd5b505afa158015611142573d6000803e3d6000fd5b505050505b6320c13b0b60e01b92505050949350505050565b6000807f60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca60001b83805190602001206040516020018083815260200182815260200192505050604051602081830303815290604052805190602001209050601960f81b600160f81b8573ffffffffffffffffffffffffffffffffffffffff1663f698da256040518163ffffffff1660e01b815260040160206040518083038186803b15801561120957600080fd5b505afa15801561121d573d6000803e3d6000fd5b505050506040513d602081101561123357600080fd5b81019080805190602001909291905050508360405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600101847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526001018381526020018281526020019450505050506040516020818303038152906040528051906020012091505092915050565b6040518060400160405280601881526020017f44656661756c742043616c6c6261636b2048616e646c6572000000000000000081525081565b6060600033905060008173ffffffffffffffffffffffffffffffffffffffff1663cc2f84526001600a6040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060006040518083038186803b15801561138057600080fd5b505afa158015611394573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f8201168201806040525060408110156113be57600080fd5b81019080805160405193929190846401000000008211156113de57600080fd5b838201915060208201858111156113f457600080fd5b825186602082028301116401000000008211171561141157600080fd5b8083526020830192505050908051906020019060200280838360005b8381101561144857808201518184015260208101905061142d565b5050505090500160405260200180519060200190929190505050509050809250505090565b600063bc197c8160e01b905098975050505050505050565b60606040517fb4faba09000000000000000000000000000000000000000000000000000000008152600436036004808301376020600036836000335af15060203d036040519250808301604052806020843e6000516114e657825160208401fd5b50509392505050565b600063f23a6e6160e01b90509695505050505050565b6040518060400160405280600581526020017f312e302e300000000000000000000000000000000000000000000000000000008152508156fea26469706673582212204251d58f2a197439239faafa82818b7696d25bb75655794a81cc773a0e39ed2b64736f6c63430007060033' diff --git a/packages/cli/src/test-utils/multisigUtils.ts b/packages/cli/src/test-utils/multisigUtils.ts index 1295b0f6c..95f676080 100644 --- a/packages/cli/src/test-utils/multisigUtils.ts +++ b/packages/cli/src/test-utils/multisigUtils.ts @@ -1,7 +1,22 @@ import { multiSigABI, proxyABI } from '@celo/abis' import { StrongAddress } from '@celo/base' import { ContractKit } from '@celo/contractkit' -import { multiSigBytecode, proxyBytecode } from './constants' +import { setCode } from '@celo/dev-utils/lib/anvil-test' +import Web3 from 'web3' +import { + multiSigBytecode, + proxyBytecode, + SAFE_FALLBACK_HANDLER_ADDRESS, + SAFE_FALLBACK_HANDLER_CODE, + SAFE_MULTISEND_ADDRESS, + SAFE_MULTISEND_CALL_ONLY_ADDRESS, + SAFE_MULTISEND_CALL_ONLY_CODE, + SAFE_MULTISEND_CODE, + SAFE_PROXY_ADDRESS, + SAFE_PROXY_CODE, + SAFE_PROXY_FACTORY_ADDRESS, + SAFE_PROXY_FACTORY_CODE, +} from './constants' export async function createMultisig( kit: ContractKit, @@ -54,3 +69,12 @@ export async function createMultisig( return proxyAddress as StrongAddress } + +export const setupSafeContracts = async (web3: Web3) => { + // Set up safe 1.3.0 in devchain + await setCode(web3, SAFE_MULTISEND_ADDRESS, SAFE_MULTISEND_CODE) + await setCode(web3, SAFE_MULTISEND_CALL_ONLY_ADDRESS, SAFE_MULTISEND_CALL_ONLY_CODE) + await setCode(web3, SAFE_PROXY_FACTORY_ADDRESS, SAFE_PROXY_FACTORY_CODE) + await setCode(web3, SAFE_PROXY_ADDRESS, SAFE_PROXY_CODE) + await setCode(web3, SAFE_FALLBACK_HANDLER_ADDRESS, SAFE_FALLBACK_HANDLER_CODE) +} diff --git a/packages/cli/src/utils/cli.ts b/packages/cli/src/utils/cli.ts index 74e712f93..4d0d565ec 100644 --- a/packages/cli/src/utils/cli.ts +++ b/packages/cli/src/utils/cli.ts @@ -3,10 +3,11 @@ import { CeloTx, Connection, EventLog, - TransactionResult, parseDecodedParams, + TransactionResult, } from '@celo/connect' import { Errors, ux } from '@oclif/core' +import { TransactionResult as SafeTransactionResult } from '@safe-global/types-kit' import BigNumber from 'bignumber.js' import chalk from 'chalk' import { ethers } from 'ethers' @@ -21,6 +22,39 @@ export async function displayWeb3Tx(name: string, txObj: any, tx?: Omit { + if (!(web3.currentProvider instanceof CeloProvider)) { + throw new Error('Unexpected web3 provider') + } + + return await Safe.init({ + provider: web3.currentProvider.toEip1193Provider(), + signer, + safeAddress, + }) +} + +export const safeTransactionMetadataFromCeloTransactionObject = async ( + tx: CeloTransactionObject, + toAddress: StrongAddress +): Promise => { + return { + to: toAddress, + data: tx.txo.encodeABI(), + value: '0', + } +} + +export const performSafeTransaction = async ( + web3: Web3, + safeAddress: StrongAddress, + safeSigner: StrongAddress, + txData: MetaTransactionData +) => { + const safe = await createSafeFromWeb3(web3, safeSigner, safeAddress) + const approveTxPromise = await createApproveSafeTransactionIfNotApproved(safe, txData, safeSigner) + + if (approveTxPromise) { + await displaySafeTx('approveTx', approveTxPromise) + } + + const executeTxPromise = await createExecuteSafeTransactionIfThresholdMet(safe, txData) + + if (executeTxPromise) { + await displaySafeTx('executeTx', executeTxPromise) + } +} + +const createApproveSafeTransactionIfNotApproved = async ( + safe: Safe, + txData: MetaTransactionData, + ownerAddress: StrongAddress +): Promise => { + const txHash = await safe.getTransactionHash( + await safe.createTransaction({ + transactions: [txData], + }) + ) + + if (!(await safe.getOwnersWhoApprovedTx(txHash)).includes(ownerAddress)) { + return await safe.approveTransactionHash(txHash) + } + + return null +} + +const createExecuteSafeTransactionIfThresholdMet = async ( + safe: Safe, + txData: MetaTransactionData +): Promise => { + const tx = await safe.createTransaction({ + transactions: [txData], + }) + const txHash = await safe.getTransactionHash(tx) + + if ((await safe.getOwnersWhoApprovedTx(txHash)).length >= (await safe.getThreshold())) { + return await safe.executeTransaction(tx) + } + + return null +} diff --git a/packages/dev-utils/src/anvil-test.ts b/packages/dev-utils/src/anvil-test.ts index 56ed7c0ae..abd334d53 100644 --- a/packages/dev-utils/src/anvil-test.ts +++ b/packages/dev-utils/src/anvil-test.ts @@ -32,7 +32,7 @@ export enum LinkedLibraryAddress { Signatures = '0xe7f1725e7734ce288f8367e1bb143e90bb3f0512', } -function createInstance(stateFilePath: string): Anvil { +function createInstance(stateFilePath: string, chainId?: number): Anvil { const port = ANVIL_PORT + (process.pid - process.ppid) const options: CreateAnvilOptions = { port, @@ -43,6 +43,7 @@ function createInstance(stateFilePath: string): Anvil { gasLimit: TEST_GAS_LIMIT, blockBaseFeePerGas: 0, stopTimeout: 1000, + chainId, } instance = createAnvil(options) @@ -50,16 +51,33 @@ function createInstance(stateFilePath: string): Anvil { return instance } -export function testWithAnvilL1(name: string, fn: (web3: Web3) => void) { - return testWithAnvil(require.resolve('@celo/devchain-anvil/devchain.json'), name, fn) +type TestWithAnvilOptions = { + chainId?: number } -export function testWithAnvilL2(name: string, fn: (web3: Web3) => void) { - return testWithAnvil(require.resolve('@celo/devchain-anvil/l2-devchain.json'), name, fn) +export function testWithAnvilL1( + name: string, + fn: (web3: Web3) => void, + options?: TestWithAnvilOptions +) { + return testWithAnvil(require.resolve('@celo/devchain-anvil/devchain.json'), name, fn, options) } -function testWithAnvil(stateFilePath: string, name: string, fn: (web3: Web3) => void) { - const anvil = createInstance(stateFilePath) +export function testWithAnvilL2( + name: string, + fn: (web3: Web3) => void, + options?: TestWithAnvilOptions +) { + return testWithAnvil(require.resolve('@celo/devchain-anvil/l2-devchain.json'), name, fn, options) +} + +function testWithAnvil( + stateFilePath: string, + name: string, + fn: (web3: Web3) => void, + options?: TestWithAnvilOptions +) { + const anvil = createInstance(stateFilePath, options?.chainId) // for each test suite, we start and stop a new anvil instance return testWithWeb3(name, `http://127.0.0.1:${anvil.port}`, fn, { diff --git a/packages/sdk/connect/src/celo-provider.ts b/packages/sdk/connect/src/celo-provider.ts index fce5b071c..cde7d6301 100644 --- a/packages/sdk/connect/src/celo-provider.ts +++ b/packages/sdk/connect/src/celo-provider.ts @@ -4,6 +4,8 @@ import debugFactory from 'debug' import { Connection } from './connection' import { Callback, + Eip1193Provider, + Eip1193RequestArguments, EncodedTransaction, Error, JsonRpcPayload, @@ -168,6 +170,30 @@ export class CeloProvider implements Provider { } } + toEip1193Provider(): Eip1193Provider { + return { + request: async (args: Eip1193RequestArguments) => { + return new Promise((resolve, reject) => { + this.send( + { + id: 0, + jsonrpc: '2.0', + method: args.method, + params: args.params as any[], + }, + (error: Error | null, result: unknown) => { + if (error) { + reject(error) + } else { + resolve((result as any).result) + } + } + ) + }) + }, + } + } + private async handleAccounts(_payload: JsonRpcPayload): Promise { return this.connection.getAccounts() } diff --git a/packages/sdk/connect/src/types.ts b/packages/sdk/connect/src/types.ts index 60c65bcee..519349a7b 100644 --- a/packages/sdk/connect/src/types.ts +++ b/packages/sdk/connect/src/types.ts @@ -147,3 +147,13 @@ export interface RLPEncodedTx { rlpEncode: Hex type: TransactionTypes } + +// Based on https://eips.ethereum.org/EIPS/eip-1193 +export interface Eip1193RequestArguments { + readonly method: string + readonly params?: readonly unknown[] | object +} + +export interface Eip1193Provider { + request(args: Eip1193RequestArguments): Promise +} diff --git a/yarn.lock b/yarn.lock index 2b15d666c..569762198 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,6 +19,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:1.11.0": + version: 1.11.0 + resolution: "@adraffy/ens-normalize@npm:1.11.0" + checksum: abef75f21470ea43dd6071168e092d2d13e38067e349e76186c78838ae174a46c3e18ca50921d05bea6ec3203074147c9e271f8cb6531d1c2c0e146f3199ddcb + languageName: node + linkType: hard + "@adraffy/ens-normalize@npm:1.9.0": version: 1.9.0 resolution: "@adraffy/ens-normalize@npm:1.9.0" @@ -1715,6 +1722,8 @@ __metadata: "@oclif/plugin-not-found": "npm:^3.2.15" "@oclif/plugin-plugins": "npm:^4.3.10" "@oclif/plugin-warn-if-update-available": "npm:^3.1.11" + "@safe-global/protocol-kit": "npm:^5.0.4" + "@safe-global/types-kit": "npm:^1.0.0" "@types/command-exists": "npm:^1.2.3" "@types/debug": "npm:^4.1.4" "@types/fs-extra": "npm:^8.0.0" @@ -4521,6 +4530,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.6.0, @noble/curves@npm:~1.6.0": + version: 1.6.0 + resolution: "@noble/curves@npm:1.6.0" + dependencies: + "@noble/hashes": "npm:1.5.0" + checksum: 9090b5a020b7e38c7b6d21506afaacd0c7557129d716a174334c1efc36385bf3ca6de16a543c216db58055e019c6a6c3bea8d9c0b79386e6bacff5c4c6b438a9 + languageName: node + linkType: hard + "@noble/curves@npm:~1.4.0": version: 1.4.2 resolution: "@noble/curves@npm:1.4.2" @@ -4565,7 +4583,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.5.0": +"@noble/hashes@npm:1.5.0, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.5.0": version: 1.5.0 resolution: "@noble/hashes@npm:1.5.0" checksum: da7fc7af52af7afcf59810a7eea6155075464ff462ffda2572dc6d57d53e2669b1ea2ec774e814f6273f1697e567f28d36823776c9bf7068cba2a2855140f26e @@ -5185,6 +5203,46 @@ __metadata: languageName: node linkType: hard +"@safe-global/protocol-kit@npm:^5.0.4": + version: 5.0.4 + resolution: "@safe-global/protocol-kit@npm:5.0.4" + dependencies: + "@noble/hashes": "npm:^1.3.3" + "@safe-global/safe-deployments": "npm:^1.37.14" + "@safe-global/safe-modules-deployments": "npm:^2.2.4" + "@safe-global/types-kit": "npm:^1.0.0" + abitype: "npm:^1.0.2" + semver: "npm:^7.6.3" + viem: "npm:^2.21.8" + checksum: a6f7c64ca55dc61be1161920dca2f8aa4755d5289f14e271fc1c78c96239b478011f45d0921678dead647eed26dd3f2e2665c20f804eb05b93507142a0ef8de0 + languageName: node + linkType: hard + +"@safe-global/safe-deployments@npm:^1.37.14": + version: 1.37.14 + resolution: "@safe-global/safe-deployments@npm:1.37.14" + dependencies: + semver: "npm:^7.6.2" + checksum: f2e032238ce0ec0c786871fefcb5d75fb46fc5a25e1b0b2c47065fcea3d35bbb13dff564d9c810a6e4a0925488dd452a4ec0d1c4046b3d13c28b21c40d759acb + languageName: node + linkType: hard + +"@safe-global/safe-modules-deployments@npm:^2.2.4": + version: 2.2.4 + resolution: "@safe-global/safe-modules-deployments@npm:2.2.4" + checksum: 594a86c3c8b9b4b39379dfcc360cf81fce5bda633738f0455ce208447e0bbd01133ddb5934486e714d8115da8b5f38a1b7d2fa0fef2a04d57eb81362ef02ce6d + languageName: node + linkType: hard + +"@safe-global/types-kit@npm:^1.0.0": + version: 1.0.0 + resolution: "@safe-global/types-kit@npm:1.0.0" + dependencies: + abitype: "npm:^1.0.2" + checksum: a17024b306a3b93e1647d7d9629723e02c25e066774d7aaceca298d5e5ef49a736693ae9ed31b1a4a6fa5d0fcb97f5a08aaf059451e7929348520ceb232e9085 + languageName: node + linkType: hard + "@scure/base@npm:~1.1.0, @scure/base@npm:~1.1.4": version: 1.1.5 resolution: "@scure/base@npm:1.1.5" @@ -5192,7 +5250,7 @@ __metadata: languageName: node linkType: hard -"@scure/base@npm:~1.1.6, @scure/base@npm:~1.1.8": +"@scure/base@npm:~1.1.6, @scure/base@npm:~1.1.7, @scure/base@npm:~1.1.8": version: 1.1.9 resolution: "@scure/base@npm:1.1.9" checksum: f0ab7f687bbcdee2a01377fe3cd808bf63977999672751295b6a92625d5322f4754a96d40f6bd579bc367aad48ecf8a4e6d0390e70296e6ded1076f52adb16bb @@ -5243,6 +5301,17 @@ __metadata: languageName: node linkType: hard +"@scure/bip32@npm:1.5.0": + version: 1.5.0 + resolution: "@scure/bip32@npm:1.5.0" + dependencies: + "@noble/curves": "npm:~1.6.0" + "@noble/hashes": "npm:~1.5.0" + "@scure/base": "npm:~1.1.7" + checksum: 17e296a782e09aec18ed27e2e8bb6a76072604c40997ec49a6840f223296421612dbe6b44275f04db9acd6da6cefb0322141110f5ac9dc686eb0c44d5bd868fa + languageName: node + linkType: hard + "@scure/bip32@npm:^1.3.3": version: 1.3.3 resolution: "@scure/bip32@npm:1.3.3" @@ -7212,6 +7281,21 @@ __metadata: languageName: node linkType: hard +"abitype@npm:1.0.6, abitype@npm:^1.0.2": + version: 1.0.6 + resolution: "abitype@npm:1.0.6" + peerDependencies: + typescript: ">=5.0.4" + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + checksum: d04d58f90405c29a3c68353508502d7e870feb27418a6281ba9a13e6aaee42c26b2c5f08f648f058b8eaffac32927194b33f396d2451d18afeccfb654c7285c2 + languageName: node + linkType: hard + "abort-controller@npm:^3.0.0": version: 3.0.0 resolution: "abort-controller@npm:3.0.0" @@ -13111,6 +13195,15 @@ __metadata: languageName: node linkType: hard +"isows@npm:1.0.6": + version: 1.0.6 + resolution: "isows@npm:1.0.6" + peerDependencies: + ws: "*" + checksum: ab9e85b50bcc3d70aa5ec875aa2746c5daf9321cb376ed4e5434d3c2643c5d62b1f466d93a05cd2ad0ead5297224922748c31707cb4fbd68f5d05d0479dce99c + languageName: node + linkType: hard + "isstream@npm:~0.1.2": version: 0.1.2 resolution: "isstream@npm:0.1.2" @@ -17910,7 +18003,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.6.3": +"semver@npm:^7.6.2, semver@npm:^7.6.3": version: 7.6.3 resolution: "semver@npm:7.6.3" bin: @@ -19986,6 +20079,28 @@ __metadata: languageName: node linkType: hard +"viem@npm:^2.21.8": + version: 2.21.41 + resolution: "viem@npm:2.21.41" + dependencies: + "@adraffy/ens-normalize": "npm:1.11.0" + "@noble/curves": "npm:1.6.0" + "@noble/hashes": "npm:1.5.0" + "@scure/bip32": "npm:1.5.0" + "@scure/bip39": "npm:1.4.0" + abitype: "npm:1.0.6" + isows: "npm:1.0.6" + webauthn-p256: "npm:0.0.10" + ws: "npm:8.18.0" + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + checksum: bd3d1426584eb319c6ab69949c188d7142f6fa14b38df5ed54c967c5d5246e4eb98a9412ab7d053ff3d649df3d0174fc57f8a1e6f2803ce3aa97be2e010500b9 + languageName: node + linkType: hard + "viem@npm:~1.5.4": version: 1.5.4 resolution: "viem@npm:1.5.4" @@ -20651,6 +20766,16 @@ __metadata: languageName: node linkType: hard +"webauthn-p256@npm:0.0.10": + version: 0.0.10 + resolution: "webauthn-p256@npm:0.0.10" + dependencies: + "@noble/curves": "npm:^1.4.0" + "@noble/hashes": "npm:^1.4.0" + checksum: dde2b6313b6a0f20996f7ee90181258fc7685bfff401df7d904578da75b374f25d5b9c1189cd2fcec30625b1f276b393188d156d49783f0611623cd713bb5b09 + languageName: node + linkType: hard + "webauthn-p256@npm:0.0.5": version: 0.0.5 resolution: "webauthn-p256@npm:0.0.5" @@ -20961,6 +21086,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:8.18.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 70dfe53f23ff4368d46e4c0b1d4ca734db2c4149c6f68bc62cb16fc21f753c47b35fcc6e582f3bdfba0eaeb1c488cddab3c2255755a5c3eecb251431e42b3ff6 + languageName: node + linkType: hard + "ws@npm:8.2.3": version: 8.2.3 resolution: "ws@npm:8.2.3"