From 90aaa04668c2257452a57820520951c3aef77016 Mon Sep 17 00:00:00 2001 From: esaminu Date: Wed, 16 Feb 2022 02:50:57 +0400 Subject: [PATCH 01/13] fix: avoid deploying a contract to an account with existing state --- lib/account.js | 10 ++++++++++ lib/account_multisig.js | 11 +++++++++++ src/account.ts | 10 ++++++++++ src/account_multisig.ts | 12 +++++++++++- 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/account.js b/lib/account.js index ed5541e90f..5d980be737 100644 --- a/lib/account.js +++ b/lib/account.js @@ -257,6 +257,16 @@ class Account { * @param data The compiled contract code */ async deployContract(data) { + const contractHasExistingStateError = new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); + const currentAccountState = await this.viewState('').catch(error => { + if (error.cause?.name == 'NO_CONTRACT_CODE') { + return []; + } + throw error.cause?.name == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; + }); + if (currentAccountState.length) { + throw contractHasExistingStateError; + } return this.signAndSendTransaction({ receiverId: this.accountId, actions: [transaction_1.deployContract(data)] diff --git a/lib/account_multisig.js b/lib/account_multisig.js index 6df409e3c5..507cbd4c15 100644 --- a/lib/account_multisig.js +++ b/lib/account_multisig.js @@ -10,6 +10,7 @@ const account_1 = require("./account"); const format_1 = require("./utils/format"); const key_pair_1 = require("./utils/key_pair"); const transaction_1 = require("./transaction"); +const providers_1 = require("./providers"); const web_1 = require("./utils/web"); exports.MULTISIG_STORAGE_KEY = '__multisigRequest'; exports.MULTISIG_ALLOWANCE = new bn_js_1.default(format_1.parseNearAmount('1')); @@ -154,6 +155,16 @@ class Account2FA extends AccountMultisig { } // default helpers for CH deployments of multisig async deployMultisig(contractBytes) { + const contractHasExistingStateError = new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); + const currentAccountState = await this.viewState('').catch(error => { + if (error.cause?.name == 'NO_CONTRACT_CODE') { + return []; + } + throw error.cause?.name == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; + }); + if (currentAccountState.length) { + throw contractHasExistingStateError; + } const { accountId } = this; const seedOrLedgerKey = (await this.getRecoveryMethods()).data .filter(({ kind, publicKey }) => (kind === 'phrase' || kind === 'ledger') && publicKey !== null) diff --git a/src/account.ts b/src/account.ts index 25f1960897..f5dd887b2e 100644 --- a/src/account.ts +++ b/src/account.ts @@ -389,6 +389,16 @@ export class Account { * @param data The compiled contract code */ async deployContract(data: Uint8Array): Promise { + const contractHasExistingStateError = new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); + const currentAccountState = await this.viewState('').catch(error => { + if(error.cause?.name == 'NO_CONTRACT_CODE') { + return []; + } + throw error.cause?.name == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; + }); + if(currentAccountState.length) { + throw contractHasExistingStateError; + } return this.signAndSendTransaction({ receiverId: this.accountId, actions: [deployContract(data)] diff --git a/src/account_multisig.ts b/src/account_multisig.ts index b8d9594324..2564c409c7 100644 --- a/src/account_multisig.ts +++ b/src/account_multisig.ts @@ -7,7 +7,7 @@ import { Connection } from './connection'; import { parseNearAmount } from './utils/format'; import { PublicKey } from './utils/key_pair'; import { Action, addKey, deleteKey, deployContract, functionCall, functionCallAccessKey } from './transaction'; -import { FinalExecutionOutcome } from './providers'; +import { FinalExecutionOutcome, TypedError } from './providers'; import { fetchJson } from './utils/web'; import { FunctionCallPermissionView } from './providers/provider'; @@ -205,6 +205,16 @@ export class Account2FA extends AccountMultisig { // default helpers for CH deployments of multisig async deployMultisig(contractBytes: Uint8Array) { + const contractHasExistingStateError = new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); + const currentAccountState = await this.viewState('').catch(error => { + if(error.cause?.name == 'NO_CONTRACT_CODE') { + return []; + } + throw error.cause?.name == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; + }); + if(currentAccountState.length) { + throw contractHasExistingStateError; + } const { accountId } = this; const seedOrLedgerKey = (await this.getRecoveryMethods()).data From 17eb11af1e17b6c28d90fce88634c46c94bd17c0 Mon Sep 17 00:00:00 2001 From: esaminu Date: Wed, 16 Feb 2022 03:08:53 +0400 Subject: [PATCH 02/13] refactor: remove optional chaining operator --- lib/account.js | 5 +++-- lib/account_multisig.js | 5 +++-- src/account.ts | 5 +++-- src/account_multisig.ts | 5 +++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/account.js b/lib/account.js index 5d980be737..6b6adc53a1 100644 --- a/lib/account.js +++ b/lib/account.js @@ -259,10 +259,11 @@ class Account { async deployContract(data) { const contractHasExistingStateError = new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); const currentAccountState = await this.viewState('').catch(error => { - if (error.cause?.name == 'NO_CONTRACT_CODE') { + const cause = error.cause && error.cause.name; + if (cause == 'NO_CONTRACT_CODE') { return []; } - throw error.cause?.name == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; + throw cause == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; }); if (currentAccountState.length) { throw contractHasExistingStateError; diff --git a/lib/account_multisig.js b/lib/account_multisig.js index 507cbd4c15..5a539067ec 100644 --- a/lib/account_multisig.js +++ b/lib/account_multisig.js @@ -157,10 +157,11 @@ class Account2FA extends AccountMultisig { async deployMultisig(contractBytes) { const contractHasExistingStateError = new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); const currentAccountState = await this.viewState('').catch(error => { - if (error.cause?.name == 'NO_CONTRACT_CODE') { + const cause = error.cause && error.cause.name; + if (cause == 'NO_CONTRACT_CODE') { return []; } - throw error.cause?.name == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; + throw cause == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; }); if (currentAccountState.length) { throw contractHasExistingStateError; diff --git a/src/account.ts b/src/account.ts index f5dd887b2e..d26b84908f 100644 --- a/src/account.ts +++ b/src/account.ts @@ -391,10 +391,11 @@ export class Account { async deployContract(data: Uint8Array): Promise { const contractHasExistingStateError = new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); const currentAccountState = await this.viewState('').catch(error => { - if(error.cause?.name == 'NO_CONTRACT_CODE') { + const cause = error.cause && error.cause.name; + if(cause == 'NO_CONTRACT_CODE') { return []; } - throw error.cause?.name == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; + throw cause == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; }); if(currentAccountState.length) { throw contractHasExistingStateError; diff --git a/src/account_multisig.ts b/src/account_multisig.ts index 2564c409c7..c39585d873 100644 --- a/src/account_multisig.ts +++ b/src/account_multisig.ts @@ -207,10 +207,11 @@ export class Account2FA extends AccountMultisig { async deployMultisig(contractBytes: Uint8Array) { const contractHasExistingStateError = new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); const currentAccountState = await this.viewState('').catch(error => { - if(error.cause?.name == 'NO_CONTRACT_CODE') { + const cause = error.cause && error.cause.name; + if(cause == 'NO_CONTRACT_CODE') { return []; } - throw error.cause?.name == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; + throw cause == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; }); if(currentAccountState.length) { throw contractHasExistingStateError; From 06081705b41e37174bb15d6ed2bd107300953c44 Mon Sep 17 00:00:00 2001 From: esaminu Date: Thu, 24 Feb 2022 00:59:45 +0400 Subject: [PATCH 03/13] feat: add state cleanup on disable multisig --- lib/account.js | 11 ----------- lib/account_multisig.d.ts | 8 +++++--- lib/account_multisig.js | 37 ++++++++++++++++++++++++++++-------- src/account.ts | 11 ----------- src/account_multisig.ts | 40 ++++++++++++++++++++++++++++++--------- 5 files changed, 65 insertions(+), 42 deletions(-) diff --git a/lib/account.js b/lib/account.js index 6b6adc53a1..ed5541e90f 100644 --- a/lib/account.js +++ b/lib/account.js @@ -257,17 +257,6 @@ class Account { * @param data The compiled contract code */ async deployContract(data) { - const contractHasExistingStateError = new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); - const currentAccountState = await this.viewState('').catch(error => { - const cause = error.cause && error.cause.name; - if (cause == 'NO_CONTRACT_CODE') { - return []; - } - throw cause == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; - }); - if (currentAccountState.length) { - throw contractHasExistingStateError; - } return this.signAndSendTransaction({ receiverId: this.accountId, actions: [transaction_1.deployContract(data)] diff --git a/lib/account_multisig.d.ts b/lib/account_multisig.d.ts index 4ca8593390..efe6993edb 100644 --- a/lib/account_multisig.d.ts +++ b/lib/account_multisig.d.ts @@ -2,7 +2,7 @@ import BN from 'bn.js'; import { Account, SignAndSendTransactionOptions } from './account'; import { Connection } from './connection'; import { Action } from './transaction'; -import { FinalExecutionOutcome } from './providers'; +import { FinalExecutionOutcome, TypedError } from './providers'; export declare const MULTISIG_STORAGE_KEY = "__multisigRequest"; export declare const MULTISIG_ALLOWANCE: BN; export declare const MULTISIG_GAS: BN; @@ -19,8 +19,9 @@ export declare class AccountMultisig extends Account { signAndSendTransactionWithAccount(receiverId: string, actions: Action[]): Promise; protected signAndSendTransaction(...args: any[]): Promise; private _signAndSendTransaction; + deleteAllRequests(): Promise; deleteUnconfirmedRequests(): Promise; - getRequestIds(): Promise; + getRequestIds(): Promise; getRequest(): any; setRequest(data: any): any; } @@ -36,6 +37,7 @@ export declare class Account2FA extends AccountMultisig { verifyCode: verifyCodeFunction; onConfirmResult: Function; helperUrl: string; + contractHasExistingStateError: TypedError; constructor(connection: Connection, accountId: string, options: any); /** * Sign a transaction to preform a list of actions and broadcast it using the RPC API. @@ -53,7 +55,7 @@ export declare class Account2FA extends AccountMultisig { signAndSendTransaction(receiverId: string, actions: Action[]): Promise; private __signAndSendTransaction; deployMultisig(contractBytes: Uint8Array): Promise; - disable(contractBytes: Uint8Array): Promise; + disable(contractBytes: Uint8Array, cleanupContractBytes: Uint8Array): Promise; sendCodeDefault(): Promise; getCodeDefault(method: any): Promise; promptAndVerify(): any; diff --git a/lib/account_multisig.js b/lib/account_multisig.js index 5a539067ec..a1189bbea4 100644 --- a/lib/account_multisig.js +++ b/lib/account_multisig.js @@ -82,6 +82,15 @@ class AccountMultisig extends account_1.Account { this.deleteUnconfirmedRequests(); return result; } + async deleteAllRequests() { + const request_ids = await this.getRequestIds(); + if (request_ids.length) { + await super.signAndSendTransaction({ + receiverId: this.accountId, + actions: request_ids.map(requestIdToDelete => transaction_1.functionCall('delete_request', { request_id: requestIdToDelete }, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT)) + }); + } + } async deleteUnconfirmedRequests() { // TODO: Delete in batch, don't delete unexpired // TODO: Delete in batch, don't delete unexpired (can reduce gas usage dramatically) @@ -126,6 +135,7 @@ class Account2FA extends AccountMultisig { constructor(connection, accountId, options) { super(connection, accountId, options); this.helperUrl = 'https://helper.testnet.near.org'; + this.contractHasExistingStateError = new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); this.helperUrl = options.helperUrl || this.helperUrl; this.storage = options.storage; this.sendCode = options.sendCode || this.sendCodeDefault; @@ -155,16 +165,15 @@ class Account2FA extends AccountMultisig { } // default helpers for CH deployments of multisig async deployMultisig(contractBytes) { - const contractHasExistingStateError = new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); const currentAccountState = await this.viewState('').catch(error => { const cause = error.cause && error.cause.name; if (cause == 'NO_CONTRACT_CODE') { return []; } - throw cause == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; + throw cause == 'TOO_LARGE_CONTRACT_STATE' ? this.contractHasExistingStateError : error; }); - if (currentAccountState.length) { - throw contractHasExistingStateError; + if (currentAccountState.length) { // TODO add check that tries to deserialize existing state and passes if it can + throw this.contractHasExistingStateError; } const { accountId } = this; const seedOrLedgerKey = (await this.getRecoveryMethods()).data @@ -181,14 +190,12 @@ class Account2FA extends AccountMultisig { ...fak2lak.map((pk) => transaction_1.addKey(pk, transaction_1.functionCallAccessKey(accountId, exports.MULTISIG_CHANGE_METHODS, null))), transaction_1.addKey(confirmOnlyKey, transaction_1.functionCallAccessKey(accountId, exports.MULTISIG_CONFIRM_METHODS, null)), transaction_1.deployContract(contractBytes), + transaction_1.functionCall('new', newArgs, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT) ]; - if ((await this.state()).code_hash === '11111111111111111111111111111111') { - actions.push(transaction_1.functionCall('new', newArgs, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT)); - } console.log('deploying multisig contract for', accountId); return await super.signAndSendTransactionWithAccount(accountId, actions); } - async disable(contractBytes) { + async disable(contractBytes, cleanupContractBytes) { const { accountId } = this; const accessKeys = await this.getAccessKeys(); const lak2fak = accessKeys @@ -200,7 +207,21 @@ class Account2FA extends AccountMultisig { perm.method_names.includes('add_request_and_confirm'); }); const confirmOnlyKey = key_pair_1.PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); + await this.deleteAllRequests(); + const currentAccountState = await this.viewState('').catch(error => { + const cause = error.cause && error.cause.name; + if (cause == 'NO_CONTRACT_CODE') { + return []; + } + throw cause == 'TOO_LARGE_CONTRACT_STATE' ? this.contractHasExistingStateError : error; + }); + const currentAccountStateKeys = currentAccountState.map(({ key }) => key.toString('base64')); + const cleanupActions = currentAccountState.length ? [ + transaction_1.deployContract(cleanupContractBytes), + transaction_1.functionCall('clean', { keys: currentAccountStateKeys }, exports.MULTISIG_GAS, new bn_js_1.default('0')) + ] : []; const actions = [ + ...cleanupActions, transaction_1.deleteKey(confirmOnlyKey), ...lak2fak.map(({ public_key }) => transaction_1.deleteKey(key_pair_1.PublicKey.from(public_key))), ...lak2fak.map(({ public_key }) => transaction_1.addKey(key_pair_1.PublicKey.from(public_key), null)), diff --git a/src/account.ts b/src/account.ts index d26b84908f..25f1960897 100644 --- a/src/account.ts +++ b/src/account.ts @@ -389,17 +389,6 @@ export class Account { * @param data The compiled contract code */ async deployContract(data: Uint8Array): Promise { - const contractHasExistingStateError = new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); - const currentAccountState = await this.viewState('').catch(error => { - const cause = error.cause && error.cause.name; - if(cause == 'NO_CONTRACT_CODE') { - return []; - } - throw cause == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; - }); - if(currentAccountState.length) { - throw contractHasExistingStateError; - } return this.signAndSendTransaction({ receiverId: this.accountId, actions: [deployContract(data)] diff --git a/src/account_multisig.ts b/src/account_multisig.ts index c39585d873..45ddb4de54 100644 --- a/src/account_multisig.ts +++ b/src/account_multisig.ts @@ -101,6 +101,16 @@ export class AccountMultisig extends Account { return result; } + async deleteAllRequests() { + const request_ids = await this.getRequestIds(); + if(request_ids.length) { + await super.signAndSendTransaction({ + receiverId: this.accountId, + actions: request_ids.map(requestIdToDelete => functionCall('delete_request', { request_id: requestIdToDelete }, MULTISIG_GAS, MULTISIG_DEPOSIT)) + }) + } + } + async deleteUnconfirmedRequests () { // TODO: Delete in batch, don't delete unexpired // TODO: Delete in batch, don't delete unexpired (can reduce gas usage dramatically) @@ -123,7 +133,7 @@ export class AccountMultisig extends Account { // helpers - async getRequestIds(): Promise { + async getRequestIds(): Promise { // TODO: Read requests from state to allow filtering by expiration time // TODO: https://github.com/near/core-contracts/blob/305d1db4f4f2cf5ce4c1ef3479f7544957381f11/multisig/src/lib.rs#L84 return this.viewFunction(this.accountId, 'list_request_ids'); @@ -156,6 +166,7 @@ export class Account2FA extends AccountMultisig { public verifyCode: verifyCodeFunction; public onConfirmResult: Function; public helperUrl = 'https://helper.testnet.near.org'; + public contractHasExistingStateError = new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); constructor(connection: Connection, accountId: string, options: any) { super(connection, accountId, options); @@ -205,16 +216,15 @@ export class Account2FA extends AccountMultisig { // default helpers for CH deployments of multisig async deployMultisig(contractBytes: Uint8Array) { - const contractHasExistingStateError = new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); const currentAccountState = await this.viewState('').catch(error => { const cause = error.cause && error.cause.name; if(cause == 'NO_CONTRACT_CODE') { return []; } - throw cause == 'TOO_LARGE_CONTRACT_STATE' ? contractHasExistingStateError : error; + throw cause == 'TOO_LARGE_CONTRACT_STATE' ? this.contractHasExistingStateError : error; }); - if(currentAccountState.length) { - throw contractHasExistingStateError; + if(currentAccountState.length) { // TODO add check that tries to deserialize existing state and passes if it can + throw this.contractHasExistingStateError; } const { accountId } = this; @@ -236,15 +246,13 @@ export class Account2FA extends AccountMultisig { ...fak2lak.map((pk) => addKey(pk, functionCallAccessKey(accountId, MULTISIG_CHANGE_METHODS, null))), addKey(confirmOnlyKey, functionCallAccessKey(accountId, MULTISIG_CONFIRM_METHODS, null)), deployContract(contractBytes), + functionCall('new', newArgs, MULTISIG_GAS, MULTISIG_DEPOSIT) ]; - if ((await this.state()).code_hash === '11111111111111111111111111111111') { - actions.push(functionCall('new', newArgs, MULTISIG_GAS, MULTISIG_DEPOSIT),); - } console.log('deploying multisig contract for', accountId); return await super.signAndSendTransactionWithAccount(accountId, actions); } - async disable(contractBytes: Uint8Array) { + async disable(contractBytes: Uint8Array, cleanupContractBytes: Uint8Array) { const { accountId } = this; const accessKeys = await this.getAccessKeys(); const lak2fak = accessKeys @@ -256,7 +264,21 @@ export class Account2FA extends AccountMultisig { perm.method_names.includes('add_request_and_confirm'); }); const confirmOnlyKey = PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); + await this.deleteAllRequests(); + const currentAccountState: { key: Buffer, value: Buffer }[] = await this.viewState('').catch(error => { + const cause = error.cause && error.cause.name; + if (cause == 'NO_CONTRACT_CODE') { + return []; + } + throw cause == 'TOO_LARGE_CONTRACT_STATE' ? this.contractHasExistingStateError : error; + }); + const currentAccountStateKeys = currentAccountState.map(({ key }) => key.toString('base64')) + const cleanupActions = currentAccountState.length ? [ + deployContract(cleanupContractBytes), + functionCall('clean', { keys: currentAccountStateKeys }, MULTISIG_GAS, new BN('0')) + ] : []; const actions = [ + ...cleanupActions, deleteKey(confirmOnlyKey), ...lak2fak.map(({ public_key }) => deleteKey(PublicKey.from(public_key))), ...lak2fak.map(({ public_key }) => addKey(PublicKey.from(public_key), null)), From 746cea9c7cde319fd7c420fe2aa549df29de7377 Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 4 Mar 2022 20:37:45 +0400 Subject: [PATCH 04/13] fix: add catch addRequest on deployMultisig --- lib/account_multisig.js | 30 +++++++++++++++++------------- src/account_multisig.ts | 30 +++++++++++++++++------------- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/lib/account_multisig.js b/lib/account_multisig.js index a1189bbea4..ec753c3c22 100644 --- a/lib/account_multisig.js +++ b/lib/account_multisig.js @@ -19,6 +19,7 @@ exports.MULTISIG_GAS = new bn_js_1.default('100000000000000'); exports.MULTISIG_DEPOSIT = new bn_js_1.default('0'); exports.MULTISIG_CHANGE_METHODS = ['add_request', 'add_request_and_confirm', 'delete_request', 'confirm']; exports.MULTISIG_CONFIRM_METHODS = ['confirm']; +const SHOULD_BE_INITIALIZED_ERROR_REGEX = /Smart contract panicked: Multisig contract should be initialized before usage/; // in memory request cache for node w/o localStorage const storageFallback = { [exports.MULTISIG_STORAGE_KEY]: null @@ -165,16 +166,6 @@ class Account2FA extends AccountMultisig { } // default helpers for CH deployments of multisig async deployMultisig(contractBytes) { - const currentAccountState = await this.viewState('').catch(error => { - const cause = error.cause && error.cause.name; - if (cause == 'NO_CONTRACT_CODE') { - return []; - } - throw cause == 'TOO_LARGE_CONTRACT_STATE' ? this.contractHasExistingStateError : error; - }); - if (currentAccountState.length) { // TODO add check that tries to deserialize existing state and passes if it can - throw this.contractHasExistingStateError; - } const { accountId } = this; const seedOrLedgerKey = (await this.getRecoveryMethods()).data .filter(({ kind, publicKey }) => (kind === 'phrase' || kind === 'ledger') && publicKey !== null) @@ -185,15 +176,28 @@ class Account2FA extends AccountMultisig { .map(toPK); const confirmOnlyKey = toPK((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); const newArgs = Buffer.from(JSON.stringify({ 'num_confirmations': 2 })); + const emptyRequest = Buffer.from(JSON.stringify({ + request: { + receiver_id: accountId, + actions: convertActions([], accountId, accountId) + } + })); const actions = [ ...fak2lak.map((pk) => transaction_1.deleteKey(pk)), ...fak2lak.map((pk) => transaction_1.addKey(pk, transaction_1.functionCallAccessKey(accountId, exports.MULTISIG_CHANGE_METHODS, null))), transaction_1.addKey(confirmOnlyKey, transaction_1.functionCallAccessKey(accountId, exports.MULTISIG_CONFIRM_METHODS, null)), transaction_1.deployContract(contractBytes), - transaction_1.functionCall('new', newArgs, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT) ]; + const addRequestFunctionCallActionBatch = actions.concat(transaction_1.functionCall('add_request', emptyRequest, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT)); + const newFunctionCallActionBatch = actions.concat(transaction_1.functionCall('new', newArgs, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT)); console.log('deploying multisig contract for', accountId); - return await super.signAndSendTransactionWithAccount(accountId, actions); + return await super.signAndSendTransactionWithAccount(accountId, addRequestFunctionCallActionBatch) + .catch(error => { + if (SHOULD_BE_INITIALIZED_ERROR_REGEX.test(error?.kind?.ExecutionError)) { + return super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); + } + throw error; + }); } async disable(contractBytes, cleanupContractBytes) { const { accountId } = this; @@ -207,7 +211,7 @@ class Account2FA extends AccountMultisig { perm.method_names.includes('add_request_and_confirm'); }); const confirmOnlyKey = key_pair_1.PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - await this.deleteAllRequests(); + await this.deleteAllRequests().catch(e => e); const currentAccountState = await this.viewState('').catch(error => { const cause = error.cause && error.cause.name; if (cause == 'NO_CONTRACT_CODE') { diff --git a/src/account_multisig.ts b/src/account_multisig.ts index 45ddb4de54..a8c945ba5e 100644 --- a/src/account_multisig.ts +++ b/src/account_multisig.ts @@ -18,6 +18,7 @@ export const MULTISIG_GAS = new BN('100000000000000'); export const MULTISIG_DEPOSIT = new BN('0'); export const MULTISIG_CHANGE_METHODS = ['add_request', 'add_request_and_confirm', 'delete_request', 'confirm']; export const MULTISIG_CONFIRM_METHODS = ['confirm']; +const SHOULD_BE_INITIALIZED_ERROR_REGEX = /Smart contract panicked: Multisig contract should be initialized before usage/; type sendCodeFunction = () => Promise; type getCodeFunction = (method: any) => Promise; @@ -216,16 +217,6 @@ export class Account2FA extends AccountMultisig { // default helpers for CH deployments of multisig async deployMultisig(contractBytes: Uint8Array) { - const currentAccountState = await this.viewState('').catch(error => { - const cause = error.cause && error.cause.name; - if(cause == 'NO_CONTRACT_CODE') { - return []; - } - throw cause == 'TOO_LARGE_CONTRACT_STATE' ? this.contractHasExistingStateError : error; - }); - if(currentAccountState.length) { // TODO add check that tries to deserialize existing state and passes if it can - throw this.contractHasExistingStateError; - } const { accountId } = this; const seedOrLedgerKey = (await this.getRecoveryMethods()).data @@ -240,16 +231,29 @@ export class Account2FA extends AccountMultisig { const confirmOnlyKey = toPK((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); const newArgs = Buffer.from(JSON.stringify({ 'num_confirmations': 2 })); + const emptyRequest = Buffer.from(JSON.stringify({ + request: { + receiver_id: accountId, + actions: convertActions([], accountId, accountId) + } + })); const actions = [ ...fak2lak.map((pk) => deleteKey(pk)), ...fak2lak.map((pk) => addKey(pk, functionCallAccessKey(accountId, MULTISIG_CHANGE_METHODS, null))), addKey(confirmOnlyKey, functionCallAccessKey(accountId, MULTISIG_CONFIRM_METHODS, null)), deployContract(contractBytes), - functionCall('new', newArgs, MULTISIG_GAS, MULTISIG_DEPOSIT) ]; + const addRequestFunctionCallActionBatch = actions.concat(functionCall('add_request', emptyRequest, MULTISIG_GAS, MULTISIG_DEPOSIT)); + const newFunctionCallActionBatch = actions.concat(functionCall('new', newArgs, MULTISIG_GAS, MULTISIG_DEPOSIT)) console.log('deploying multisig contract for', accountId); - return await super.signAndSendTransactionWithAccount(accountId, actions); + return await super.signAndSendTransactionWithAccount(accountId, addRequestFunctionCallActionBatch) + .catch(error => { + if (SHOULD_BE_INITIALIZED_ERROR_REGEX.test(error?.kind?.ExecutionError)) { + return super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); + } + throw error; + }); } async disable(contractBytes: Uint8Array, cleanupContractBytes: Uint8Array) { @@ -264,7 +268,7 @@ export class Account2FA extends AccountMultisig { perm.method_names.includes('add_request_and_confirm'); }); const confirmOnlyKey = PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - await this.deleteAllRequests(); + await this.deleteAllRequests().catch(e => e); const currentAccountState: { key: Buffer, value: Buffer }[] = await this.viewState('').catch(error => { const cause = error.cause && error.cause.name; if (cause == 'NO_CONTRACT_CODE') { From c299da03c3a142a7dd19b8c72b37d4faf7399054 Mon Sep 17 00:00:00 2001 From: esaminu Date: Sat, 5 Mar 2022 01:33:17 +0400 Subject: [PATCH 05/13] fix: add test transaction to determine contract state --- lib/account_multisig.d.ts | 6 +++++ lib/account_multisig.js | 56 ++++++++++++++++++++++++++++----------- src/account_multisig.ts | 55 +++++++++++++++++++++++++++----------- 3 files changed, 87 insertions(+), 30 deletions(-) diff --git a/lib/account_multisig.d.ts b/lib/account_multisig.d.ts index efe6993edb..50c14e73ab 100644 --- a/lib/account_multisig.d.ts +++ b/lib/account_multisig.d.ts @@ -12,6 +12,11 @@ export declare const MULTISIG_CONFIRM_METHODS: string[]; declare type sendCodeFunction = () => Promise; declare type getCodeFunction = (method: any) => Promise; declare type verifyCodeFunction = (securityCode: any) => Promise; +declare enum MultisigStateStatus { + INVALID_STATE = 0, + NOT_INITIALIZED = 1, + VALID = 2 +} export declare class AccountMultisig extends Account { storage: any; onAddRequestResult: Function; @@ -19,6 +24,7 @@ export declare class AccountMultisig extends Account { signAndSendTransactionWithAccount(receiverId: string, actions: Action[]): Promise; protected signAndSendTransaction(...args: any[]): Promise; private _signAndSendTransaction; + checkMultisigStateStatus(contractBytes: Uint8Array): Promise; deleteAllRequests(): Promise; deleteUnconfirmedRequests(): Promise; getRequestIds(): Promise; diff --git a/lib/account_multisig.js b/lib/account_multisig.js index ec753c3c22..8e27257793 100644 --- a/lib/account_multisig.js +++ b/lib/account_multisig.js @@ -19,7 +19,19 @@ exports.MULTISIG_GAS = new bn_js_1.default('100000000000000'); exports.MULTISIG_DEPOSIT = new bn_js_1.default('0'); exports.MULTISIG_CHANGE_METHODS = ['add_request', 'add_request_and_confirm', 'delete_request', 'confirm']; exports.MULTISIG_CONFIRM_METHODS = ['confirm']; -const SHOULD_BE_INITIALIZED_ERROR_REGEX = /Smart contract panicked: Multisig contract should be initialized before usage/; +var MultisigDeleteRequestRejectionError; +(function (MultisigDeleteRequestRejectionError) { + MultisigDeleteRequestRejectionError["CANNOT_DESERIALIZE_STATE"] = "Smart contract panicked: panicked at 'Cannot deserialize the contract state.: Custom { kind: InvalidInput, error: \"Unexpected length of input\" }'"; + MultisigDeleteRequestRejectionError["MULTISIG_NOT_INITIALIZED"] = "Smart contract panicked: Multisig contract should be initialized before usage"; + MultisigDeleteRequestRejectionError["NO_SUCH_REQUEST"] = "Smart contract panicked: panicked at 'No such request: either wrong number or already confirmed'"; +})(MultisigDeleteRequestRejectionError || (MultisigDeleteRequestRejectionError = {})); +; +var MultisigStateStatus; +(function (MultisigStateStatus) { + MultisigStateStatus[MultisigStateStatus["INVALID_STATE"] = 0] = "INVALID_STATE"; + MultisigStateStatus[MultisigStateStatus["NOT_INITIALIZED"] = 1] = "NOT_INITIALIZED"; + MultisigStateStatus[MultisigStateStatus["VALID"] = 2] = "VALID"; +})(MultisigStateStatus || (MultisigStateStatus = {})); // in memory request cache for node w/o localStorage const storageFallback = { [exports.MULTISIG_STORAGE_KEY]: null @@ -83,6 +95,28 @@ class AccountMultisig extends account_1.Account { this.deleteUnconfirmedRequests(); return result; } + async checkMultisigStateStatus(contractBytes) { + const u32_max = 4294967295; + return super.signAndSendTransaction({ + receiverId: this.accountId, actions: [ + transaction_1.deployContract(contractBytes), + transaction_1.functionCall('delete_request', { request_id: u32_max }, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT) + ] + }) + .then(() => MultisigStateStatus.VALID) + .catch((e) => { + if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e?.kind?.ExecutionError)) { + return MultisigStateStatus.INVALID_STATE; + } + else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e?.kind?.ExecutionError)) { + return MultisigStateStatus.NOT_INITIALIZED; + } + else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e?.kind?.ExecutionError)) { + return MultisigStateStatus.VALID; + } + throw e; + }); + } async deleteAllRequests() { const request_ids = await this.getRequestIds(); if (request_ids.length) { @@ -176,28 +210,20 @@ class Account2FA extends AccountMultisig { .map(toPK); const confirmOnlyKey = toPK((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); const newArgs = Buffer.from(JSON.stringify({ 'num_confirmations': 2 })); - const emptyRequest = Buffer.from(JSON.stringify({ - request: { - receiver_id: accountId, - actions: convertActions([], accountId, accountId) - } - })); const actions = [ ...fak2lak.map((pk) => transaction_1.deleteKey(pk)), ...fak2lak.map((pk) => transaction_1.addKey(pk, transaction_1.functionCallAccessKey(accountId, exports.MULTISIG_CHANGE_METHODS, null))), transaction_1.addKey(confirmOnlyKey, transaction_1.functionCallAccessKey(accountId, exports.MULTISIG_CONFIRM_METHODS, null)), transaction_1.deployContract(contractBytes), ]; - const addRequestFunctionCallActionBatch = actions.concat(transaction_1.functionCall('add_request', emptyRequest, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT)); const newFunctionCallActionBatch = actions.concat(transaction_1.functionCall('new', newArgs, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT)); console.log('deploying multisig contract for', accountId); - return await super.signAndSendTransactionWithAccount(accountId, addRequestFunctionCallActionBatch) - .catch(error => { - if (SHOULD_BE_INITIALIZED_ERROR_REGEX.test(error?.kind?.ExecutionError)) { - return super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); - } - throw error; - }); + const multisigStateStatus = await this.checkMultisigStateStatus(contractBytes); + switch (multisigStateStatus) { + case MultisigStateStatus.NOT_INITIALIZED: return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); + case MultisigStateStatus.VALID: return await super.signAndSendTransactionWithAccount(accountId, actions); + case MultisigStateStatus.INVALID_STATE: throw this.contractHasExistingStateError; + } } async disable(contractBytes, cleanupContractBytes) { const { accountId } = this; diff --git a/src/account_multisig.ts b/src/account_multisig.ts index a8c945ba5e..404289f653 100644 --- a/src/account_multisig.ts +++ b/src/account_multisig.ts @@ -18,12 +18,23 @@ export const MULTISIG_GAS = new BN('100000000000000'); export const MULTISIG_DEPOSIT = new BN('0'); export const MULTISIG_CHANGE_METHODS = ['add_request', 'add_request_and_confirm', 'delete_request', 'confirm']; export const MULTISIG_CONFIRM_METHODS = ['confirm']; -const SHOULD_BE_INITIALIZED_ERROR_REGEX = /Smart contract panicked: Multisig contract should be initialized before usage/; type sendCodeFunction = () => Promise; type getCodeFunction = (method: any) => Promise; type verifyCodeFunction = (securityCode: any) => Promise; +enum MultisigDeleteRequestRejectionError { + CANNOT_DESERIALIZE_STATE = `Smart contract panicked: panicked at 'Cannot deserialize the contract state\.: Custom { kind: InvalidInput, error: "Unexpected length of input" }'`, + MULTISIG_NOT_INITIALIZED = `Smart contract panicked: Multisig contract should be initialized before usage`, + NO_SUCH_REQUEST = `Smart contract panicked: panicked at 'No such request: either wrong number or already confirmed'` +}; + +enum MultisigStateStatus { + INVALID_STATE, + NOT_INITIALIZED, + VALID +} + // in memory request cache for node w/o localStorage const storageFallback = { [MULTISIG_STORAGE_KEY]: null @@ -102,6 +113,27 @@ export class AccountMultisig extends Account { return result; } + async checkMultisigStateStatus(contractBytes: Uint8Array): Promise { + const u32_max = 4_294_967_295; + return super.signAndSendTransaction({ + receiverId: this.accountId, actions: [ + deployContract(contractBytes), + functionCall('delete_request', { request_id: u32_max }, MULTISIG_GAS, MULTISIG_DEPOSIT) + ] + }) + .then(() => MultisigStateStatus.VALID) + .catch((e) => { + if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e?.kind?.ExecutionError)) { + return MultisigStateStatus.INVALID_STATE; + } else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e?.kind?.ExecutionError)) { + return MultisigStateStatus.NOT_INITIALIZED; + } else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e?.kind?.ExecutionError)) { + return MultisigStateStatus.VALID; + } + throw e; + }) + } + async deleteAllRequests() { const request_ids = await this.getRequestIds(); if(request_ids.length) { @@ -231,12 +263,6 @@ export class Account2FA extends AccountMultisig { const confirmOnlyKey = toPK((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); const newArgs = Buffer.from(JSON.stringify({ 'num_confirmations': 2 })); - const emptyRequest = Buffer.from(JSON.stringify({ - request: { - receiver_id: accountId, - actions: convertActions([], accountId, accountId) - } - })); const actions = [ ...fak2lak.map((pk) => deleteKey(pk)), @@ -244,16 +270,15 @@ export class Account2FA extends AccountMultisig { addKey(confirmOnlyKey, functionCallAccessKey(accountId, MULTISIG_CONFIRM_METHODS, null)), deployContract(contractBytes), ]; - const addRequestFunctionCallActionBatch = actions.concat(functionCall('add_request', emptyRequest, MULTISIG_GAS, MULTISIG_DEPOSIT)); const newFunctionCallActionBatch = actions.concat(functionCall('new', newArgs, MULTISIG_GAS, MULTISIG_DEPOSIT)) console.log('deploying multisig contract for', accountId); - return await super.signAndSendTransactionWithAccount(accountId, addRequestFunctionCallActionBatch) - .catch(error => { - if (SHOULD_BE_INITIALIZED_ERROR_REGEX.test(error?.kind?.ExecutionError)) { - return super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); - } - throw error; - }); + + const multisigStateStatus = await this.checkMultisigStateStatus(contractBytes); + switch (multisigStateStatus) { + case MultisigStateStatus.NOT_INITIALIZED: return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); + case MultisigStateStatus.VALID: return await super.signAndSendTransactionWithAccount(accountId, actions); + case MultisigStateStatus.INVALID_STATE: throw this.contractHasExistingStateError; + } } async disable(contractBytes: Uint8Array, cleanupContractBytes: Uint8Array) { From 394a41ea6772fcd880cf2332fe4119fbb0688c28 Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Tue, 15 Mar 2022 23:37:46 +0400 Subject: [PATCH 06/13] refactor: format switch statement Co-authored-by: Daryl Collins --- src/account_multisig.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/account_multisig.ts b/src/account_multisig.ts index 404289f653..18a229ce1f 100644 --- a/src/account_multisig.ts +++ b/src/account_multisig.ts @@ -275,9 +275,12 @@ export class Account2FA extends AccountMultisig { const multisigStateStatus = await this.checkMultisigStateStatus(contractBytes); switch (multisigStateStatus) { - case MultisigStateStatus.NOT_INITIALIZED: return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); - case MultisigStateStatus.VALID: return await super.signAndSendTransactionWithAccount(accountId, actions); - case MultisigStateStatus.INVALID_STATE: throw this.contractHasExistingStateError; + case MultisigStateStatus.NOT_INITIALIZED: + return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); + case MultisigStateStatus.VALID: + return await super.signAndSendTransactionWithAccount(accountId, actions); + case MultisigStateStatus.INVALID_STATE: + throw this.contractHasExistingStateError; } } From 6d57d90c6afba774ca344dc0957b632df1daec86 Mon Sep 17 00:00:00 2001 From: esaminu Date: Wed, 16 Mar 2022 00:29:06 +0400 Subject: [PATCH 07/13] refactor: inline contractHasExistingStateError --- lib/account_multisig.d.ts | 3 +-- lib/account_multisig.js | 14 +++++++++----- src/account_multisig.ts | 7 ++++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/account_multisig.d.ts b/lib/account_multisig.d.ts index 50c14e73ab..2f74786a8f 100644 --- a/lib/account_multisig.d.ts +++ b/lib/account_multisig.d.ts @@ -2,7 +2,7 @@ import BN from 'bn.js'; import { Account, SignAndSendTransactionOptions } from './account'; import { Connection } from './connection'; import { Action } from './transaction'; -import { FinalExecutionOutcome, TypedError } from './providers'; +import { FinalExecutionOutcome } from './providers'; export declare const MULTISIG_STORAGE_KEY = "__multisigRequest"; export declare const MULTISIG_ALLOWANCE: BN; export declare const MULTISIG_GAS: BN; @@ -43,7 +43,6 @@ export declare class Account2FA extends AccountMultisig { verifyCode: verifyCodeFunction; onConfirmResult: Function; helperUrl: string; - contractHasExistingStateError: TypedError; constructor(connection: Connection, accountId: string, options: any); /** * Sign a transaction to preform a list of actions and broadcast it using the RPC API. diff --git a/lib/account_multisig.js b/lib/account_multisig.js index 8e27257793..4529256ffe 100644 --- a/lib/account_multisig.js +++ b/lib/account_multisig.js @@ -170,7 +170,6 @@ class Account2FA extends AccountMultisig { constructor(connection, accountId, options) { super(connection, accountId, options); this.helperUrl = 'https://helper.testnet.near.org'; - this.contractHasExistingStateError = new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); this.helperUrl = options.helperUrl || this.helperUrl; this.storage = options.storage; this.sendCode = options.sendCode || this.sendCodeDefault; @@ -220,9 +219,12 @@ class Account2FA extends AccountMultisig { console.log('deploying multisig contract for', accountId); const multisigStateStatus = await this.checkMultisigStateStatus(contractBytes); switch (multisigStateStatus) { - case MultisigStateStatus.NOT_INITIALIZED: return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); - case MultisigStateStatus.VALID: return await super.signAndSendTransactionWithAccount(accountId, actions); - case MultisigStateStatus.INVALID_STATE: throw this.contractHasExistingStateError; + case MultisigStateStatus.NOT_INITIALIZED: + return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); + case MultisigStateStatus.VALID: + return await super.signAndSendTransactionWithAccount(accountId, actions); + case MultisigStateStatus.INVALID_STATE: + throw new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); } } async disable(contractBytes, cleanupContractBytes) { @@ -243,7 +245,9 @@ class Account2FA extends AccountMultisig { if (cause == 'NO_CONTRACT_CODE') { return []; } - throw cause == 'TOO_LARGE_CONTRACT_STATE' ? this.contractHasExistingStateError : error; + throw cause == 'TOO_LARGE_CONTRACT_STATE' ? + new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState') : + error; }); const currentAccountStateKeys = currentAccountState.map(({ key }) => key.toString('base64')); const cleanupActions = currentAccountState.length ? [ diff --git a/src/account_multisig.ts b/src/account_multisig.ts index 18a229ce1f..9b482c85c4 100644 --- a/src/account_multisig.ts +++ b/src/account_multisig.ts @@ -199,7 +199,6 @@ export class Account2FA extends AccountMultisig { public verifyCode: verifyCodeFunction; public onConfirmResult: Function; public helperUrl = 'https://helper.testnet.near.org'; - public contractHasExistingStateError = new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); constructor(connection: Connection, accountId: string, options: any) { super(connection, accountId, options); @@ -280,7 +279,7 @@ export class Account2FA extends AccountMultisig { case MultisigStateStatus.VALID: return await super.signAndSendTransactionWithAccount(accountId, actions); case MultisigStateStatus.INVALID_STATE: - throw this.contractHasExistingStateError; + throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); } } @@ -302,7 +301,9 @@ export class Account2FA extends AccountMultisig { if (cause == 'NO_CONTRACT_CODE') { return []; } - throw cause == 'TOO_LARGE_CONTRACT_STATE' ? this.contractHasExistingStateError : error; + throw cause == 'TOO_LARGE_CONTRACT_STATE' ? + new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState') : + error; }); const currentAccountStateKeys = currentAccountState.map(({ key }) => key.toString('base64')) const cleanupActions = currentAccountState.length ? [ From 249a1ba0f16dc82ed7f4eedd559a676a61d153c4 Mon Sep 17 00:00:00 2001 From: esaminu Date: Thu, 17 Mar 2022 04:17:37 +0400 Subject: [PATCH 08/13] fix: update multisig errors and fix gas issues --- lib/account_multisig.js | 21 ++++++++++++++++----- src/account_multisig.ts | 27 ++++++++++++++++++++------- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/lib/account_multisig.js b/lib/account_multisig.js index 4529256ffe..8f180b1084 100644 --- a/lib/account_multisig.js +++ b/lib/account_multisig.js @@ -21,9 +21,10 @@ exports.MULTISIG_CHANGE_METHODS = ['add_request', 'add_request_and_confirm', 'de exports.MULTISIG_CONFIRM_METHODS = ['confirm']; var MultisigDeleteRequestRejectionError; (function (MultisigDeleteRequestRejectionError) { - MultisigDeleteRequestRejectionError["CANNOT_DESERIALIZE_STATE"] = "Smart contract panicked: panicked at 'Cannot deserialize the contract state.: Custom { kind: InvalidInput, error: \"Unexpected length of input\" }'"; + MultisigDeleteRequestRejectionError["CANNOT_DESERIALIZE_STATE"] = "Cannot deserialize the contract state"; MultisigDeleteRequestRejectionError["MULTISIG_NOT_INITIALIZED"] = "Smart contract panicked: Multisig contract should be initialized before usage"; MultisigDeleteRequestRejectionError["NO_SUCH_REQUEST"] = "Smart contract panicked: panicked at 'No such request: either wrong number or already confirmed'"; + MultisigDeleteRequestRejectionError["REQUEST_COOLDOWN_ERROR"] = "Request cannot be deleted immediately after creation."; })(MultisigDeleteRequestRejectionError || (MultisigDeleteRequestRejectionError = {})); ; var MultisigStateStatus; @@ -95,6 +96,11 @@ class AccountMultisig extends account_1.Account { this.deleteUnconfirmedRequests(); return result; } + /* + * This method submits a canary transaction that is expected to always fail in order to determine whether the contract currently has valid multisig state + * and whether it is initialized. The canary transaction attempts to delete a request at index u32_max and will go through if a request exists at that index. + * a u32_max + 1 and -1 value cannot be used for the canary due to expected u32 error thrown before deserialization attempt. + */ async checkMultisigStateStatus(contractBytes) { const u32_max = 4294967295; return super.signAndSendTransaction({ @@ -120,10 +126,10 @@ class AccountMultisig extends account_1.Account { async deleteAllRequests() { const request_ids = await this.getRequestIds(); if (request_ids.length) { - await super.signAndSendTransaction({ + await Promise.all(request_ids.map(requestIdToDelete => super.signAndSendTransaction({ receiverId: this.accountId, - actions: request_ids.map(requestIdToDelete => transaction_1.functionCall('delete_request', { request_id: requestIdToDelete }, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT)) - }); + actions: [transaction_1.functionCall('delete_request', { request_id: requestIdToDelete }, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT)] + }))); } } async deleteUnconfirmedRequests() { @@ -239,7 +245,12 @@ class Account2FA extends AccountMultisig { perm.method_names.includes('add_request_and_confirm'); }); const confirmOnlyKey = key_pair_1.PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - await this.deleteAllRequests().catch(e => e); + await this.deleteAllRequests().catch(e => { + if (new RegExp(MultisigDeleteRequestRejectionError.REQUEST_COOLDOWN_ERROR).test(e?.kind?.ExecutionError)) { + return e; + } + throw e; + }); const currentAccountState = await this.viewState('').catch(error => { const cause = error.cause && error.cause.name; if (cause == 'NO_CONTRACT_CODE') { diff --git a/src/account_multisig.ts b/src/account_multisig.ts index 9b482c85c4..13f25f7fcc 100644 --- a/src/account_multisig.ts +++ b/src/account_multisig.ts @@ -24,9 +24,10 @@ type getCodeFunction = (method: any) => Promise; type verifyCodeFunction = (securityCode: any) => Promise; enum MultisigDeleteRequestRejectionError { - CANNOT_DESERIALIZE_STATE = `Smart contract panicked: panicked at 'Cannot deserialize the contract state\.: Custom { kind: InvalidInput, error: "Unexpected length of input" }'`, + CANNOT_DESERIALIZE_STATE = `Cannot deserialize the contract state`, MULTISIG_NOT_INITIALIZED = `Smart contract panicked: Multisig contract should be initialized before usage`, - NO_SUCH_REQUEST = `Smart contract panicked: panicked at 'No such request: either wrong number or already confirmed'` + NO_SUCH_REQUEST = `Smart contract panicked: panicked at 'No such request: either wrong number or already confirmed'`, + REQUEST_COOLDOWN_ERROR = `Request cannot be deleted immediately after creation.` }; enum MultisigStateStatus { @@ -113,6 +114,11 @@ export class AccountMultisig extends Account { return result; } + /* + * This method submits a canary transaction that is expected to always fail in order to determine whether the contract currently has valid multisig state + * and whether it is initialized. The canary transaction attempts to delete a request at index u32_max and will go through if a request exists at that index. + * a u32_max + 1 and -1 value cannot be used for the canary due to expected u32 error thrown before deserialization attempt. + */ async checkMultisigStateStatus(contractBytes: Uint8Array): Promise { const u32_max = 4_294_967_295; return super.signAndSendTransaction({ @@ -137,10 +143,12 @@ export class AccountMultisig extends Account { async deleteAllRequests() { const request_ids = await this.getRequestIds(); if(request_ids.length) { - await super.signAndSendTransaction({ - receiverId: this.accountId, - actions: request_ids.map(requestIdToDelete => functionCall('delete_request', { request_id: requestIdToDelete }, MULTISIG_GAS, MULTISIG_DEPOSIT)) - }) + await Promise.all(request_ids.map(requestIdToDelete => + super.signAndSendTransaction({ + receiverId: this.accountId, + actions: [functionCall('delete_request', { request_id: requestIdToDelete }, MULTISIG_GAS, MULTISIG_DEPOSIT)] + }) + )) } } @@ -295,7 +303,12 @@ export class Account2FA extends AccountMultisig { perm.method_names.includes('add_request_and_confirm'); }); const confirmOnlyKey = PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - await this.deleteAllRequests().catch(e => e); + await this.deleteAllRequests().catch(e => { + if(new RegExp(MultisigDeleteRequestRejectionError.REQUEST_COOLDOWN_ERROR).test(e?.kind?.ExecutionError)) { + return e; + } + throw e; + }); const currentAccountState: { key: Buffer, value: Buffer }[] = await this.viewState('').catch(error => { const cause = error.cause && error.cause.name; if (cause == 'NO_CONTRACT_CODE') { From ae5e8a5d82de50f91894bcc1b4d94100f34c46fc Mon Sep 17 00:00:00 2001 From: esaminu Date: Wed, 23 Mar 2022 04:39:14 +0400 Subject: [PATCH 09/13] feat: add checkMultisigCodeAndStateStatus --- lib/account_multisig.d.ts | 11 ++++- lib/account_multisig.js | 83 +++++++++++++++++++++++---------- src/account_multisig.ts | 96 +++++++++++++++++++++++++-------------- 3 files changed, 131 insertions(+), 59 deletions(-) diff --git a/lib/account_multisig.d.ts b/lib/account_multisig.d.ts index 2f74786a8f..c3f2c725f1 100644 --- a/lib/account_multisig.d.ts +++ b/lib/account_multisig.d.ts @@ -14,8 +14,13 @@ declare type getCodeFunction = (method: any) => Promise; declare type verifyCodeFunction = (securityCode: any) => Promise; declare enum MultisigStateStatus { INVALID_STATE = 0, - NOT_INITIALIZED = 1, - VALID = 2 + STATE_NOT_INITIALIZED = 1, + VALID_STATE = 2, + UNKNOWN_STATE = 3 +} +declare enum MultisigCodeStatus { + INVALID_CODE = 0, + VALID_CODE = 1 } export declare class AccountMultisig extends Account { storage: any; @@ -25,6 +30,8 @@ export declare class AccountMultisig extends Account { protected signAndSendTransaction(...args: any[]): Promise; private _signAndSendTransaction; checkMultisigStateStatus(contractBytes: Uint8Array): Promise; + checkMultisigCodeAndStateStatus(): Promise<[MultisigCodeStatus, MultisigStateStatus]>; + deleteRequest(request_id: any): Promise; deleteAllRequests(): Promise; deleteUnconfirmedRequests(): Promise; getRequestIds(): Promise; diff --git a/lib/account_multisig.js b/lib/account_multisig.js index 8f180b1084..c581f79d6b 100644 --- a/lib/account_multisig.js +++ b/lib/account_multisig.js @@ -25,14 +25,21 @@ var MultisigDeleteRequestRejectionError; MultisigDeleteRequestRejectionError["MULTISIG_NOT_INITIALIZED"] = "Smart contract panicked: Multisig contract should be initialized before usage"; MultisigDeleteRequestRejectionError["NO_SUCH_REQUEST"] = "Smart contract panicked: panicked at 'No such request: either wrong number or already confirmed'"; MultisigDeleteRequestRejectionError["REQUEST_COOLDOWN_ERROR"] = "Request cannot be deleted immediately after creation."; + MultisigDeleteRequestRejectionError["METHOD_NOT_FOUND"] = "Contract method is not found"; })(MultisigDeleteRequestRejectionError || (MultisigDeleteRequestRejectionError = {})); ; var MultisigStateStatus; (function (MultisigStateStatus) { MultisigStateStatus[MultisigStateStatus["INVALID_STATE"] = 0] = "INVALID_STATE"; - MultisigStateStatus[MultisigStateStatus["NOT_INITIALIZED"] = 1] = "NOT_INITIALIZED"; - MultisigStateStatus[MultisigStateStatus["VALID"] = 2] = "VALID"; + MultisigStateStatus[MultisigStateStatus["STATE_NOT_INITIALIZED"] = 1] = "STATE_NOT_INITIALIZED"; + MultisigStateStatus[MultisigStateStatus["VALID_STATE"] = 2] = "VALID_STATE"; + MultisigStateStatus[MultisigStateStatus["UNKNOWN_STATE"] = 3] = "UNKNOWN_STATE"; })(MultisigStateStatus || (MultisigStateStatus = {})); +var MultisigCodeStatus; +(function (MultisigCodeStatus) { + MultisigCodeStatus[MultisigCodeStatus["INVALID_CODE"] = 0] = "INVALID_CODE"; + MultisigCodeStatus[MultisigCodeStatus["VALID_CODE"] = 1] = "VALID_CODE"; +})(MultisigCodeStatus || (MultisigCodeStatus = {})); // in memory request cache for node w/o localStorage const storageFallback = { [exports.MULTISIG_STORAGE_KEY]: null @@ -103,33 +110,60 @@ class AccountMultisig extends account_1.Account { */ async checkMultisigStateStatus(contractBytes) { const u32_max = 4294967295; - return super.signAndSendTransaction({ - receiverId: this.accountId, actions: [ - transaction_1.deployContract(contractBytes), - transaction_1.functionCall('delete_request', { request_id: u32_max }, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT) - ] - }) - .then(() => MultisigStateStatus.VALID) - .catch((e) => { + try { + await super.signAndSendTransaction({ + receiverId: this.accountId, actions: [ + transaction_1.deployContract(contractBytes), + transaction_1.functionCall('delete_request', { request_id: u32_max }, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT) + ] + }); + return MultisigStateStatus.VALID_STATE; + } + catch (e) { if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e?.kind?.ExecutionError)) { return MultisigStateStatus.INVALID_STATE; } else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e?.kind?.ExecutionError)) { - return MultisigStateStatus.NOT_INITIALIZED; + return MultisigStateStatus.STATE_NOT_INITIALIZED; } else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e?.kind?.ExecutionError)) { - return MultisigStateStatus.VALID; + return MultisigStateStatus.VALID_STATE; } throw e; + } + } + async checkMultisigCodeAndStateStatus() { + const u32_max = 4294967295; + try { + await this.deleteRequest(u32_max); + return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.VALID_STATE]; + } + catch (e) { + if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e?.kind?.ExecutionError)) { + return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.INVALID_STATE]; + } + else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e?.kind?.ExecutionError)) { + return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.STATE_NOT_INITIALIZED]; + } + else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e?.kind?.ExecutionError)) { + return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.VALID_STATE]; + } + else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e?.kind?.ExecutionError)) { + return [MultisigCodeStatus.INVALID_CODE, MultisigStateStatus.UNKNOWN_STATE]; + } + throw e; + } + } + deleteRequest(request_id) { + return super.signAndSendTransaction({ + receiverId: this.accountId, + actions: [transaction_1.functionCall('delete_request', { request_id }, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT)] }); } async deleteAllRequests() { const request_ids = await this.getRequestIds(); if (request_ids.length) { - await Promise.all(request_ids.map(requestIdToDelete => super.signAndSendTransaction({ - receiverId: this.accountId, - actions: [transaction_1.functionCall('delete_request', { request_id: requestIdToDelete }, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT)] - }))); + await Promise.all(request_ids.map(this.deleteRequest)); } } async deleteUnconfirmedRequests() { @@ -225,12 +259,14 @@ class Account2FA extends AccountMultisig { console.log('deploying multisig contract for', accountId); const multisigStateStatus = await this.checkMultisigStateStatus(contractBytes); switch (multisigStateStatus) { - case MultisigStateStatus.NOT_INITIALIZED: + case MultisigStateStatus.STATE_NOT_INITIALIZED: return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); - case MultisigStateStatus.VALID: + case MultisigStateStatus.VALID_STATE: return await super.signAndSendTransactionWithAccount(accountId, actions); case MultisigStateStatus.INVALID_STATE: throw new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); + default: + throw new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); } } async disable(contractBytes, cleanupContractBytes) { @@ -245,12 +281,11 @@ class Account2FA extends AccountMultisig { perm.method_names.includes('add_request_and_confirm'); }); const confirmOnlyKey = key_pair_1.PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - await this.deleteAllRequests().catch(e => { - if (new RegExp(MultisigDeleteRequestRejectionError.REQUEST_COOLDOWN_ERROR).test(e?.kind?.ExecutionError)) { - return e; - } - throw e; - }); + const [, stateValidity] = await this.checkMultisigCodeAndStateStatus(); + if (stateValidity !== MultisigStateStatus.VALID_STATE && stateValidity !== MultisigStateStatus.STATE_NOT_INITIALIZED) { + throw new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); + } + await this.deleteAllRequests().catch(e => e); const currentAccountState = await this.viewState('').catch(error => { const cause = error.cause && error.cause.name; if (cause == 'NO_CONTRACT_CODE') { diff --git a/src/account_multisig.ts b/src/account_multisig.ts index 13f25f7fcc..bc07b7c558 100644 --- a/src/account_multisig.ts +++ b/src/account_multisig.ts @@ -27,13 +27,20 @@ enum MultisigDeleteRequestRejectionError { CANNOT_DESERIALIZE_STATE = `Cannot deserialize the contract state`, MULTISIG_NOT_INITIALIZED = `Smart contract panicked: Multisig contract should be initialized before usage`, NO_SUCH_REQUEST = `Smart contract panicked: panicked at 'No such request: either wrong number or already confirmed'`, - REQUEST_COOLDOWN_ERROR = `Request cannot be deleted immediately after creation.` + REQUEST_COOLDOWN_ERROR = `Request cannot be deleted immediately after creation.`, + METHOD_NOT_FOUND = `Contract method is not found` }; enum MultisigStateStatus { INVALID_STATE, - NOT_INITIALIZED, - VALID + STATE_NOT_INITIALIZED, + VALID_STATE, + UNKNOWN_STATE +} + +enum MultisigCodeStatus { + INVALID_CODE, + VALID_CODE } // in memory request cache for node w/o localStorage @@ -121,34 +128,56 @@ export class AccountMultisig extends Account { */ async checkMultisigStateStatus(contractBytes: Uint8Array): Promise { const u32_max = 4_294_967_295; + try { + await super.signAndSendTransaction({ + receiverId: this.accountId, actions: [ + deployContract(contractBytes), + functionCall('delete_request', { request_id: u32_max }, MULTISIG_GAS, MULTISIG_DEPOSIT) + ] + }); + return MultisigStateStatus.VALID_STATE; + } catch (e) { + if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e?.kind?.ExecutionError)) { + return MultisigStateStatus.INVALID_STATE; + } else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e?.kind?.ExecutionError)) { + return MultisigStateStatus.STATE_NOT_INITIALIZED; + } else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e?.kind?.ExecutionError)) { + return MultisigStateStatus.VALID_STATE; + } + throw e; + } + } + + async checkMultisigCodeAndStateStatus(): Promise<[MultisigCodeStatus, MultisigStateStatus]> { + const u32_max = 4_294_967_295; + try { + await this.deleteRequest(u32_max); + return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.VALID_STATE]; + } catch (e) { + if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e?.kind?.ExecutionError)) { + return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.INVALID_STATE]; + } else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e?.kind?.ExecutionError)) { + return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.STATE_NOT_INITIALIZED]; + } else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e?.kind?.ExecutionError)) { + return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.VALID_STATE]; + } else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e?.kind?.ExecutionError)) { + return [MultisigCodeStatus.INVALID_CODE, MultisigStateStatus.UNKNOWN_STATE]; + } + throw e; + } + } + + deleteRequest(request_id) { return super.signAndSendTransaction({ - receiverId: this.accountId, actions: [ - deployContract(contractBytes), - functionCall('delete_request', { request_id: u32_max }, MULTISIG_GAS, MULTISIG_DEPOSIT) - ] - }) - .then(() => MultisigStateStatus.VALID) - .catch((e) => { - if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e?.kind?.ExecutionError)) { - return MultisigStateStatus.INVALID_STATE; - } else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e?.kind?.ExecutionError)) { - return MultisigStateStatus.NOT_INITIALIZED; - } else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e?.kind?.ExecutionError)) { - return MultisigStateStatus.VALID; - } - throw e; - }) + receiverId: this.accountId, + actions: [functionCall('delete_request', { request_id }, MULTISIG_GAS, MULTISIG_DEPOSIT)] + }); } async deleteAllRequests() { const request_ids = await this.getRequestIds(); if(request_ids.length) { - await Promise.all(request_ids.map(requestIdToDelete => - super.signAndSendTransaction({ - receiverId: this.accountId, - actions: [functionCall('delete_request', { request_id: requestIdToDelete }, MULTISIG_GAS, MULTISIG_DEPOSIT)] - }) - )) + await Promise.all(request_ids.map(this.deleteRequest)); } } @@ -282,12 +311,14 @@ export class Account2FA extends AccountMultisig { const multisigStateStatus = await this.checkMultisigStateStatus(contractBytes); switch (multisigStateStatus) { - case MultisigStateStatus.NOT_INITIALIZED: + case MultisigStateStatus.STATE_NOT_INITIALIZED: return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); - case MultisigStateStatus.VALID: + case MultisigStateStatus.VALID_STATE: return await super.signAndSendTransactionWithAccount(accountId, actions); case MultisigStateStatus.INVALID_STATE: throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); + default: + throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); } } @@ -303,12 +334,11 @@ export class Account2FA extends AccountMultisig { perm.method_names.includes('add_request_and_confirm'); }); const confirmOnlyKey = PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - await this.deleteAllRequests().catch(e => { - if(new RegExp(MultisigDeleteRequestRejectionError.REQUEST_COOLDOWN_ERROR).test(e?.kind?.ExecutionError)) { - return e; - } - throw e; - }); + const [,stateValidity] = await this.checkMultisigCodeAndStateStatus(); + if(stateValidity !== MultisigStateStatus.VALID_STATE && stateValidity !== MultisigStateStatus.STATE_NOT_INITIALIZED) { + throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); + } + await this.deleteAllRequests().catch(e => e); const currentAccountState: { key: Buffer, value: Buffer }[] = await this.viewState('').catch(error => { const cause = error.cause && error.cause.name; if (cause == 'NO_CONTRACT_CODE') { From 3df552afdaa4c205de69a605d8606ea8dd5310ca Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 25 Mar 2022 02:24:24 +0400 Subject: [PATCH 10/13] refactor: combine checkMultisigStateStatus into checkMultisigCodeAndStateStatus --- lib/account_multisig.d.ts | 9 ++++-- lib/account_multisig.js | 60 +++++++++++++++-------------------- src/account_multisig.ts | 66 ++++++++++++++++++--------------------- 3 files changed, 62 insertions(+), 73 deletions(-) diff --git a/lib/account_multisig.d.ts b/lib/account_multisig.d.ts index c3f2c725f1..75e9861e1f 100644 --- a/lib/account_multisig.d.ts +++ b/lib/account_multisig.d.ts @@ -20,7 +20,8 @@ declare enum MultisigStateStatus { } declare enum MultisigCodeStatus { INVALID_CODE = 0, - VALID_CODE = 1 + VALID_CODE = 1, + UNKNOWN_CODE = 2 } export declare class AccountMultisig extends Account { storage: any; @@ -29,8 +30,10 @@ export declare class AccountMultisig extends Account { signAndSendTransactionWithAccount(receiverId: string, actions: Action[]): Promise; protected signAndSendTransaction(...args: any[]): Promise; private _signAndSendTransaction; - checkMultisigStateStatus(contractBytes: Uint8Array): Promise; - checkMultisigCodeAndStateStatus(): Promise<[MultisigCodeStatus, MultisigStateStatus]>; + checkMultisigCodeAndStateStatus(contractBytes?: Uint8Array): Promise<{ + codeStatus: MultisigCodeStatus; + stateStatus: MultisigStateStatus; + }>; deleteRequest(request_id: any): Promise; deleteAllRequests(): Promise; deleteUnconfirmedRequests(): Promise; diff --git a/lib/account_multisig.js b/lib/account_multisig.js index c581f79d6b..ba8b66d01b 100644 --- a/lib/account_multisig.js +++ b/lib/account_multisig.js @@ -39,6 +39,7 @@ var MultisigCodeStatus; (function (MultisigCodeStatus) { MultisigCodeStatus[MultisigCodeStatus["INVALID_CODE"] = 0] = "INVALID_CODE"; MultisigCodeStatus[MultisigCodeStatus["VALID_CODE"] = 1] = "VALID_CODE"; + MultisigCodeStatus[MultisigCodeStatus["UNKNOWN_CODE"] = 2] = "UNKNOWN_CODE"; })(MultisigCodeStatus || (MultisigCodeStatus = {})); // in memory request cache for node w/o localStorage const storageFallback = { @@ -108,48 +109,36 @@ class AccountMultisig extends account_1.Account { * and whether it is initialized. The canary transaction attempts to delete a request at index u32_max and will go through if a request exists at that index. * a u32_max + 1 and -1 value cannot be used for the canary due to expected u32 error thrown before deserialization attempt. */ - async checkMultisigStateStatus(contractBytes) { + async checkMultisigCodeAndStateStatus(contractBytes) { const u32_max = 4294967295; + const validCodeStatusIfNoDeploy = contractBytes ? MultisigCodeStatus.UNKNOWN_CODE : MultisigCodeStatus.VALID_CODE; try { - await super.signAndSendTransaction({ - receiverId: this.accountId, actions: [ - transaction_1.deployContract(contractBytes), - transaction_1.functionCall('delete_request', { request_id: u32_max }, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT) - ] - }); - return MultisigStateStatus.VALID_STATE; - } - catch (e) { - if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e?.kind?.ExecutionError)) { - return MultisigStateStatus.INVALID_STATE; - } - else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e?.kind?.ExecutionError)) { - return MultisigStateStatus.STATE_NOT_INITIALIZED; + if (contractBytes) { + await super.signAndSendTransaction({ + receiverId: this.accountId, actions: [ + transaction_1.deployContract(contractBytes), + transaction_1.functionCall('delete_request', { request_id: u32_max }, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT) + ] + }); } - else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e?.kind?.ExecutionError)) { - return MultisigStateStatus.VALID_STATE; + else { + await this.deleteRequest(u32_max); } - throw e; - } - } - async checkMultisigCodeAndStateStatus() { - const u32_max = 4294967295; - try { - await this.deleteRequest(u32_max); - return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.VALID_STATE]; + return { codeStatus: MultisigCodeStatus.VALID_CODE, stateStatus: MultisigStateStatus.VALID_STATE }; } catch (e) { if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e?.kind?.ExecutionError)) { - return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.INVALID_STATE]; + return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.INVALID_STATE }; } else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e?.kind?.ExecutionError)) { - return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.STATE_NOT_INITIALIZED]; + return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.STATE_NOT_INITIALIZED }; } else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e?.kind?.ExecutionError)) { - return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.VALID_STATE]; + return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.VALID_STATE }; } - else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e?.kind?.ExecutionError)) { - return [MultisigCodeStatus.INVALID_CODE, MultisigStateStatus.UNKNOWN_STATE]; + else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e?.message)) { + // not reachable if transaction included a deploy + return { codeStatus: MultisigCodeStatus.UNKNOWN_CODE, stateStatus: MultisigStateStatus.UNKNOWN_STATE }; } throw e; } @@ -257,7 +246,7 @@ class Account2FA extends AccountMultisig { ]; const newFunctionCallActionBatch = actions.concat(transaction_1.functionCall('new', newArgs, exports.MULTISIG_GAS, exports.MULTISIG_DEPOSIT)); console.log('deploying multisig contract for', accountId); - const multisigStateStatus = await this.checkMultisigStateStatus(contractBytes); + const { stateStatus: multisigStateStatus } = await this.checkMultisigCodeAndStateStatus(contractBytes); switch (multisigStateStatus) { case MultisigStateStatus.STATE_NOT_INITIALIZED: return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); @@ -281,18 +270,19 @@ class Account2FA extends AccountMultisig { perm.method_names.includes('add_request_and_confirm'); }); const confirmOnlyKey = key_pair_1.PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - const [, stateValidity] = await this.checkMultisigCodeAndStateStatus(); - if (stateValidity !== MultisigStateStatus.VALID_STATE && stateValidity !== MultisigStateStatus.STATE_NOT_INITIALIZED) { + const { stateStatus } = await this.checkMultisigCodeAndStateStatus(); + if (stateStatus !== MultisigStateStatus.VALID_STATE && stateStatus !== MultisigStateStatus.STATE_NOT_INITIALIZED) { throw new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); } - await this.deleteAllRequests().catch(e => e); + let deleteAllRequestsError; + await this.deleteAllRequests().catch(e => deleteAllRequestsError = e); const currentAccountState = await this.viewState('').catch(error => { const cause = error.cause && error.cause.name; if (cause == 'NO_CONTRACT_CODE') { return []; } throw cause == 'TOO_LARGE_CONTRACT_STATE' ? - new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState') : + deleteAllRequestsError || new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState') : error; }); const currentAccountStateKeys = currentAccountState.map(({ key }) => key.toString('base64')); diff --git a/src/account_multisig.ts b/src/account_multisig.ts index bc07b7c558..ee7a907e25 100644 --- a/src/account_multisig.ts +++ b/src/account_multisig.ts @@ -40,7 +40,8 @@ enum MultisigStateStatus { enum MultisigCodeStatus { INVALID_CODE, - VALID_CODE + VALID_CODE, + UNKNOWN_CODE } // in memory request cache for node w/o localStorage @@ -126,42 +127,33 @@ export class AccountMultisig extends Account { * and whether it is initialized. The canary transaction attempts to delete a request at index u32_max and will go through if a request exists at that index. * a u32_max + 1 and -1 value cannot be used for the canary due to expected u32 error thrown before deserialization attempt. */ - async checkMultisigStateStatus(contractBytes: Uint8Array): Promise { + async checkMultisigCodeAndStateStatus(contractBytes?: Uint8Array): Promise<{ codeStatus: MultisigCodeStatus, stateStatus: MultisigStateStatus }> { const u32_max = 4_294_967_295; - try { - await super.signAndSendTransaction({ - receiverId: this.accountId, actions: [ - deployContract(contractBytes), - functionCall('delete_request', { request_id: u32_max }, MULTISIG_GAS, MULTISIG_DEPOSIT) - ] - }); - return MultisigStateStatus.VALID_STATE; - } catch (e) { - if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e?.kind?.ExecutionError)) { - return MultisigStateStatus.INVALID_STATE; - } else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e?.kind?.ExecutionError)) { - return MultisigStateStatus.STATE_NOT_INITIALIZED; - } else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e?.kind?.ExecutionError)) { - return MultisigStateStatus.VALID_STATE; - } - throw e; - } - } + const validCodeStatusIfNoDeploy = contractBytes ? MultisigCodeStatus.UNKNOWN_CODE : MultisigCodeStatus.VALID_CODE; - async checkMultisigCodeAndStateStatus(): Promise<[MultisigCodeStatus, MultisigStateStatus]> { - const u32_max = 4_294_967_295; try { - await this.deleteRequest(u32_max); - return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.VALID_STATE]; + if(contractBytes) { + await super.signAndSendTransaction({ + receiverId: this.accountId, actions: [ + deployContract(contractBytes), + functionCall('delete_request', { request_id: u32_max }, MULTISIG_GAS, MULTISIG_DEPOSIT) + ] + }); + } else { + await this.deleteRequest(u32_max); + } + + return { codeStatus: MultisigCodeStatus.VALID_CODE, stateStatus: MultisigStateStatus.VALID_STATE }; } catch (e) { if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e?.kind?.ExecutionError)) { - return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.INVALID_STATE]; + return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.INVALID_STATE }; } else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e?.kind?.ExecutionError)) { - return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.STATE_NOT_INITIALIZED]; + return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.STATE_NOT_INITIALIZED }; } else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e?.kind?.ExecutionError)) { - return [MultisigCodeStatus.VALID_CODE, MultisigStateStatus.VALID_STATE]; - } else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e?.kind?.ExecutionError)) { - return [MultisigCodeStatus.INVALID_CODE, MultisigStateStatus.UNKNOWN_STATE]; + return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.VALID_STATE }; + } else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e?.message)) { + // not reachable if transaction included a deploy + return { codeStatus: MultisigCodeStatus.UNKNOWN_CODE, stateStatus: MultisigStateStatus.UNKNOWN_STATE }; } throw e; } @@ -309,7 +301,7 @@ export class Account2FA extends AccountMultisig { const newFunctionCallActionBatch = actions.concat(functionCall('new', newArgs, MULTISIG_GAS, MULTISIG_DEPOSIT)) console.log('deploying multisig contract for', accountId); - const multisigStateStatus = await this.checkMultisigStateStatus(contractBytes); + const { stateStatus: multisigStateStatus } = await this.checkMultisigCodeAndStateStatus(contractBytes); switch (multisigStateStatus) { case MultisigStateStatus.STATE_NOT_INITIALIZED: return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); @@ -334,20 +326,24 @@ export class Account2FA extends AccountMultisig { perm.method_names.includes('add_request_and_confirm'); }); const confirmOnlyKey = PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - const [,stateValidity] = await this.checkMultisigCodeAndStateStatus(); - if(stateValidity !== MultisigStateStatus.VALID_STATE && stateValidity !== MultisigStateStatus.STATE_NOT_INITIALIZED) { + + const { stateStatus } = await this.checkMultisigCodeAndStateStatus(); + if(stateStatus !== MultisigStateStatus.VALID_STATE && stateStatus !== MultisigStateStatus.STATE_NOT_INITIALIZED) { throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); } - await this.deleteAllRequests().catch(e => e); + + let deleteAllRequestsError; + await this.deleteAllRequests().catch(e => deleteAllRequestsError = e); const currentAccountState: { key: Buffer, value: Buffer }[] = await this.viewState('').catch(error => { const cause = error.cause && error.cause.name; if (cause == 'NO_CONTRACT_CODE') { return []; } throw cause == 'TOO_LARGE_CONTRACT_STATE' ? - new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState') : + deleteAllRequestsError || new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState') : error; }); + const currentAccountStateKeys = currentAccountState.map(({ key }) => key.toString('base64')) const cleanupActions = currentAccountState.length ? [ deployContract(cleanupContractBytes), From fedd3d58bfdc3d8666035805a74419aae7d0a8ba Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Fri, 25 Mar 2022 21:29:04 +0400 Subject: [PATCH 11/13] refactor: remove point free style from 'this' method Co-authored-by: Daryl Collins --- src/account_multisig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/account_multisig.ts b/src/account_multisig.ts index ee7a907e25..861915a272 100644 --- a/src/account_multisig.ts +++ b/src/account_multisig.ts @@ -169,7 +169,7 @@ export class AccountMultisig extends Account { async deleteAllRequests() { const request_ids = await this.getRequestIds(); if(request_ids.length) { - await Promise.all(request_ids.map(this.deleteRequest)); + await Promise.all(request_ids.map((id) => this.deleteRequest(id))); } } From 34a492fff88e1ad408bac4bb5fbc0d63f0792677 Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 25 Mar 2022 22:25:37 +0400 Subject: [PATCH 12/13] fix: return invalid_code on method_not_found --- lib/account_multisig.d.ts | 14 +++++++++++++- lib/account_multisig.js | 15 ++++++++++----- src/account_multisig.ts | 11 ++++++++--- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/lib/account_multisig.d.ts b/lib/account_multisig.d.ts index 75e9861e1f..32871da0a9 100644 --- a/lib/account_multisig.d.ts +++ b/lib/account_multisig.d.ts @@ -12,7 +12,14 @@ export declare const MULTISIG_CONFIRM_METHODS: string[]; declare type sendCodeFunction = () => Promise; declare type getCodeFunction = (method: any) => Promise; declare type verifyCodeFunction = (securityCode: any) => Promise; -declare enum MultisigStateStatus { +export declare enum MultisigDeleteRequestRejectionError { + CANNOT_DESERIALIZE_STATE = "Cannot deserialize the contract state", + MULTISIG_NOT_INITIALIZED = "Smart contract panicked: Multisig contract should be initialized before usage", + NO_SUCH_REQUEST = "Smart contract panicked: panicked at 'No such request: either wrong number or already confirmed'", + REQUEST_COOLDOWN_ERROR = "Request cannot be deleted immediately after creation.", + METHOD_NOT_FOUND = "Contract method is not found" +} +export declare enum MultisigStateStatus { INVALID_STATE = 0, STATE_NOT_INITIALIZED = 1, VALID_STATE = 2, @@ -70,6 +77,11 @@ export declare class Account2FA extends AccountMultisig { signAndSendTransaction(receiverId: string, actions: Action[]): Promise; private __signAndSendTransaction; deployMultisig(contractBytes: Uint8Array): Promise; + /** + * This method converts LAKs back to FAKs, clears state and deploys an 'empty' contract (contractBytes param) + * @param [contractBytes]{@link https://github.com/near/near-wallet/blob/master/packages/frontend/src/wasm/main.wasm?raw=true} + * @param [cleanupContractBytes]{@link https://github.com/near/core-contracts/blob/master/state-cleanup/res/state_cleanup.wasm?raw=true} + */ disable(contractBytes: Uint8Array, cleanupContractBytes: Uint8Array): Promise; sendCodeDefault(): Promise; getCodeDefault(method: any): Promise; diff --git a/lib/account_multisig.js b/lib/account_multisig.js index ba8b66d01b..0f0b17d075 100644 --- a/lib/account_multisig.js +++ b/lib/account_multisig.js @@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Account2FA = exports.AccountMultisig = exports.MULTISIG_CONFIRM_METHODS = exports.MULTISIG_CHANGE_METHODS = exports.MULTISIG_DEPOSIT = exports.MULTISIG_GAS = exports.MULTISIG_ALLOWANCE = exports.MULTISIG_STORAGE_KEY = void 0; +exports.Account2FA = exports.AccountMultisig = exports.MultisigStateStatus = exports.MultisigDeleteRequestRejectionError = exports.MULTISIG_CONFIRM_METHODS = exports.MULTISIG_CHANGE_METHODS = exports.MULTISIG_DEPOSIT = exports.MULTISIG_GAS = exports.MULTISIG_ALLOWANCE = exports.MULTISIG_STORAGE_KEY = void 0; const bn_js_1 = __importDefault(require("bn.js")); const depd_1 = __importDefault(require("depd")); const account_1 = require("./account"); @@ -26,7 +26,7 @@ var MultisigDeleteRequestRejectionError; MultisigDeleteRequestRejectionError["NO_SUCH_REQUEST"] = "Smart contract panicked: panicked at 'No such request: either wrong number or already confirmed'"; MultisigDeleteRequestRejectionError["REQUEST_COOLDOWN_ERROR"] = "Request cannot be deleted immediately after creation."; MultisigDeleteRequestRejectionError["METHOD_NOT_FOUND"] = "Contract method is not found"; -})(MultisigDeleteRequestRejectionError || (MultisigDeleteRequestRejectionError = {})); +})(MultisigDeleteRequestRejectionError = exports.MultisigDeleteRequestRejectionError || (exports.MultisigDeleteRequestRejectionError = {})); ; var MultisigStateStatus; (function (MultisigStateStatus) { @@ -34,7 +34,7 @@ var MultisigStateStatus; MultisigStateStatus[MultisigStateStatus["STATE_NOT_INITIALIZED"] = 1] = "STATE_NOT_INITIALIZED"; MultisigStateStatus[MultisigStateStatus["VALID_STATE"] = 2] = "VALID_STATE"; MultisigStateStatus[MultisigStateStatus["UNKNOWN_STATE"] = 3] = "UNKNOWN_STATE"; -})(MultisigStateStatus || (MultisigStateStatus = {})); +})(MultisigStateStatus = exports.MultisigStateStatus || (exports.MultisigStateStatus = {})); var MultisigCodeStatus; (function (MultisigCodeStatus) { MultisigCodeStatus[MultisigCodeStatus["INVALID_CODE"] = 0] = "INVALID_CODE"; @@ -138,7 +138,7 @@ class AccountMultisig extends account_1.Account { } else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e?.message)) { // not reachable if transaction included a deploy - return { codeStatus: MultisigCodeStatus.UNKNOWN_CODE, stateStatus: MultisigStateStatus.UNKNOWN_STATE }; + return { codeStatus: MultisigCodeStatus.INVALID_CODE, stateStatus: MultisigStateStatus.UNKNOWN_STATE }; } throw e; } @@ -152,7 +152,7 @@ class AccountMultisig extends account_1.Account { async deleteAllRequests() { const request_ids = await this.getRequestIds(); if (request_ids.length) { - await Promise.all(request_ids.map(this.deleteRequest)); + await Promise.all(request_ids.map((id) => this.deleteRequest(id))); } } async deleteUnconfirmedRequests() { @@ -258,6 +258,11 @@ class Account2FA extends AccountMultisig { throw new providers_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); } } + /** + * This method converts LAKs back to FAKs, clears state and deploys an 'empty' contract (contractBytes param) + * @param [contractBytes]{@link https://github.com/near/near-wallet/blob/master/packages/frontend/src/wasm/main.wasm?raw=true} + * @param [cleanupContractBytes]{@link https://github.com/near/core-contracts/blob/master/state-cleanup/res/state_cleanup.wasm?raw=true} + */ async disable(contractBytes, cleanupContractBytes) { const { accountId } = this; const accessKeys = await this.getAccessKeys(); diff --git a/src/account_multisig.ts b/src/account_multisig.ts index 861915a272..31dd2dd9fe 100644 --- a/src/account_multisig.ts +++ b/src/account_multisig.ts @@ -23,7 +23,7 @@ type sendCodeFunction = () => Promise; type getCodeFunction = (method: any) => Promise; type verifyCodeFunction = (securityCode: any) => Promise; -enum MultisigDeleteRequestRejectionError { +export enum MultisigDeleteRequestRejectionError { CANNOT_DESERIALIZE_STATE = `Cannot deserialize the contract state`, MULTISIG_NOT_INITIALIZED = `Smart contract panicked: Multisig contract should be initialized before usage`, NO_SUCH_REQUEST = `Smart contract panicked: panicked at 'No such request: either wrong number or already confirmed'`, @@ -31,7 +31,7 @@ enum MultisigDeleteRequestRejectionError { METHOD_NOT_FOUND = `Contract method is not found` }; -enum MultisigStateStatus { +export enum MultisigStateStatus { INVALID_STATE, STATE_NOT_INITIALIZED, VALID_STATE, @@ -153,7 +153,7 @@ export class AccountMultisig extends Account { return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.VALID_STATE }; } else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e?.message)) { // not reachable if transaction included a deploy - return { codeStatus: MultisigCodeStatus.UNKNOWN_CODE, stateStatus: MultisigStateStatus.UNKNOWN_STATE }; + return { codeStatus: MultisigCodeStatus.INVALID_CODE, stateStatus: MultisigStateStatus.UNKNOWN_STATE }; } throw e; } @@ -314,6 +314,11 @@ export class Account2FA extends AccountMultisig { } } + /** + * This method converts LAKs back to FAKs, clears state and deploys an 'empty' contract (contractBytes param) + * @param [contractBytes]{@link https://github.com/near/near-wallet/blob/master/packages/frontend/src/wasm/main.wasm?raw=true} + * @param [cleanupContractBytes]{@link https://github.com/near/core-contracts/blob/master/state-cleanup/res/state_cleanup.wasm?raw=true} + */ async disable(contractBytes: Uint8Array, cleanupContractBytes: Uint8Array) { const { accountId } = this; const accessKeys = await this.getAccessKeys(); From 1ebbbe224ba656f0990902dabddf126616493a88 Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 25 Mar 2022 23:02:10 +0400 Subject: [PATCH 13/13] refactor: remove optional chaining --- lib/account_multisig.js | 8 ++++---- src/account_multisig.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/account_multisig.js b/lib/account_multisig.js index 0f0b17d075..7900349561 100644 --- a/lib/account_multisig.js +++ b/lib/account_multisig.js @@ -127,16 +127,16 @@ class AccountMultisig extends account_1.Account { return { codeStatus: MultisigCodeStatus.VALID_CODE, stateStatus: MultisigStateStatus.VALID_STATE }; } catch (e) { - if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e?.kind?.ExecutionError)) { + if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e && e.kind && e.kind.ExecutionError)) { return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.INVALID_STATE }; } - else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e?.kind?.ExecutionError)) { + else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e && e.kind && e.kind.ExecutionError)) { return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.STATE_NOT_INITIALIZED }; } - else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e?.kind?.ExecutionError)) { + else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e && e.kind && e.kind.ExecutionError)) { return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.VALID_STATE }; } - else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e?.message)) { + else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e && e.message)) { // not reachable if transaction included a deploy return { codeStatus: MultisigCodeStatus.INVALID_CODE, stateStatus: MultisigStateStatus.UNKNOWN_STATE }; } diff --git a/src/account_multisig.ts b/src/account_multisig.ts index 31dd2dd9fe..226b2ce128 100644 --- a/src/account_multisig.ts +++ b/src/account_multisig.ts @@ -145,13 +145,13 @@ export class AccountMultisig extends Account { return { codeStatus: MultisigCodeStatus.VALID_CODE, stateStatus: MultisigStateStatus.VALID_STATE }; } catch (e) { - if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e?.kind?.ExecutionError)) { + if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e && e.kind && e.kind.ExecutionError)) { return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.INVALID_STATE }; - } else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e?.kind?.ExecutionError)) { + } else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e && e.kind && e.kind.ExecutionError)) { return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.STATE_NOT_INITIALIZED }; - } else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e?.kind?.ExecutionError)) { + } else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e && e.kind && e.kind.ExecutionError)) { return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.VALID_STATE }; - } else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e?.message)) { + } else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e && e.message)) { // not reachable if transaction included a deploy return { codeStatus: MultisigCodeStatus.INVALID_CODE, stateStatus: MultisigStateStatus.UNKNOWN_STATE }; }