From c13790e289871122876d4321c483852ced6b12f9 Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Wed, 3 Jul 2024 23:33:33 +0200 Subject: [PATCH 01/16] fix: non zero balance on non deployed cairo 0 account (dapp-ui flow) --- packages/starknet-snap/src/recoverAccounts.ts | 12 ++- packages/starknet-snap/src/types/snapApi.ts | 1 + packages/starknet-snap/src/types/snapState.ts | 1 + .../starknet-snap/src/upgradeAccContract.ts | 53 ++++++++---- .../starknet-snap/src/utils/starknetUtils.ts | 84 ++++++++++++++++++- packages/starknet-snap/test/constants.test.ts | 8 ++ .../test/src/recoverAccounts.test.ts | 30 +++++-- .../test/src/upgradeAccContract.test.ts | 4 +- .../test/utils/starknetUtils.test.ts | 48 ++++++++--- packages/wallet-ui/src/App.tsx | 4 +- .../UpgradeModel/UpgradeModel.view.tsx | 11 ++- .../wallet-ui/src/services/useStarkNetSnap.ts | 11 ++- packages/wallet-ui/src/slices/modalSlice.ts | 7 +- packages/wallet-ui/src/types/index.ts | 2 +- 14 files changed, 222 insertions(+), 54 deletions(-) diff --git a/packages/starknet-snap/src/recoverAccounts.ts b/packages/starknet-snap/src/recoverAccounts.ts index fb4cef06..d3fe2c61 100644 --- a/packages/starknet-snap/src/recoverAccounts.ts +++ b/packages/starknet-snap/src/recoverAccounts.ts @@ -1,6 +1,11 @@ import { toJson } from './utils/serializer'; import { num } from 'starknet'; -import { getKeysFromAddressIndex, getCorrectContractAddress } from './utils/starknetUtils'; +import { + getKeysFromAddressIndex, + getCorrectContractAddress, + estimateAccountDeployFee, + estimateAccountUpgradeFee, +} from './utils/starknetUtils'; import { getNetworkFromChainId, getValidNumber, upsertAccount } from './utils/snapUtils'; import { AccContract } from './types/snapState'; import { ApiParams, RecoverAccountsRequestParams } from './types/snapApi'; @@ -29,11 +34,13 @@ export async function recoverAccounts(params: ApiParams) { state, i, ); + const { address: contractAddress, signerPubKey: signerPublicKey, upgradeRequired, - } = await getCorrectContractAddress(network, publicKey); + deployRequired, + } = await getCorrectContractAddress(network, publicKey, state); logger.log( `recoverAccounts: index ${i}:\ncontractAddress = ${contractAddress}\npublicKey = ${publicKey}\nisUpgradeRequired = ${upgradeRequired}`, ); @@ -57,6 +64,7 @@ export async function recoverAccounts(params: ApiParams) { deployTxnHash: '', chainId: network.chainId, upgradeRequired: upgradeRequired, + deployRequired: deployRequired, }; logger.log(`recoverAccounts: index ${i}\nuserAccount: ${toJson(userAccount)}`); diff --git a/packages/starknet-snap/src/types/snapApi.ts b/packages/starknet-snap/src/types/snapApi.ts index 027f305e..ab08158f 100644 --- a/packages/starknet-snap/src/types/snapApi.ts +++ b/packages/starknet-snap/src/types/snapApi.ts @@ -105,6 +105,7 @@ export interface SendTransactionRequestParams extends BaseRequestParams { export interface UpgradeTransactionRequestParams extends BaseRequestParams { contractAddress: string; maxFee?: string; + forceDeploy?: boolean; } export interface GetValueRequestParams extends BaseRequestParams { diff --git a/packages/starknet-snap/src/types/snapState.ts b/packages/starknet-snap/src/types/snapState.ts index 041cfcbf..08f5b841 100644 --- a/packages/starknet-snap/src/types/snapState.ts +++ b/packages/starknet-snap/src/types/snapState.ts @@ -17,6 +17,7 @@ export interface AccContract { deployTxnHash: string; // in hex chainId: string; // in hex upgradeRequired?: boolean; + deployRequired?: boolean; } export interface Erc20Token { diff --git a/packages/starknet-snap/src/upgradeAccContract.ts b/packages/starknet-snap/src/upgradeAccContract.ts index 2272a820..2fe3f7b1 100644 --- a/packages/starknet-snap/src/upgradeAccContract.ts +++ b/packages/starknet-snap/src/upgradeAccContract.ts @@ -7,13 +7,15 @@ import { isUpgradeRequired, executeTxn, isAccountDeployed, - estimateFee, + getUpgradeTxnInvocation, + estimateAccountUpgradeFee, } from './utils/starknetUtils'; import { getNetworkFromChainId, upsertTransaction, getSendTxnText } from './utils/snapUtils'; import { ApiParams, UpgradeTransactionRequestParams } from './types/snapApi'; import { ACCOUNT_CLASS_HASH, CAIRO_VERSION_LEGACY } from './utils/constants'; import { heading, panel, DialogType } from '@metamask/snaps-sdk'; import { logger } from './utils/logger'; +import { createAccount } from './createAccount'; export async function upgradeAccContract(params: ApiParams) { try { @@ -21,7 +23,7 @@ export async function upgradeAccContract(params: ApiParams) { const requestParamsObj = requestParams as UpgradeTransactionRequestParams; const contractAddress = requestParamsObj.contractAddress; const chainId = requestParamsObj.chainId; - + const forceDeploy = requestParamsObj.forceDeploy; if (!contractAddress) { throw new Error(`The given contract address need to be non-empty string, got: ${toJson(requestParamsObj)}`); } @@ -32,16 +34,15 @@ export async function upgradeAccContract(params: ApiParams) { } const network = getNetworkFromChainId(state, chainId); - - if (!(await isAccountDeployed(network, contractAddress))) { - throw new Error('Contract has not deployed'); + if (!(await isAccountDeployed(network, contractAddress)) && !forceDeploy) { + throw new Error('Contract is not deployed and address has no balance'); } if (!(await isUpgradeRequired(network, contractAddress))) { throw new Error('Upgrade is not required'); } - const { privateKey } = await getKeysFromAddress(keyDeriver, network, state, contractAddress); + const { privateKey, addressIndex } = await getKeysFromAddress(keyDeriver, network, state, contractAddress); const method = 'upgrade'; @@ -50,17 +51,10 @@ export async function upgradeAccContract(params: ApiParams) { calldata: [0], }); - const txnInvocation = { - contractAddress, - entrypoint: method, - calldata, - }; + const txnInvocation = getUpgradeTxnInvocation(contractAddress); let maxFee = requestParamsObj.maxFee ? num.toBigInt(requestParamsObj.maxFee) : constants.ZERO; - if (maxFee === constants.ZERO) { - const estFeeResp = await estimateFee(network, contractAddress, privateKey, txnInvocation, CAIRO_VERSION_LEGACY); - maxFee = num.toBigInt(estFeeResp.suggestedMaxFee.toString(10) ?? '0'); - } + maxFee = num.toBigInt(await estimateAccountUpgradeFee(network, contractAddress, privateKey, maxFee)); const dialogComponents = getSendTxnText(state, contractAddress, method, calldata, contractAddress, maxFee, network); @@ -74,7 +68,31 @@ export async function upgradeAccContract(params: ApiParams) { if (!response) return false; - logger.log(`sendTransaction:\ntxnInvocation: ${toJson(txnInvocation)}\nmaxFee: ${maxFee.toString()}}`); + logger.log(`upgradeAccContract:\ntxnInvocation: ${toJson(txnInvocation)}\nmaxFee: ${maxFee.toString()}}`); + + const accountDeployed = await isAccountDeployed(network, contractAddress); + if (forceDeploy) { + if (accountDeployed) { + throw new Error(`Upgrade with Force Deploy cannot be executed on deployed account`); + } + //Deploy account before sending the transaction + logger.log('upgradeAccContract:\nFirst transaction : send deploy transaction'); + const createAccountApiParams = { + state, + wallet: params.wallet, + saveMutex: params.saveMutex, + keyDeriver, + requestParams: { + addressIndex, + deploy: true, + chainId: requestParamsObj.chainId, + }, + }; + await createAccount(createAccountApiParams, true, true); + } + + //In case we forceDeployed we assign a nonce of 1 to make sure it does after the deploy transaction + const nonce = forceDeploy ? 1 : undefined; const txnResp = await executeTxn( network, @@ -84,11 +102,12 @@ export async function upgradeAccContract(params: ApiParams) { undefined, { maxFee, + nonce, }, CAIRO_VERSION_LEGACY, ); - logger.log(`sendTransaction:\ntxnResp: ${toJson(txnResp)}`); + logger.log(`upgradeAccContract:\ntxnResp: ${toJson(txnResp)}`); if (!txnResp?.transaction_hash) { throw new Error(`Transaction hash is not found`); diff --git a/packages/starknet-snap/src/utils/starknetUtils.ts b/packages/starknet-snap/src/utils/starknetUtils.ts index c1d2abf9..0494643d 100644 --- a/packages/starknet-snap/src/utils/starknetUtils.ts +++ b/packages/starknet-snap/src/utils/starknetUtils.ts @@ -35,6 +35,7 @@ import { InvocationsSignerDetails, ProviderInterface, GetTransactionReceiptResponse, + BigNumberish, } from 'starknet'; import { Network, SnapState, Transaction, TransactionType } from '../types/snapState'; import { @@ -51,6 +52,7 @@ import { getAddressKey } from './keyPair'; import { getAccount, getAccounts, + getEtherErc20Token, getRPCUrl, getTransactionFromVoyagerUrl, getTransactionsFromVoyagerUrl, @@ -526,6 +528,7 @@ export const getAccContractAddressAndCallData = (publicKey) => { * @returns - address and calldata. */ export const getAccContractAddressAndCallDataLegacy = (publicKey) => { + // [TODO]: Check why use ACCOUNT_CLASS_HASH_LEGACY and PROXY_CONTRACT_HASH ? const callData = CallData.compile({ implementation: ACCOUNT_CLASS_HASH_LEGACY, selector: hash.getSelectorFromName('initialize'), @@ -707,6 +710,50 @@ export const isGTEMinVersion = (version: string) => { return Number(versionArr[1]) >= MIN_ACC_CONTRACT_VERSION[1]; }; +/** + * Generate the transaction invocation object for upgrading a contract. + * + * @param contractAddress - The address of the contract to upgrade. + * @returns An object representing the transaction invocation. + */ +export function getUpgradeTxnInvocation(contractAddress: string) { + const method = 'upgrade'; + + const calldata = CallData.compile({ + implementation: ACCOUNT_CLASS_HASH, + calldata: [0], + }); + + return { + contractAddress, + entrypoint: method, + calldata, + }; +} + +/** + * Calculate the transaction fee for upgrading a contract. + * + * @param network - The network on which the contract is deployed. + * @param contractAddress - The address of the contract to upgrade. + * @param privateKey - The private key of the account performing the upgrade. + * @param maxFee - The maximum fee allowed for the transaction. + * @returns The calculated transaction fee as a bigint. + */ +export async function estimateAccountUpgradeFee( + network: any, + contractAddress: string, + privateKey: string, + maxFee: BigNumberish = constants.ZERO, +) { + if (maxFee === constants.ZERO) { + const txnInvocation = getUpgradeTxnInvocation(contractAddress); + const estFeeResp = await estimateFee(network, contractAddress, privateKey, txnInvocation, CAIRO_VERSION_LEGACY); + return num.toBigInt(estFeeResp.suggestedMaxFee.toString(10) ?? '0'); + } + return maxFee; +} + /** * Get user address by public key, return address if the address has deployed * @@ -714,7 +761,12 @@ export const isGTEMinVersion = (version: string) => { * @param publicKey - address's public key. * @returns - address and address's public key. */ -export const getCorrectContractAddress = async (network: Network, publicKey: string) => { +export const getCorrectContractAddress = async ( + network: Network, + publicKey: string, + state: SnapState, + maxFee = constants.ZERO, +) => { const { address: contractAddress, addressLegacy: contractAddressLegacy } = getPermutationAddresses(publicKey); logger.log( @@ -723,6 +775,7 @@ export const getCorrectContractAddress = async (network: Network, publicKey: str let address = contractAddress; let upgradeRequired = false; + let deployRequired = false; let pk = ''; try { @@ -747,18 +800,41 @@ export const getCorrectContractAddress = async (network: Network, publicKey: str ); address = contractAddressLegacy; } catch (e) { - if (!e.message.includes('Contract not found')) { + if (e.message.includes('network error for getSigner')) { throw e; } + // Edge case detection + logger.log(`getContractAddressByKey: no deployed contract found, checking balance for edge cases`); - logger.log(`getContractAddressByKey: no deployed contract found, fallback to cairo ${CAIRO_VERSION}`); + try { + const balance = num.toBigInt( + (await getBalance( + getEtherErc20Token(state, network.chainId)?.address, + num.toBigInt(contractAddressLegacy).toString(10), + network, + )) ?? num.toBigInt(constants.ZERO), + ); + if (balance > maxFee) { + upgradeRequired = true; + deployRequired = true; + logger.log( + `getContractAddressByKey: no deployed cairo0 contract found with non-zero balance, force cairo ${CAIRO_VERSION_LEGACY}`, + ); + } else { + logger.log(`getContractAddressByKey: no deployed contract found, fallback to cairo ${CAIRO_VERSION}`); + } + } catch (err) { + logger.log(`getContractAddressByKey: balance check failed with error ${err}`); + throw err; + } } } return { address, signerPubKey: pk, - upgradeRequired: upgradeRequired, + upgradeRequired, + deployRequired, }; }; diff --git a/packages/starknet-snap/test/constants.test.ts b/packages/starknet-snap/test/constants.test.ts index 95c8e1f0..a2ac46e0 100644 --- a/packages/starknet-snap/test/constants.test.ts +++ b/packages/starknet-snap/test/constants.test.ts @@ -70,6 +70,13 @@ export const Cairo1Account1: AccContract = { chainId: constants.StarknetChainId.SN_GOERLI, }; +export const token0: Erc20Token = { + address: '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', + name: 'Ether', + symbol: 'ETH', + decimals: 18, + chainId: STARKNET_SEPOLIA_TESTNET_NETWORK.chainId, +}; export const token1: Erc20Token = { address: '0x244c20d51109adcf604fde1bbf878e5dcd549b3877ac87911ec6a158bd7aa62', name: 'Starknet ERC-20 sample', @@ -363,6 +370,7 @@ export const mainnetTxn1: Transaction = { }; export const getBalanceResp = ['0x0', '0x0']; +export const getNonZeroBalanceResp = ['0x100000', '0x0']; export const estimateDeployFeeResp = { overall_fee: num.toBigInt('0x0'), diff --git a/packages/starknet-snap/test/src/recoverAccounts.test.ts b/packages/starknet-snap/test/src/recoverAccounts.test.ts index 1ff1d2e6..ba98fd9a 100644 --- a/packages/starknet-snap/test/src/recoverAccounts.test.ts +++ b/packages/starknet-snap/test/src/recoverAccounts.test.ts @@ -66,14 +66,18 @@ describe('Test function: recoverAccounts', function () { for (let i = 0; i < maxScanned; i++) { if (i < validPublicKeys) { - getCorrectContractAddressStub - .onCall(i) - .resolves({ address: mainnetAccAddresses[i], signerPubKey: mainnetPublicKeys[i], upgradeRequired: false }); + getCorrectContractAddressStub.onCall(i).resolves({ + address: mainnetAccAddresses[i], + signerPubKey: mainnetPublicKeys[i], + upgradeRequired: false, + deployRequired: false, + }); } else { getCorrectContractAddressStub.onCall(i).resolves({ address: mainnetAccAddresses[i], signerPubKey: num.toHex(constants.ZERO), upgradeRequired: false, + deployRequired: false, }); } } @@ -110,14 +114,18 @@ describe('Test function: recoverAccounts', function () { for (let i = 0; i < maxScanned; i++) { if (i < validPublicKeys) { - getCorrectContractAddressStub - .onCall(i) - .resolves({ address: testnetAccAddresses[i], signerPubKey: testnetPublicKeys[i], upgradeRequired: false }); + getCorrectContractAddressStub.onCall(i).resolves({ + address: testnetAccAddresses[i], + signerPubKey: testnetPublicKeys[i], + upgradeRequired: false, + deployRequired: false, + }); } else { getCorrectContractAddressStub.onCall(i).resolves({ address: testnetAccAddresses[i], signerPubKey: num.toHex(constants.ZERO), upgradeRequired: false, + deployRequired: false, }); } } @@ -184,14 +192,18 @@ describe('Test function: recoverAccounts', function () { for (let i = 0; i < maxScanned; i++) { if (i < validPublicKeys) { - getCorrectContractAddressStub - .onCall(i) - .resolves({ address: mainnetAccAddresses[i], signerPubKey: mainnetPublicKeys[i], upgradeRequired: false }); + getCorrectContractAddressStub.onCall(i).resolves({ + address: mainnetAccAddresses[i], + signerPubKey: mainnetPublicKeys[i], + upgradeRequired: false, + deployRequired: false, + }); } else { getCorrectContractAddressStub.onCall(i).resolves({ address: mainnetAccAddresses[i], signerPubKey: num.toHex(constants.ZERO), upgradeRequired: false, + deployRequired: false, }); } } diff --git a/packages/starknet-snap/test/src/upgradeAccContract.test.ts b/packages/starknet-snap/test/src/upgradeAccContract.test.ts index bd3ed17a..7c427f7c 100644 --- a/packages/starknet-snap/test/src/upgradeAccContract.test.ts +++ b/packages/starknet-snap/test/src/upgradeAccContract.test.ts @@ -94,7 +94,7 @@ describe('Test function: upgradeAccContract', function () { result = err; } finally { expect(result).to.be.an('Error'); - expect(result.message).to.be.include('Contract has not deployed'); + expect(result.message).to.be.include('Contract is not deployed and address has no balance'); } }); @@ -161,6 +161,7 @@ describe('Test function: upgradeAccContract', function () { undefined, { maxFee: num.toBigInt(10000), + nonce: undefined, }, CAIRO_VERSION_LEGACY, ); @@ -195,6 +196,7 @@ describe('Test function: upgradeAccContract', function () { undefined, { maxFee: num.toBigInt(estimateFeeResp.suggestedMaxFee), + nonce: undefined, }, CAIRO_VERSION_LEGACY, ); diff --git a/packages/starknet-snap/test/utils/starknetUtils.test.ts b/packages/starknet-snap/test/utils/starknetUtils.test.ts index 7ef654f2..46f78a48 100644 --- a/packages/starknet-snap/test/utils/starknetUtils.test.ts +++ b/packages/starknet-snap/test/utils/starknetUtils.test.ts @@ -12,6 +12,11 @@ import { account1, account2, account3, + token1, + token2, + token0, + getBalanceResp, + getNonZeroBalanceResp, } from '../constants.test'; import { SnapState } from '../../src/types/snapState'; import { Calldata, num, Account, Provider, GetTransactionReceiptResponse } from 'starknet'; @@ -423,6 +428,12 @@ describe('Test function: getContractOwner', function () { }); describe('Test function: getCorrectContractAddress', function () { + const state: SnapState = { + accContracts: [], + erc20Tokens: [token0], + networks: [STARKNET_SEPOLIA_TESTNET_NETWORK], + transactions: [], + }; const walletStub = new WalletMock(); let getAccContractAddressAndCallDataStub: sinon.SinonStub; let getAccContractAddressAndCallDataLegacyStub: sinon.SinonStub; @@ -453,7 +464,7 @@ describe('Test function: getCorrectContractAddress', function () { sandbox.stub(utils, 'getSigner').callsFake(async () => PK); sandbox.stub(utils, 'getVersion').callsFake(async () => cairoVersionHex); - await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); + await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); expect(getAccContractAddressAndCallDataStub).to.have.been.calledOnceWith(PK); expect(getAccContractAddressAndCallDataLegacyStub).to.have.been.calledOnceWith(PK); }); @@ -465,7 +476,7 @@ describe('Test function: getCorrectContractAddress', function () { let result = null; try { - await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); + await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); } catch (e) { result = e; } finally { @@ -488,7 +499,7 @@ describe('Test function: getCorrectContractAddress', function () { let result = null; try { - await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); + await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); } catch (e) { result = e; } finally { @@ -504,7 +515,7 @@ describe('Test function: getCorrectContractAddress', function () { getSignerStub = sandbox.stub(utils, 'getSigner').resolves(PK); getOwnerStub = sandbox.stub(utils, 'getOwner').resolves(PK); - const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); + const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); expect(getVersionStub).to.have.been.calledOnceWith(account1.address, STARKNET_SEPOLIA_TESTNET_NETWORK); expect(getOwnerStub).to.have.been.calledOnceWith(account1.address, STARKNET_SEPOLIA_TESTNET_NETWORK); expect(getSignerStub).to.have.been.callCount(0); @@ -514,7 +525,7 @@ describe('Test function: getCorrectContractAddress', function () { }); }); - describe(`when contact is Cairo${CAIRO_VERSION} has not deployed`, function () { + describe(`when contract is Cairo${CAIRO_VERSION} has not deployed`, function () { describe(`when when is Cairo${CAIRO_VERSION_LEGACY} has deployed`, function () { describe(`when when is Cairo${CAIRO_VERSION_LEGACY} has upgraded`, function () { it(`should return Cairo${CAIRO_VERSION_LEGACY} address with upgrade = false`, async function () { @@ -528,7 +539,7 @@ describe('Test function: getCorrectContractAddress', function () { getSignerStub = sandbox.stub(utils, 'getSigner').resolves(PK); getOwnerStub = sandbox.stub(utils, 'getOwner').resolves(PK); - const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); + const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); expect(getOwnerStub).to.have.been.calledOnceWith(account2.address, STARKNET_SEPOLIA_TESTNET_NETWORK); expect(getSignerStub).to.have.been.callCount(0); @@ -550,7 +561,7 @@ describe('Test function: getCorrectContractAddress', function () { getSignerStub = sandbox.stub(utils, 'getSigner').resolves(PK); getOwnerStub = sandbox.stub(utils, 'getOwner').resolves(PK); - const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); + const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); expect(getSignerStub).to.have.been.calledOnceWith(account2.address, STARKNET_SEPOLIA_TESTNET_NETWORK); expect(getOwnerStub).to.have.been.callCount(0); @@ -561,14 +572,15 @@ describe('Test function: getCorrectContractAddress', function () { }); }); - describe(`when when is Cairo${CAIRO_VERSION_LEGACY} has not deployed`, function () { - it(`should return Cairo${CAIRO_VERSION} address with upgrade = false`, async function () { + describe(`when when Cairo${CAIRO_VERSION_LEGACY} is not deployed`, function () { + it(`should return Cairo${CAIRO_VERSION} address with upgrade = false if no balance`, async function () { sandbox.stub(utils, 'getVersion').rejects(new Error('Contract not found')); + sandbox.stub(utils, 'getBalance').callsFake(async () => getBalanceResp[0]); getSignerStub = sandbox.stub(utils, 'getSigner').resolves(PK); getOwnerStub = sandbox.stub(utils, 'getOwner').resolves(PK); - const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); + const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); expect(getSignerStub).to.have.been.callCount(0); expect(getOwnerStub).to.have.been.callCount(0); @@ -576,6 +588,22 @@ describe('Test function: getCorrectContractAddress', function () { expect(result.signerPubKey).to.be.eq(''); expect(result.upgradeRequired).to.be.eq(false); }); + it(`should return Cairo${CAIRO_VERSION_LEGACY} address with upgrade = true and deploy = true if no balance`, async function () { + sandbox.stub(utils, 'getVersion').rejects(new Error('Contract not found')); + sandbox.stub(utils, 'getBalance').callsFake(async () => getNonZeroBalanceResp[0]); + + getSignerStub = sandbox.stub(utils, 'getSigner').resolves(PK); + getOwnerStub = sandbox.stub(utils, 'getOwner').resolves(PK); + + const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); + + expect(getSignerStub).to.have.been.callCount(0); + expect(getOwnerStub).to.have.been.callCount(0); + expect(result.address).to.be.eq(account1.address); + expect(result.signerPubKey).to.be.eq(''); + expect(result.upgradeRequired).to.be.eq(true); + expect(result.deployRequired).to.be.eq(true); + }); }); }); }); diff --git a/packages/wallet-ui/src/App.tsx b/packages/wallet-ui/src/App.tsx index d37475e8..90cd14cd 100644 --- a/packages/wallet-ui/src/App.tsx +++ b/packages/wallet-ui/src/App.tsx @@ -26,7 +26,7 @@ library.add(fas, far); function App() { const { initSnap, getWalletData, checkConnection } = useStarkNetSnap(); const { connected, forceReconnect, provider } = useAppSelector((state) => state.wallet); - const { infoModalVisible, minVersionModalVisible, upgradeModalVisible } = useAppSelector((state) => state.modals); + const { infoModalVisible, minVersionModalVisible, upgradeModalVisible, upgradeModalDeployText } = useAppSelector((state) => state.modals); const { loader } = useAppSelector((state) => state.UI); const networks = useAppSelector((state) => state.networks); const { accounts } = useAppSelector((state) => state.wallet); @@ -74,7 +74,7 @@ function App() { - + {loading && {loader.loadingMessage}} diff --git a/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx b/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx index 55602fdb..a43e3f8d 100644 --- a/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx +++ b/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx @@ -9,6 +9,7 @@ import { UpgradeButton, StarknetLogo, Title, Wrapper, DescriptionCentered, Txnli interface Props { address: string; + deploy: boolean; // whether to deploy before upgrade } enum Stage { @@ -18,7 +19,7 @@ enum Stage { FAIL = 3, } -export const UpgradeModelView = ({ address }: Props) => { +export const UpgradeModelView = ({ address, deploy }: Props) => { const dispatch = useAppDispatch(); const { upgradeAccount, waitForAccountUpdate } = useStarkNetSnap(); const [txnHash, setTxnHash] = useState(''); @@ -29,7 +30,7 @@ export const UpgradeModelView = ({ address }: Props) => { const onUpgrade = async () => { try { - const resp = await upgradeAccount(address, '0', chainId); + const resp = await upgradeAccount(address, '0', chainId, deploy); if (resp === false) { return; @@ -69,6 +70,8 @@ export const UpgradeModelView = ({ address }: Props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [stage, dispatch]); + const upgradeTxt = deploy ? 'Deploy & Upgrade' : 'Upgrade'; + const renderComponent = () => { switch (stage) { case Stage.INIT: @@ -83,11 +86,11 @@ export const UpgradeModelView = ({ address }: Props) => { this version.

- Click on the "Upgrade" button to install it. + Click on the "{upgradeTxt}" button to install it.
Thank you! - Upgrade + {upgradeTxt} ); case Stage.WAITING_FOR_TXN: diff --git a/packages/wallet-ui/src/services/useStarkNetSnap.ts b/packages/wallet-ui/src/services/useStarkNetSnap.ts index 3e59f1c7..aeb15cc9 100644 --- a/packages/wallet-ui/src/services/useStarkNetSnap.ts +++ b/packages/wallet-ui/src/services/useStarkNetSnap.ts @@ -1,4 +1,4 @@ -import { setInfoModalVisible, setMinVersionModalVisible, setUpgradeModalVisible } from 'slices/modalSlice'; +import { setInfoModalVisible, setMinVersionModalVisible, setUpgradeModalVisible, setUpgradeModalDeployText } from 'slices/modalSlice'; import { setNetworks } from 'slices/networkSlice'; import { useAppDispatch, useAppSelector } from 'hooks/redux'; import { @@ -235,11 +235,14 @@ export const useStarkNetSnap = () => { const tokens = await getTokens(chainId); let acc: Account[] | Account = await recoverAccounts(chainId); let upgradeRequired = false; - + let deployRequired = false; + console.log("accounts") + console.log(acc) if (!acc || acc.length === 0 || !acc[0].publicKey) { acc = await addAccount(chainId); } else { upgradeRequired = (Array.isArray(acc) ? acc[0].upgradeRequired : (acc as Account).upgradeRequired) ?? false; + deployRequired = (Array.isArray(acc) ? acc[0].deployRequired : (acc as Account).deployRequired) ?? false; } const tokenBalances = await Promise.all( @@ -270,6 +273,7 @@ export const useStarkNetSnap = () => { dispatch(setInfoModalVisible(true)); } dispatch(setUpgradeModalVisible(upgradeRequired)); + dispatch(setUpgradeModalDeployText(deployRequired)); dispatch(disableLoading()); }; @@ -415,7 +419,7 @@ export const useStarkNetSnap = () => { } }; - const upgradeAccount = async (contractAddress: string, maxFee: string, chainId: string) => { + const upgradeAccount = async (contractAddress: string, maxFee: string, chainId: string, forceDeploy: boolean) => { dispatch(enableLoadingWithMessage('Upgrading account...')); try { const response = await provider.request({ @@ -429,6 +433,7 @@ export const useStarkNetSnap = () => { contractAddress, maxFee, chainId, + forceDeploy, }, }, }, diff --git a/packages/wallet-ui/src/slices/modalSlice.ts b/packages/wallet-ui/src/slices/modalSlice.ts index 329e8314..b10ddab3 100644 --- a/packages/wallet-ui/src/slices/modalSlice.ts +++ b/packages/wallet-ui/src/slices/modalSlice.ts @@ -4,12 +4,14 @@ export interface modalState { infoModalVisible: boolean; minVersionModalVisible: boolean; upgradeModalVisible: boolean; + upgradeModalDeployText: boolean; } const initialState: modalState = { infoModalVisible: false, minVersionModalVisible: false, upgradeModalVisible: false, + upgradeModalDeployText: false, }; export const modalSlice = createSlice({ @@ -23,12 +25,15 @@ export const modalSlice = createSlice({ setUpgradeModalVisible: (state, { payload }) => { state.upgradeModalVisible = payload; }, + setUpgradeModalDeployText: (state, { payload }) => { + state.upgradeModalDeployText = payload; + }, setMinVersionModalVisible: (state, { payload }) => { state.minVersionModalVisible = payload; }, }, }); -export const { setInfoModalVisible, setMinVersionModalVisible, setUpgradeModalVisible } = modalSlice.actions; +export const { setInfoModalVisible, setMinVersionModalVisible, setUpgradeModalVisible, setUpgradeModalDeployText } = modalSlice.actions; export default modalSlice.reducer; diff --git a/packages/wallet-ui/src/types/index.ts b/packages/wallet-ui/src/types/index.ts index 5a5855e3..cb22cbc8 100644 --- a/packages/wallet-ui/src/types/index.ts +++ b/packages/wallet-ui/src/types/index.ts @@ -1,7 +1,7 @@ import * as Types from '@consensys/starknet-snap/src/types/snapState'; import { BigNumber } from 'ethers'; -export type Account = Pick; +export type Account = Pick; export type Network = Pick; export interface Erc20TokenBalance extends Types.Erc20Token { From c6b7ddd7e42cba093c62e8c3463518e57f85f370 Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Thu, 4 Jul 2024 19:02:27 +0200 Subject: [PATCH 02/16] feat: balance check in upgradecontract --- packages/starknet-snap/src/createAccount.ts | 14 ++-- .../starknet-snap/src/upgradeAccContract.ts | 67 ++++++++++--------- .../starknet-snap/src/utils/starknetUtils.ts | 11 ++- .../UpgradeModel/UpgradeModel.stories.tsx | 4 +- .../wallet-ui/src/services/useStarkNetSnap.ts | 5 +- 5 files changed, 59 insertions(+), 42 deletions(-) diff --git a/packages/starknet-snap/src/createAccount.ts b/packages/starknet-snap/src/createAccount.ts index ca9bcdf9..a71b0e25 100644 --- a/packages/starknet-snap/src/createAccount.ts +++ b/packages/starknet-snap/src/createAccount.ts @@ -4,6 +4,7 @@ import { getAccContractAddressAndCallData, deployAccount, waitForTransaction, + getAccContractAddressAndCallDataLegacy, } from './utils/starknetUtils'; import { getNetworkFromChainId, @@ -16,6 +17,8 @@ import { AccContract, VoyagerTransactionType, Transaction, TransactionStatus } f import { ApiParams, CreateAccountRequestParams } from './types/snapApi'; import { heading, panel, text, DialogType } from '@metamask/snaps-sdk'; import { logger } from './utils/logger'; +import { CAIRO_VERSION, CAIRO_VERSION_LEGACY } from './utils/constants'; +import { CairoContract, CairoVersion } from 'starknet'; /** * Create an starknet account. @@ -24,11 +27,12 @@ import { logger } from './utils/logger'; * @param silentMode - The flag to disable the confirmation dialog from snap. * @param waitMode - The flag to enable an determination by doing an recursive fetch to check if the deploy account status is on L2 or not. The wait mode is only useful when it compose with other txn together, it can make sure the deploy txn execute complete, avoiding the latter txn failed. */ -export async function createAccount(params: ApiParams, silentMode = false, waitMode = false) { +export async function createAccount(params: ApiParams, silentMode = false, waitMode = false, cairoVersion: CairoVersion = CAIRO_VERSION) { try { const { state, wallet, saveMutex, keyDeriver, requestParams } = params; const requestParamsObj = requestParams as CreateAccountRequestParams; - + console.log("requestParamsObj.addressIndex") + console.log(requestParamsObj.addressIndex) const addressIndex = getValidNumber(requestParamsObj.addressIndex, -1, 0); const network = getNetworkFromChainId(state, requestParamsObj.chainId); const deploy = !!requestParamsObj.deploy; @@ -39,7 +43,9 @@ export async function createAccount(params: ApiParams, silentMode = false, waitM addressIndex: addressIndexInUsed, derivationPath, } = await getKeysFromAddressIndex(keyDeriver, network.chainId, state, addressIndex); - const { address: contractAddress, callData: contractCallData } = getAccContractAddressAndCallData(publicKey); + + const { address: contractAddress, callData: contractCallData } = + cairoVersion == CAIRO_VERSION_LEGACY ? getAccContractAddressAndCallDataLegacy(publicKey) : getAccContractAddressAndCallData(publicKey); logger.log( `createAccount:\ncontractAddress = ${contractAddress}\npublicKey = ${publicKey}\naddressIndex = ${addressIndexInUsed}`, ); @@ -69,7 +75,7 @@ export async function createAccount(params: ApiParams, silentMode = false, waitM } // Deploy account will auto estimate the fee from the network if not provided - const deployResp = await deployAccount(network, contractAddress, contractCallData, publicKey, privateKey); + const deployResp = await deployAccount(network, contractAddress, contractCallData, publicKey, privateKey, cairoVersion); if (deployResp.contract_address && deployResp.transaction_hash) { const userAccount: AccContract = { diff --git a/packages/starknet-snap/src/upgradeAccContract.ts b/packages/starknet-snap/src/upgradeAccContract.ts index 2fe3f7b1..fd5bd60d 100644 --- a/packages/starknet-snap/src/upgradeAccContract.ts +++ b/packages/starknet-snap/src/upgradeAccContract.ts @@ -34,16 +34,42 @@ export async function upgradeAccContract(params: ApiParams) { } const network = getNetworkFromChainId(state, chainId); - if (!(await isAccountDeployed(network, contractAddress)) && !forceDeploy) { - throw new Error('Contract is not deployed and address has no balance'); - } - if (!(await isUpgradeRequired(network, contractAddress))) { - throw new Error('Upgrade is not required'); + if(!forceDeploy){ + if (!(await isAccountDeployed(network, contractAddress))) { + throw new Error('Contract is not deployed and address has no balance'); + } + + if (!(await isUpgradeRequired(network, contractAddress))) { + throw new Error('Upgrade is not required'); + } } - const { privateKey, addressIndex } = await getKeysFromAddress(keyDeriver, network, state, contractAddress); + const accountDeployed = await isAccountDeployed(network, contractAddress); + if (forceDeploy) { + if (accountDeployed) { + throw new Error(`Upgrade with Force Deploy cannot be executed on deployed account`); + } + //Deploy account before sending the transaction + logger.log('upgradeAccContract:\nFirst transaction : send deploy transaction'); + const createAccountApiParams = { + state, + wallet: params.wallet, + saveMutex: params.saveMutex, + keyDeriver, + requestParams: { + addressIndex, + deploy: true, + chainId: requestParamsObj.chainId, + }, + }; + await createAccount(createAccountApiParams, false, true, CAIRO_VERSION_LEGACY); + } + + //In case we forceDeployed we assign a nonce of 1 to make sure it does after the deploy transaction + const nonce = forceDeploy ? 1 : undefined; + const method = 'upgrade'; const calldata = CallData.compile({ @@ -53,11 +79,13 @@ export async function upgradeAccContract(params: ApiParams) { const txnInvocation = getUpgradeTxnInvocation(contractAddress); + let maxFee = requestParamsObj.maxFee ? num.toBigInt(requestParamsObj.maxFee) : constants.ZERO; + console.log("estimateFee") maxFee = num.toBigInt(await estimateAccountUpgradeFee(network, contractAddress, privateKey, maxFee)); - + console.log("getSendTxnText") const dialogComponents = getSendTxnText(state, contractAddress, method, calldata, contractAddress, maxFee, network); - + console.log("whaat") const response = await wallet.request({ method: 'snap_dialog', params: { @@ -70,29 +98,6 @@ export async function upgradeAccContract(params: ApiParams) { logger.log(`upgradeAccContract:\ntxnInvocation: ${toJson(txnInvocation)}\nmaxFee: ${maxFee.toString()}}`); - const accountDeployed = await isAccountDeployed(network, contractAddress); - if (forceDeploy) { - if (accountDeployed) { - throw new Error(`Upgrade with Force Deploy cannot be executed on deployed account`); - } - //Deploy account before sending the transaction - logger.log('upgradeAccContract:\nFirst transaction : send deploy transaction'); - const createAccountApiParams = { - state, - wallet: params.wallet, - saveMutex: params.saveMutex, - keyDeriver, - requestParams: { - addressIndex, - deploy: true, - chainId: requestParamsObj.chainId, - }, - }; - await createAccount(createAccountApiParams, true, true); - } - - //In case we forceDeployed we assign a nonce of 1 to make sure it does after the deploy transaction - const nonce = forceDeploy ? 1 : undefined; const txnResp = await executeTxn( network, diff --git a/packages/starknet-snap/src/utils/starknetUtils.ts b/packages/starknet-snap/src/utils/starknetUtils.ts index 0494643d..1bbd3b2a 100644 --- a/packages/starknet-snap/src/utils/starknetUtils.ts +++ b/packages/starknet-snap/src/utils/starknetUtils.ts @@ -52,6 +52,7 @@ import { getAddressKey } from './keyPair'; import { getAccount, getAccounts, + getErc20Token, getEtherErc20Token, getRPCUrl, getTransactionFromVoyagerUrl, @@ -61,6 +62,7 @@ import { import { logger } from './logger'; import { RpcV4GetTransactionReceiptResponse } from '../types/snapApi'; import { hexToString } from './formatterUtils'; +import { getErc20TokenBalance } from '../getErc20TokenBalance'; export const getCallDataArray = (callDataStr: string): string[] => { return (callDataStr ?? '') @@ -184,8 +186,9 @@ export const deployAccount = async ( cairoVersion?: CairoVersion, invocationsDetails?: UniversalDetails, ): Promise => { + const classHash = cairoVersion == CAIRO_VERSION ? ACCOUNT_CLASS_HASH : PROXY_CONTRACT_HASH; const deployAccountPayload = { - classHash: ACCOUNT_CLASS_HASH, + classHash: classHash, contractAddress: contractAddress, constructorCalldata: contractCallData, addressSalt, @@ -805,18 +808,20 @@ export const getCorrectContractAddress = async ( } // Edge case detection logger.log(`getContractAddressByKey: no deployed contract found, checking balance for edge cases`); - + console.log(getEtherErc20Token(state, network.chainId)?.address) try { const balance = num.toBigInt( (await getBalance( + contractAddressLegacy, getEtherErc20Token(state, network.chainId)?.address, - num.toBigInt(contractAddressLegacy).toString(10), network, )) ?? num.toBigInt(constants.ZERO), ); + console.log(balance) if (balance > maxFee) { upgradeRequired = true; deployRequired = true; + address = contractAddressLegacy; logger.log( `getContractAddressByKey: no deployed cairo0 contract found with non-zero balance, force cairo ${CAIRO_VERSION_LEGACY}`, ); diff --git a/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.stories.tsx b/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.stories.tsx index 4e3f0acb..c5b840ce 100644 --- a/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.stories.tsx +++ b/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.stories.tsx @@ -8,7 +8,7 @@ export default { component: UpgradeModelView, } as Meta; -export const ContentOnly = () => ; +export const ContentOnly = () => ; export const WithModal = () => { let [isOpen, setIsOpen] = useState(false); @@ -16,7 +16,7 @@ export const WithModal = () => { <> - + ); diff --git a/packages/wallet-ui/src/services/useStarkNetSnap.ts b/packages/wallet-ui/src/services/useStarkNetSnap.ts index aeb15cc9..47725a1e 100644 --- a/packages/wallet-ui/src/services/useStarkNetSnap.ts +++ b/packages/wallet-ui/src/services/useStarkNetSnap.ts @@ -238,11 +238,12 @@ export const useStarkNetSnap = () => { let deployRequired = false; console.log("accounts") console.log(acc) - if (!acc || acc.length === 0 || !acc[0].publicKey) { + deployRequired = (Array.isArray(acc) ? acc[0].deployRequired : (acc as Account).deployRequired) ?? false; + if (!acc || acc.length === 0 || (!acc[0].publicKey && !deployRequired)) { acc = await addAccount(chainId); } else { upgradeRequired = (Array.isArray(acc) ? acc[0].upgradeRequired : (acc as Account).upgradeRequired) ?? false; - deployRequired = (Array.isArray(acc) ? acc[0].deployRequired : (acc as Account).deployRequired) ?? false; + } const tokenBalances = await Promise.all( From 767ad73501610bad9d5a471e90af9701dbb16086 Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Fri, 5 Jul 2024 08:19:55 +0200 Subject: [PATCH 03/16] chore: lint + prettier --- packages/starknet-snap/src/createAccount.ts | 28 ++++++-- packages/starknet-snap/src/recoverAccounts.ts | 2 +- .../starknet-snap/src/upgradeAccContract.ts | 64 ++++++++++++++----- packages/starknet-snap/src/utils/constants.ts | 3 + .../starknet-snap/src/utils/starknetUtils.ts | 14 ++-- .../test/utils/starknetUtils.test.ts | 8 +-- packages/wallet-ui/src/App.tsx | 4 +- .../wallet-ui/src/services/useStarkNetSnap.ts | 12 ++-- packages/wallet-ui/src/slices/modalSlice.ts | 3 +- 9 files changed, 96 insertions(+), 42 deletions(-) diff --git a/packages/starknet-snap/src/createAccount.ts b/packages/starknet-snap/src/createAccount.ts index a71b0e25..652961ef 100644 --- a/packages/starknet-snap/src/createAccount.ts +++ b/packages/starknet-snap/src/createAccount.ts @@ -27,12 +27,17 @@ import { CairoContract, CairoVersion } from 'starknet'; * @param silentMode - The flag to disable the confirmation dialog from snap. * @param waitMode - The flag to enable an determination by doing an recursive fetch to check if the deploy account status is on L2 or not. The wait mode is only useful when it compose with other txn together, it can make sure the deploy txn execute complete, avoiding the latter txn failed. */ -export async function createAccount(params: ApiParams, silentMode = false, waitMode = false, cairoVersion: CairoVersion = CAIRO_VERSION) { +export async function createAccount( + params: ApiParams, + silentMode = false, + waitMode = false, + cairoVersion: CairoVersion = CAIRO_VERSION, +) { try { const { state, wallet, saveMutex, keyDeriver, requestParams } = params; const requestParamsObj = requestParams as CreateAccountRequestParams; - console.log("requestParamsObj.addressIndex") - console.log(requestParamsObj.addressIndex) + console.log('requestParamsObj.addressIndex'); + console.log(requestParamsObj.addressIndex); const addressIndex = getValidNumber(requestParamsObj.addressIndex, -1, 0); const network = getNetworkFromChainId(state, requestParamsObj.chainId); const deploy = !!requestParamsObj.deploy; @@ -43,9 +48,11 @@ export async function createAccount(params: ApiParams, silentMode = false, waitM addressIndex: addressIndexInUsed, derivationPath, } = await getKeysFromAddressIndex(keyDeriver, network.chainId, state, addressIndex); - - const { address: contractAddress, callData: contractCallData } = - cairoVersion == CAIRO_VERSION_LEGACY ? getAccContractAddressAndCallDataLegacy(publicKey) : getAccContractAddressAndCallData(publicKey); + + const { address: contractAddress, callData: contractCallData } = + cairoVersion == CAIRO_VERSION_LEGACY + ? getAccContractAddressAndCallDataLegacy(publicKey) + : getAccContractAddressAndCallData(publicKey); logger.log( `createAccount:\ncontractAddress = ${contractAddress}\npublicKey = ${publicKey}\naddressIndex = ${addressIndexInUsed}`, ); @@ -75,7 +82,14 @@ export async function createAccount(params: ApiParams, silentMode = false, waitM } // Deploy account will auto estimate the fee from the network if not provided - const deployResp = await deployAccount(network, contractAddress, contractCallData, publicKey, privateKey, cairoVersion); + const deployResp = await deployAccount( + network, + contractAddress, + contractCallData, + publicKey, + privateKey, + cairoVersion, + ); if (deployResp.contract_address && deployResp.transaction_hash) { const userAccount: AccContract = { diff --git a/packages/starknet-snap/src/recoverAccounts.ts b/packages/starknet-snap/src/recoverAccounts.ts index d3fe2c61..5c8e6edc 100644 --- a/packages/starknet-snap/src/recoverAccounts.ts +++ b/packages/starknet-snap/src/recoverAccounts.ts @@ -34,7 +34,7 @@ export async function recoverAccounts(params: ApiParams) { state, i, ); - + // [TODO] : add fee here to getCorrectContractAddress const { address: contractAddress, signerPubKey: signerPublicKey, diff --git a/packages/starknet-snap/src/upgradeAccContract.ts b/packages/starknet-snap/src/upgradeAccContract.ts index fd5bd60d..2a5443a6 100644 --- a/packages/starknet-snap/src/upgradeAccContract.ts +++ b/packages/starknet-snap/src/upgradeAccContract.ts @@ -1,5 +1,5 @@ import { toJson } from './utils/serializer'; -import { num, constants, CallData } from 'starknet'; +import { num, constants, CallData, EstimateFee, TransactionType, Invocations } from 'starknet'; import { Transaction, TransactionStatus, VoyagerTransactionType } from './types/snapState'; import { getKeysFromAddress, @@ -9,10 +9,14 @@ import { isAccountDeployed, getUpgradeTxnInvocation, estimateAccountUpgradeFee, + getAccContractAddressAndCallDataLegacy, + estimateAccountDeployFee, + estimateFeeBulk, + addFeesFromAllTransactions, } from './utils/starknetUtils'; import { getNetworkFromChainId, upsertTransaction, getSendTxnText } from './utils/snapUtils'; import { ApiParams, UpgradeTransactionRequestParams } from './types/snapApi'; -import { ACCOUNT_CLASS_HASH, CAIRO_VERSION_LEGACY } from './utils/constants'; +import { ACCOUNT_CLASS_HASH, CAIRO_VERSION_LEGACY, PROXY_CONTRACT_HASH } from './utils/constants'; import { heading, panel, DialogType } from '@metamask/snaps-sdk'; import { logger } from './utils/logger'; import { createAccount } from './createAccount'; @@ -35,22 +39,61 @@ export async function upgradeAccContract(params: ApiParams) { const network = getNetworkFromChainId(state, chainId); - if(!forceDeploy){ + if (!forceDeploy) { if (!(await isAccountDeployed(network, contractAddress))) { throw new Error('Contract is not deployed and address has no balance'); } - + if (!(await isUpgradeRequired(network, contractAddress))) { throw new Error('Upgrade is not required'); } } - const { privateKey, addressIndex } = await getKeysFromAddress(keyDeriver, network, state, contractAddress); + const { privateKey, addressIndex, publicKey } = await getKeysFromAddress(keyDeriver, network, state, contractAddress); + + const upgradeAccountpayload = getUpgradeTxnInvocation(contractAddress) + const bulkTransactions: Invocations =[]; + // [TODO] This fails. + // const bulkTransactions: Invocations =[ + // { + // type: TransactionType.INVOKE, + // payload: upgradeAccountpayload, + // }]; + const accountDeployed = await isAccountDeployed(network, contractAddress); if (forceDeploy) { if (accountDeployed) { throw new Error(`Upgrade with Force Deploy cannot be executed on deployed account`); } - //Deploy account before sending the transaction + + const { callData } = getAccContractAddressAndCallDataLegacy(publicKey); + const deployAccountpayload = { + classHash: PROXY_CONTRACT_HASH, + contractAddress: contractAddress, + constructorCalldata: callData, + addressSalt: publicKey, + }; + + bulkTransactions.unshift({ + type: TransactionType.DEPLOY_ACCOUNT, + payload: deployAccountpayload, + }); + } + + const fees = await estimateFeeBulk( + network, + contractAddress, + privateKey, + bulkTransactions, + undefined, + ); + + const estimateFeeResp = addFeesFromAllTransactions(fees); + + const maxFee = estimateFeeResp.suggestedMaxFee.toString(10); + logger.log(`MaxFee: ${maxFee}`); + + if (forceDeploy) { + //Deploy account before sending the upgrade transaction logger.log('upgradeAccContract:\nFirst transaction : send deploy transaction'); const createAccountApiParams = { state, @@ -69,7 +112,6 @@ export async function upgradeAccContract(params: ApiParams) { //In case we forceDeployed we assign a nonce of 1 to make sure it does after the deploy transaction const nonce = forceDeploy ? 1 : undefined; - const method = 'upgrade'; const calldata = CallData.compile({ @@ -78,14 +120,7 @@ export async function upgradeAccContract(params: ApiParams) { }); const txnInvocation = getUpgradeTxnInvocation(contractAddress); - - - let maxFee = requestParamsObj.maxFee ? num.toBigInt(requestParamsObj.maxFee) : constants.ZERO; - console.log("estimateFee") - maxFee = num.toBigInt(await estimateAccountUpgradeFee(network, contractAddress, privateKey, maxFee)); - console.log("getSendTxnText") const dialogComponents = getSendTxnText(state, contractAddress, method, calldata, contractAddress, maxFee, network); - console.log("whaat") const response = await wallet.request({ method: 'snap_dialog', params: { @@ -98,7 +133,6 @@ export async function upgradeAccContract(params: ApiParams) { logger.log(`upgradeAccContract:\ntxnInvocation: ${toJson(txnInvocation)}\nmaxFee: ${maxFee.toString()}}`); - const txnResp = await executeTxn( network, contractAddress, diff --git a/packages/starknet-snap/src/utils/constants.ts b/packages/starknet-snap/src/utils/constants.ts index a365bbe3..3e260872 100644 --- a/packages/starknet-snap/src/utils/constants.ts +++ b/packages/starknet-snap/src/utils/constants.ts @@ -187,3 +187,6 @@ export const MIN_ACC_CONTRACT_VERSION = [0, 3, 0]; export const CAIRO_VERSION = '1'; export const CAIRO_VERSION_LEGACY = '0'; + +// Cairo 1 : 0x07874b73bbf01b78eafd0d96061d47aafc9c5c15bd58c290d7ddacdd10f1f84b +// Cairo 0 : 0x07fffa2dd979c33b5fe6e3129bea0fcc9a7492e6404cae53a8d40c890c9512d3 diff --git a/packages/starknet-snap/src/utils/starknetUtils.ts b/packages/starknet-snap/src/utils/starknetUtils.ts index 1bbd3b2a..6db94d8b 100644 --- a/packages/starknet-snap/src/utils/starknetUtils.ts +++ b/packages/starknet-snap/src/utils/starknetUtils.ts @@ -209,8 +209,9 @@ export const estimateAccountDeployFee = async ( cairoVersion?: CairoVersion, invocationsDetails?: UniversalDetails, ): Promise => { + const classHash = cairoVersion == CAIRO_VERSION ? ACCOUNT_CLASS_HASH : PROXY_CONTRACT_HASH; const deployAccountPayload = { - classHash: ACCOUNT_CLASS_HASH, + classHash: classHash, contractAddress: contractAddress, constructorCalldata: contractCallData, addressSalt, @@ -808,16 +809,13 @@ export const getCorrectContractAddress = async ( } // Edge case detection logger.log(`getContractAddressByKey: no deployed contract found, checking balance for edge cases`); - console.log(getEtherErc20Token(state, network.chainId)?.address) + console.log(getEtherErc20Token(state, network.chainId)?.address); try { const balance = num.toBigInt( - (await getBalance( - contractAddressLegacy, - getEtherErc20Token(state, network.chainId)?.address, - network, - )) ?? num.toBigInt(constants.ZERO), + (await getBalance(contractAddressLegacy, getEtherErc20Token(state, network.chainId)?.address, network)) ?? + num.toBigInt(constants.ZERO), ); - console.log(balance) + console.log(balance); if (balance > maxFee) { upgradeRequired = true; deployRequired = true; diff --git a/packages/starknet-snap/test/utils/starknetUtils.test.ts b/packages/starknet-snap/test/utils/starknetUtils.test.ts index 46f78a48..21956bf4 100644 --- a/packages/starknet-snap/test/utils/starknetUtils.test.ts +++ b/packages/starknet-snap/test/utils/starknetUtils.test.ts @@ -12,8 +12,6 @@ import { account1, account2, account3, - token1, - token2, token0, getBalanceResp, getNonZeroBalanceResp, @@ -573,7 +571,7 @@ describe('Test function: getCorrectContractAddress', function () { }); describe(`when when Cairo${CAIRO_VERSION_LEGACY} is not deployed`, function () { - it(`should return Cairo${CAIRO_VERSION} address with upgrade = false if no balance`, async function () { + it(`should return Cairo${CAIRO_VERSION} address with upgrade = false and deploy = false if no balance`, async function () { sandbox.stub(utils, 'getVersion').rejects(new Error('Contract not found')); sandbox.stub(utils, 'getBalance').callsFake(async () => getBalanceResp[0]); @@ -588,7 +586,7 @@ describe('Test function: getCorrectContractAddress', function () { expect(result.signerPubKey).to.be.eq(''); expect(result.upgradeRequired).to.be.eq(false); }); - it(`should return Cairo${CAIRO_VERSION_LEGACY} address with upgrade = true and deploy = true if no balance`, async function () { + it(`should return Cairo${CAIRO_VERSION_LEGACY} address with upgrade = true and deploy = true if balance`, async function () { sandbox.stub(utils, 'getVersion').rejects(new Error('Contract not found')); sandbox.stub(utils, 'getBalance').callsFake(async () => getNonZeroBalanceResp[0]); @@ -599,7 +597,7 @@ describe('Test function: getCorrectContractAddress', function () { expect(getSignerStub).to.have.been.callCount(0); expect(getOwnerStub).to.have.been.callCount(0); - expect(result.address).to.be.eq(account1.address); + expect(result.address).to.be.eq(account2.address); expect(result.signerPubKey).to.be.eq(''); expect(result.upgradeRequired).to.be.eq(true); expect(result.deployRequired).to.be.eq(true); diff --git a/packages/wallet-ui/src/App.tsx b/packages/wallet-ui/src/App.tsx index 90cd14cd..7a640082 100644 --- a/packages/wallet-ui/src/App.tsx +++ b/packages/wallet-ui/src/App.tsx @@ -26,7 +26,9 @@ library.add(fas, far); function App() { const { initSnap, getWalletData, checkConnection } = useStarkNetSnap(); const { connected, forceReconnect, provider } = useAppSelector((state) => state.wallet); - const { infoModalVisible, minVersionModalVisible, upgradeModalVisible, upgradeModalDeployText } = useAppSelector((state) => state.modals); + const { infoModalVisible, minVersionModalVisible, upgradeModalVisible, upgradeModalDeployText } = useAppSelector( + (state) => state.modals, + ); const { loader } = useAppSelector((state) => state.UI); const networks = useAppSelector((state) => state.networks); const { accounts } = useAppSelector((state) => state.wallet); diff --git a/packages/wallet-ui/src/services/useStarkNetSnap.ts b/packages/wallet-ui/src/services/useStarkNetSnap.ts index 47725a1e..fe6fb565 100644 --- a/packages/wallet-ui/src/services/useStarkNetSnap.ts +++ b/packages/wallet-ui/src/services/useStarkNetSnap.ts @@ -1,4 +1,9 @@ -import { setInfoModalVisible, setMinVersionModalVisible, setUpgradeModalVisible, setUpgradeModalDeployText } from 'slices/modalSlice'; +import { + setInfoModalVisible, + setMinVersionModalVisible, + setUpgradeModalVisible, + setUpgradeModalDeployText, +} from 'slices/modalSlice'; import { setNetworks } from 'slices/networkSlice'; import { useAppDispatch, useAppSelector } from 'hooks/redux'; import { @@ -236,14 +241,13 @@ export const useStarkNetSnap = () => { let acc: Account[] | Account = await recoverAccounts(chainId); let upgradeRequired = false; let deployRequired = false; - console.log("accounts") - console.log(acc) + console.log('accounts'); + console.log(acc); deployRequired = (Array.isArray(acc) ? acc[0].deployRequired : (acc as Account).deployRequired) ?? false; if (!acc || acc.length === 0 || (!acc[0].publicKey && !deployRequired)) { acc = await addAccount(chainId); } else { upgradeRequired = (Array.isArray(acc) ? acc[0].upgradeRequired : (acc as Account).upgradeRequired) ?? false; - } const tokenBalances = await Promise.all( diff --git a/packages/wallet-ui/src/slices/modalSlice.ts b/packages/wallet-ui/src/slices/modalSlice.ts index b10ddab3..56bf975a 100644 --- a/packages/wallet-ui/src/slices/modalSlice.ts +++ b/packages/wallet-ui/src/slices/modalSlice.ts @@ -34,6 +34,7 @@ export const modalSlice = createSlice({ }, }); -export const { setInfoModalVisible, setMinVersionModalVisible, setUpgradeModalVisible, setUpgradeModalDeployText } = modalSlice.actions; +export const { setInfoModalVisible, setMinVersionModalVisible, setUpgradeModalVisible, setUpgradeModalDeployText } = + modalSlice.actions; export default modalSlice.reducer; From 2e3f7d8e73a592c658dbb689655671a675c5db13 Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Fri, 5 Jul 2024 13:28:59 +0200 Subject: [PATCH 04/16] feat: deploy / upgrade workflow separated --- packages/starknet-snap/src/createAccount.ts | 2 - packages/starknet-snap/src/index.ts | 5 + packages/starknet-snap/src/types/snapApi.ts | 1 - .../starknet-snap/src/upgradeAccContract.ts | 106 ++++----------- .../starknet-snap/src/utils/starknetUtils.ts | 45 ++++--- packages/wallet-ui/src/App.tsx | 9 +- .../DeployModal/DeployModal.stories.tsx | 23 ++++ .../organism/DeployModal/DeployModal.style.ts | 64 +++++++++ .../organism/DeployModal/DeployModal.view.tsx | 122 ++++++++++++++++++ .../ui/organism/DeployModal/index.ts | 1 + .../UpgradeModel/UpgradeModel.stories.tsx | 4 +- .../UpgradeModel/UpgradeModel.view.tsx | 7 +- .../wallet-ui/src/services/useStarkNetSnap.ts | 41 +++++- packages/wallet-ui/src/slices/modalSlice.ts | 10 +- 14 files changed, 314 insertions(+), 126 deletions(-) create mode 100644 packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.stories.tsx create mode 100644 packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.style.ts create mode 100644 packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.view.tsx create mode 100644 packages/wallet-ui/src/components/ui/organism/DeployModal/index.ts diff --git a/packages/starknet-snap/src/createAccount.ts b/packages/starknet-snap/src/createAccount.ts index 652961ef..185f1604 100644 --- a/packages/starknet-snap/src/createAccount.ts +++ b/packages/starknet-snap/src/createAccount.ts @@ -36,8 +36,6 @@ export async function createAccount( try { const { state, wallet, saveMutex, keyDeriver, requestParams } = params; const requestParamsObj = requestParams as CreateAccountRequestParams; - console.log('requestParamsObj.addressIndex'); - console.log(requestParamsObj.addressIndex); const addressIndex = getValidNumber(requestParamsObj.addressIndex, -1, 0); const network = getNetworkFromChainId(state, requestParamsObj.chainId); const deploy = !!requestParamsObj.deploy; diff --git a/packages/starknet-snap/src/index.ts b/packages/starknet-snap/src/index.ts index b6a55714..f1d893cb 100644 --- a/packages/starknet-snap/src/index.ts +++ b/packages/starknet-snap/src/index.ts @@ -19,6 +19,7 @@ import { addNetwork } from './addNetwork'; import { switchNetwork } from './switchNetwork'; import { getCurrentNetwork } from './getCurrentNetwork'; import { + CAIRO_VERSION_LEGACY, PRELOADED_TOKENS, STARKNET_INTEGRATION_NETWORK, STARKNET_MAINNET_NETWORK, @@ -124,6 +125,10 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ origin, request }) => apiParams.keyDeriver = await getAddressKeyDeriver(snap); return createAccount(apiParams); + case 'starkNet_createAccountLegacy': + apiParams.keyDeriver = await getAddressKeyDeriver(snap); + return createAccount(apiParams, false, true, CAIRO_VERSION_LEGACY); + case 'starkNet_getStoredUserAccounts': return await getStoredUserAccounts(apiParams); diff --git a/packages/starknet-snap/src/types/snapApi.ts b/packages/starknet-snap/src/types/snapApi.ts index ab08158f..027f305e 100644 --- a/packages/starknet-snap/src/types/snapApi.ts +++ b/packages/starknet-snap/src/types/snapApi.ts @@ -105,7 +105,6 @@ export interface SendTransactionRequestParams extends BaseRequestParams { export interface UpgradeTransactionRequestParams extends BaseRequestParams { contractAddress: string; maxFee?: string; - forceDeploy?: boolean; } export interface GetValueRequestParams extends BaseRequestParams { diff --git a/packages/starknet-snap/src/upgradeAccContract.ts b/packages/starknet-snap/src/upgradeAccContract.ts index 2a5443a6..2272a820 100644 --- a/packages/starknet-snap/src/upgradeAccContract.ts +++ b/packages/starknet-snap/src/upgradeAccContract.ts @@ -1,5 +1,5 @@ import { toJson } from './utils/serializer'; -import { num, constants, CallData, EstimateFee, TransactionType, Invocations } from 'starknet'; +import { num, constants, CallData } from 'starknet'; import { Transaction, TransactionStatus, VoyagerTransactionType } from './types/snapState'; import { getKeysFromAddress, @@ -7,19 +7,13 @@ import { isUpgradeRequired, executeTxn, isAccountDeployed, - getUpgradeTxnInvocation, - estimateAccountUpgradeFee, - getAccContractAddressAndCallDataLegacy, - estimateAccountDeployFee, - estimateFeeBulk, - addFeesFromAllTransactions, + estimateFee, } from './utils/starknetUtils'; import { getNetworkFromChainId, upsertTransaction, getSendTxnText } from './utils/snapUtils'; import { ApiParams, UpgradeTransactionRequestParams } from './types/snapApi'; -import { ACCOUNT_CLASS_HASH, CAIRO_VERSION_LEGACY, PROXY_CONTRACT_HASH } from './utils/constants'; +import { ACCOUNT_CLASS_HASH, CAIRO_VERSION_LEGACY } from './utils/constants'; import { heading, panel, DialogType } from '@metamask/snaps-sdk'; import { logger } from './utils/logger'; -import { createAccount } from './createAccount'; export async function upgradeAccContract(params: ApiParams) { try { @@ -27,7 +21,7 @@ export async function upgradeAccContract(params: ApiParams) { const requestParamsObj = requestParams as UpgradeTransactionRequestParams; const contractAddress = requestParamsObj.contractAddress; const chainId = requestParamsObj.chainId; - const forceDeploy = requestParamsObj.forceDeploy; + if (!contractAddress) { throw new Error(`The given contract address need to be non-empty string, got: ${toJson(requestParamsObj)}`); } @@ -39,78 +33,15 @@ export async function upgradeAccContract(params: ApiParams) { const network = getNetworkFromChainId(state, chainId); - if (!forceDeploy) { - if (!(await isAccountDeployed(network, contractAddress))) { - throw new Error('Contract is not deployed and address has no balance'); - } - - if (!(await isUpgradeRequired(network, contractAddress))) { - throw new Error('Upgrade is not required'); - } + if (!(await isAccountDeployed(network, contractAddress))) { + throw new Error('Contract has not deployed'); } - const { privateKey, addressIndex, publicKey } = await getKeysFromAddress(keyDeriver, network, state, contractAddress); - - const upgradeAccountpayload = getUpgradeTxnInvocation(contractAddress) - const bulkTransactions: Invocations =[]; - // [TODO] This fails. - // const bulkTransactions: Invocations =[ - // { - // type: TransactionType.INVOKE, - // payload: upgradeAccountpayload, - // }]; - - const accountDeployed = await isAccountDeployed(network, contractAddress); - if (forceDeploy) { - if (accountDeployed) { - throw new Error(`Upgrade with Force Deploy cannot be executed on deployed account`); - } - - const { callData } = getAccContractAddressAndCallDataLegacy(publicKey); - const deployAccountpayload = { - classHash: PROXY_CONTRACT_HASH, - contractAddress: contractAddress, - constructorCalldata: callData, - addressSalt: publicKey, - }; - - bulkTransactions.unshift({ - type: TransactionType.DEPLOY_ACCOUNT, - payload: deployAccountpayload, - }); - } - - const fees = await estimateFeeBulk( - network, - contractAddress, - privateKey, - bulkTransactions, - undefined, - ); - const estimateFeeResp = addFeesFromAllTransactions(fees); - - const maxFee = estimateFeeResp.suggestedMaxFee.toString(10); - logger.log(`MaxFee: ${maxFee}`); - - if (forceDeploy) { - //Deploy account before sending the upgrade transaction - logger.log('upgradeAccContract:\nFirst transaction : send deploy transaction'); - const createAccountApiParams = { - state, - wallet: params.wallet, - saveMutex: params.saveMutex, - keyDeriver, - requestParams: { - addressIndex, - deploy: true, - chainId: requestParamsObj.chainId, - }, - }; - await createAccount(createAccountApiParams, false, true, CAIRO_VERSION_LEGACY); + if (!(await isUpgradeRequired(network, contractAddress))) { + throw new Error('Upgrade is not required'); } - //In case we forceDeployed we assign a nonce of 1 to make sure it does after the deploy transaction - const nonce = forceDeploy ? 1 : undefined; + const { privateKey } = await getKeysFromAddress(keyDeriver, network, state, contractAddress); const method = 'upgrade'; @@ -119,8 +50,20 @@ export async function upgradeAccContract(params: ApiParams) { calldata: [0], }); - const txnInvocation = getUpgradeTxnInvocation(contractAddress); + const txnInvocation = { + contractAddress, + entrypoint: method, + calldata, + }; + + let maxFee = requestParamsObj.maxFee ? num.toBigInt(requestParamsObj.maxFee) : constants.ZERO; + if (maxFee === constants.ZERO) { + const estFeeResp = await estimateFee(network, contractAddress, privateKey, txnInvocation, CAIRO_VERSION_LEGACY); + maxFee = num.toBigInt(estFeeResp.suggestedMaxFee.toString(10) ?? '0'); + } + const dialogComponents = getSendTxnText(state, contractAddress, method, calldata, contractAddress, maxFee, network); + const response = await wallet.request({ method: 'snap_dialog', params: { @@ -131,7 +74,7 @@ export async function upgradeAccContract(params: ApiParams) { if (!response) return false; - logger.log(`upgradeAccContract:\ntxnInvocation: ${toJson(txnInvocation)}\nmaxFee: ${maxFee.toString()}}`); + logger.log(`sendTransaction:\ntxnInvocation: ${toJson(txnInvocation)}\nmaxFee: ${maxFee.toString()}}`); const txnResp = await executeTxn( network, @@ -141,12 +84,11 @@ export async function upgradeAccContract(params: ApiParams) { undefined, { maxFee, - nonce, }, CAIRO_VERSION_LEGACY, ); - logger.log(`upgradeAccContract:\ntxnResp: ${toJson(txnResp)}`); + logger.log(`sendTransaction:\ntxnResp: ${toJson(txnResp)}`); if (!txnResp?.transaction_hash) { throw new Error(`Transaction hash is not found`); diff --git a/packages/starknet-snap/src/utils/starknetUtils.ts b/packages/starknet-snap/src/utils/starknetUtils.ts index 6db94d8b..5adc7371 100644 --- a/packages/starknet-snap/src/utils/starknetUtils.ts +++ b/packages/starknet-snap/src/utils/starknetUtils.ts @@ -807,28 +807,33 @@ export const getCorrectContractAddress = async ( if (e.message.includes('network error for getSigner')) { throw e; } - // Edge case detection - logger.log(`getContractAddressByKey: no deployed contract found, checking balance for edge cases`); - console.log(getEtherErc20Token(state, network.chainId)?.address); - try { - const balance = num.toBigInt( - (await getBalance(contractAddressLegacy, getEtherErc20Token(state, network.chainId)?.address, network)) ?? - num.toBigInt(constants.ZERO), - ); - console.log(balance); - if (balance > maxFee) { - upgradeRequired = true; - deployRequired = true; - address = contractAddressLegacy; - logger.log( - `getContractAddressByKey: no deployed cairo0 contract found with non-zero balance, force cairo ${CAIRO_VERSION_LEGACY}`, + const accountDeployed = await isAccountDeployed(network, address); + if (accountDeployed) { + address = contractAddressLegacy; + upgradeRequired = true; + deployRequired = false; + } else { + // Edge case detection + logger.log(`getContractAddressByKey: no deployed contract found, checking balance for edge cases`); + try { + const balance = num.toBigInt( + (await getBalance(contractAddressLegacy, getEtherErc20Token(state, network.chainId)?.address, network)) ?? + num.toBigInt(constants.ZERO), ); - } else { - logger.log(`getContractAddressByKey: no deployed contract found, fallback to cairo ${CAIRO_VERSION}`); + if (balance > maxFee) { + upgradeRequired = true; + deployRequired = true; + address = contractAddressLegacy; + logger.log( + `getContractAddressByKey: no deployed cairo0 contract found with non-zero balance, force cairo ${CAIRO_VERSION_LEGACY}`, + ); + } else { + logger.log(`getContractAddressByKey: no deployed contract found, fallback to cairo ${CAIRO_VERSION}`); + } + } catch (err) { + logger.log(`getContractAddressByKey: balance check failed with error ${err}`); + throw err; } - } catch (err) { - logger.log(`getContractAddressByKey: balance check failed with error ${err}`); - throw err; } } } diff --git a/packages/wallet-ui/src/App.tsx b/packages/wallet-ui/src/App.tsx index 7a640082..338d071e 100644 --- a/packages/wallet-ui/src/App.tsx +++ b/packages/wallet-ui/src/App.tsx @@ -20,13 +20,14 @@ import { NoMetamaskModal } from 'components/ui/organism/NoMetamaskModal'; import { MinVersionModal } from './components/ui/organism/MinVersionModal'; import { useHasMetamask } from 'hooks/useHasMetamask'; import { DUMMY_ADDRESS } from 'utils/constants'; +import { DeployModal } from 'components/ui/organism/DeployModal'; library.add(fas, far); function App() { const { initSnap, getWalletData, checkConnection } = useStarkNetSnap(); const { connected, forceReconnect, provider } = useAppSelector((state) => state.wallet); - const { infoModalVisible, minVersionModalVisible, upgradeModalVisible, upgradeModalDeployText } = useAppSelector( + const { infoModalVisible, minVersionModalVisible, upgradeModalVisible, deployModalVisible } = useAppSelector( (state) => state.modals, ); const { loader } = useAppSelector((state) => state.UI); @@ -58,7 +59,6 @@ function App() { }, [networks.activeNetwork, provider]); const loading = loader.isLoading; - return ( @@ -76,7 +76,10 @@ function App() { - + + + + {loading && {loader.loadingMessage}} diff --git a/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.stories.tsx b/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.stories.tsx new file mode 100644 index 00000000..c9cb4b88 --- /dev/null +++ b/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.stories.tsx @@ -0,0 +1,23 @@ +import { Meta } from '@storybook/react'; +import { useState } from 'react'; +import { PopIn } from 'components/ui/molecule/PopIn'; +import { DeployModalView } from './DeployModal.view'; + +export default { + title: 'Organism/DeployModal', + component: DeployModalView, +} as Meta; + +export const ContentOnly = () => ; + +export const WithModal = () => { + let [isOpen, setIsOpen] = useState(false); + return ( + <> + + + + + + ); +}; diff --git a/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.style.ts b/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.style.ts new file mode 100644 index 00000000..585e5c0b --- /dev/null +++ b/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.style.ts @@ -0,0 +1,64 @@ +import styled from 'styled-components'; +import starknetSrc from 'assets/images/starknet-logo.svg'; +import { Button } from 'components/ui/atom/Button'; + +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + background-color: ${(props) => props.theme.palette.grey.white}; + width: ${(props) => props.theme.modal.base}; + padding: ${(props) => props.theme.spacing.base}; + padding-top: 40px; + border-radius: 8px; + align-items: center; +`; + +export const StarknetLogo = styled.img.attrs(() => ({ + src: starknetSrc, +}))` + width: 158px; + height: 32px; + margin-bottom: 32px; +`; + +export const Title = styled.div` + text-align: center; + font-weight: ${(props) => props.theme.typography.h3.fontWeight}; + font-size: ${(props) => props.theme.typography.h3.fontSize}; + font-family: ${(props) => props.theme.typography.h3.fontFamily}; + line-height: ${(props) => props.theme.typography.h3.lineHeight}; + margin-bottom: 8px; +`; + +export const Description = styled.div` + font-size: ${(props) => props.theme.typography.p2.fontSize}; + color: ${(props) => props.theme.palette.grey.grey1}; +`; + +export const DescriptionCentered = styled(Description)` + text-align: center; + width: 264px; +`; + +export const Txnlink = styled.div` + margin-top: 12px; + font-size: ${(props) => props.theme.typography.p2.fontSize}; + color: ${(props) => props.theme.palette.primary.main}; + font-weight: ${(props) => props.theme.typography.bold.fontWeight}; + font-family: ${(props) => props.theme.typography.bold.fontFamily}; + text-decoration: underline; + cursor: pointer; +`; + +export const DeployButton = styled(Button).attrs((props) => ({ + textStyle: { + fontSize: props.theme.typography.p1.fontSize, + fontWeight: 900, + }, + upperCaseOnly: false, + backgroundTransparent: true, +}))` + box-shadow: 0px 14px 24px -6px rgba(106, 115, 125, 0.2); + padding-top: 16px; + padding-bottom: 16px; +`; diff --git a/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.view.tsx b/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.view.tsx new file mode 100644 index 00000000..5ab7de2d --- /dev/null +++ b/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.view.tsx @@ -0,0 +1,122 @@ +import { useEffect, useState } from 'react'; +import { useStarkNetSnap } from 'services'; +import { useAppSelector, useAppDispatch } from 'hooks/redux'; +import Toastr from 'toastr2'; + +import { setDeployModalVisible } from 'slices/modalSlice'; +import { openExplorerTab, shortenAddress } from '../../../../utils/utils'; +import { DeployButton, StarknetLogo, Title, Wrapper, DescriptionCentered, Txnlink } from './DeployModal.style'; + +interface Props { + address: string; +} + +enum Stage { + INIT = 0, + WAITING_FOR_TXN = 1, + SUCCESS = 2, + FAIL = 3, +} + +export const DeployModalView = ({ address }: Props) => { + const dispatch = useAppDispatch(); + const { deployAccount, waitForAccountUpdate } = useStarkNetSnap(); + const [txnHash, setTxnHash] = useState(''); + const [stage, setStage] = useState(Stage.INIT); + const networks = useAppSelector((state) => state.networks); + const chainId = networks?.items[networks.activeNetwork]?.chainId; + const toastr = new Toastr(); + + const onDeploy = async () => { + try { + const resp = await deployAccount(address, '0', chainId); + + if (resp === false) { + return; + } + + if (resp.transaction_hash) { + setTxnHash(resp.transaction_hash); + } else { + throw new Error('no transaction hash'); + } + } catch (err) { + //eslint-disable-next-line no-console + console.error(err); + toastr.error(`Deploy account failed`); + } + }; + + useEffect(() => { + if (txnHash) { + setStage(Stage.WAITING_FOR_TXN); + waitForAccountUpdate(txnHash, address, chainId) + .then((resp) => { + setStage(resp === true ? Stage.SUCCESS : Stage.FAIL); + }) + .catch(() => { + setStage(Stage.FAIL); + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [txnHash, address, chainId]); + + useEffect(() => { + if (stage === Stage.SUCCESS) { + toastr.success(`Account deployd successfully`); + dispatch(setDeployModalVisible(false)); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [stage, dispatch]); + + const deployTxt = 'Deploy'; + + const renderComponent = () => { + switch (stage) { + case Stage.INIT: + return ( + <> + + You have a non-zero balance on an non-deployed address +
+
+ A deployment of your address is necessary to proceed with the Snap. +
+
+ Click on the "{deployTxt}" button to proceed. +
+ Thank you! +
+ {deployTxt} + + ); + case Stage.WAITING_FOR_TXN: + return Waiting for transaction to be complete.; + case Stage.SUCCESS: + return Account deployd successfully.; + default: + return ( + + Transaction Hash:
{' '} + openExplorerTab(txnHash, 'tx', chainId)}>{shortenAddress(txnHash)} +
+ Unfortunately, you reached the maximum number of deploy tentatives allowed. +
+
+ Please try again in a couple of hours. +
+
+ Thank you for your comprehension. +
+ ); + } + }; + + return ( + + + Deploy Account + {renderComponent()} + + ); +}; diff --git a/packages/wallet-ui/src/components/ui/organism/DeployModal/index.ts b/packages/wallet-ui/src/components/ui/organism/DeployModal/index.ts new file mode 100644 index 00000000..e0c02921 --- /dev/null +++ b/packages/wallet-ui/src/components/ui/organism/DeployModal/index.ts @@ -0,0 +1 @@ +export { DeployModalView as DeployModal } from './DeployModal.view'; diff --git a/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.stories.tsx b/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.stories.tsx index c5b840ce..4e3f0acb 100644 --- a/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.stories.tsx +++ b/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.stories.tsx @@ -8,7 +8,7 @@ export default { component: UpgradeModelView, } as Meta; -export const ContentOnly = () => ; +export const ContentOnly = () => ; export const WithModal = () => { let [isOpen, setIsOpen] = useState(false); @@ -16,7 +16,7 @@ export const WithModal = () => { <> - + ); diff --git a/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx b/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx index a43e3f8d..60c6653b 100644 --- a/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx +++ b/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx @@ -9,7 +9,6 @@ import { UpgradeButton, StarknetLogo, Title, Wrapper, DescriptionCentered, Txnli interface Props { address: string; - deploy: boolean; // whether to deploy before upgrade } enum Stage { @@ -19,7 +18,7 @@ enum Stage { FAIL = 3, } -export const UpgradeModelView = ({ address, deploy }: Props) => { +export const UpgradeModelView = ({ address }: Props) => { const dispatch = useAppDispatch(); const { upgradeAccount, waitForAccountUpdate } = useStarkNetSnap(); const [txnHash, setTxnHash] = useState(''); @@ -30,7 +29,7 @@ export const UpgradeModelView = ({ address, deploy }: Props) => { const onUpgrade = async () => { try { - const resp = await upgradeAccount(address, '0', chainId, deploy); + const resp = await upgradeAccount(address, '0', chainId); if (resp === false) { return; @@ -70,7 +69,7 @@ export const UpgradeModelView = ({ address, deploy }: Props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [stage, dispatch]); - const upgradeTxt = deploy ? 'Deploy & Upgrade' : 'Upgrade'; + const upgradeTxt = 'Upgrade'; const renderComponent = () => { switch (stage) { diff --git a/packages/wallet-ui/src/services/useStarkNetSnap.ts b/packages/wallet-ui/src/services/useStarkNetSnap.ts index fe6fb565..a0500fae 100644 --- a/packages/wallet-ui/src/services/useStarkNetSnap.ts +++ b/packages/wallet-ui/src/services/useStarkNetSnap.ts @@ -2,7 +2,7 @@ import { setInfoModalVisible, setMinVersionModalVisible, setUpgradeModalVisible, - setUpgradeModalDeployText, + setDeployModalVisible, } from 'slices/modalSlice'; import { setNetworks } from 'slices/networkSlice'; import { useAppDispatch, useAppSelector } from 'hooks/redux'; @@ -241,8 +241,6 @@ export const useStarkNetSnap = () => { let acc: Account[] | Account = await recoverAccounts(chainId); let upgradeRequired = false; let deployRequired = false; - console.log('accounts'); - console.log(acc); deployRequired = (Array.isArray(acc) ? acc[0].deployRequired : (acc as Account).deployRequired) ?? false; if (!acc || acc.length === 0 || (!acc[0].publicKey && !deployRequired)) { acc = await addAccount(chainId); @@ -277,8 +275,8 @@ export const useStarkNetSnap = () => { if (!Array.isArray(acc)) { dispatch(setInfoModalVisible(true)); } - dispatch(setUpgradeModalVisible(upgradeRequired)); - dispatch(setUpgradeModalDeployText(deployRequired)); + dispatch(setUpgradeModalVisible(upgradeRequired && !deployRequired)); + dispatch(setDeployModalVisible(deployRequired)); dispatch(disableLoading()); }; @@ -424,7 +422,36 @@ export const useStarkNetSnap = () => { } }; - const upgradeAccount = async (contractAddress: string, maxFee: string, chainId: string, forceDeploy: boolean) => { + const deployAccount = async (contractAddress: string, maxFee: string, chainId: string) => { + dispatch(enableLoadingWithMessage('Deploying account...')); + try { + const response = await provider.request({ + method: 'wallet_invokeSnap', + params: { + snapId, + request: { + method: 'starkNet_createAccountLegacy', + params: { + ...defaultParam, + contractAddress, + maxFee, + chainId, + deploy: true, + }, + }, + }, + }); + dispatch(disableLoading()); + return response; + } catch (err) { + dispatch(disableLoading()); + //eslint-disable-next-line no-console + console.error(err); + throw err; + } + }; + + const upgradeAccount = async (contractAddress: string, maxFee: string, chainId: string) => { dispatch(enableLoadingWithMessage('Upgrading account...')); try { const response = await provider.request({ @@ -438,7 +465,6 @@ export const useStarkNetSnap = () => { contractAddress, maxFee, chainId, - forceDeploy, }, }, }, @@ -772,6 +798,7 @@ export const useStarkNetSnap = () => { estimateFees, sendTransaction, upgradeAccount, + deployAccount, getTransactions, getTransactionStatus, recoverAccounts, diff --git a/packages/wallet-ui/src/slices/modalSlice.ts b/packages/wallet-ui/src/slices/modalSlice.ts index 56bf975a..e6fa61e6 100644 --- a/packages/wallet-ui/src/slices/modalSlice.ts +++ b/packages/wallet-ui/src/slices/modalSlice.ts @@ -4,14 +4,14 @@ export interface modalState { infoModalVisible: boolean; minVersionModalVisible: boolean; upgradeModalVisible: boolean; - upgradeModalDeployText: boolean; + deployModalVisible: boolean; } const initialState: modalState = { infoModalVisible: false, minVersionModalVisible: false, upgradeModalVisible: false, - upgradeModalDeployText: false, + deployModalVisible: false, }; export const modalSlice = createSlice({ @@ -25,8 +25,8 @@ export const modalSlice = createSlice({ setUpgradeModalVisible: (state, { payload }) => { state.upgradeModalVisible = payload; }, - setUpgradeModalDeployText: (state, { payload }) => { - state.upgradeModalDeployText = payload; + setDeployModalVisible: (state, { payload }) => { + state.deployModalVisible = payload; }, setMinVersionModalVisible: (state, { payload }) => { state.minVersionModalVisible = payload; @@ -34,7 +34,7 @@ export const modalSlice = createSlice({ }, }); -export const { setInfoModalVisible, setMinVersionModalVisible, setUpgradeModalVisible, setUpgradeModalDeployText } = +export const { setInfoModalVisible, setMinVersionModalVisible, setUpgradeModalVisible, setDeployModalVisible } = modalSlice.actions; export default modalSlice.reducer; From 37495dca16f135f4b176db927119ed9687b8b3fc Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Fri, 5 Jul 2024 13:49:25 +0200 Subject: [PATCH 05/16] test: rollback upgradeAccContract testing --- packages/starknet-snap/test/src/upgradeAccContract.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/starknet-snap/test/src/upgradeAccContract.test.ts b/packages/starknet-snap/test/src/upgradeAccContract.test.ts index 7c427f7c..bd3ed17a 100644 --- a/packages/starknet-snap/test/src/upgradeAccContract.test.ts +++ b/packages/starknet-snap/test/src/upgradeAccContract.test.ts @@ -94,7 +94,7 @@ describe('Test function: upgradeAccContract', function () { result = err; } finally { expect(result).to.be.an('Error'); - expect(result.message).to.be.include('Contract is not deployed and address has no balance'); + expect(result.message).to.be.include('Contract has not deployed'); } }); @@ -161,7 +161,6 @@ describe('Test function: upgradeAccContract', function () { undefined, { maxFee: num.toBigInt(10000), - nonce: undefined, }, CAIRO_VERSION_LEGACY, ); @@ -196,7 +195,6 @@ describe('Test function: upgradeAccContract', function () { undefined, { maxFee: num.toBigInt(estimateFeeResp.suggestedMaxFee), - nonce: undefined, }, CAIRO_VERSION_LEGACY, ); From 30667a9fda3a3128e51029dc1acbd3cb33251cb2 Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Mon, 8 Jul 2024 16:15:59 +0200 Subject: [PATCH 06/16] feat: finalized wallet-ui flow --- packages/starknet-snap/src/createAccount.ts | 35 ++++++++++++------- packages/starknet-snap/src/recoverAccounts.ts | 3 -- packages/starknet-snap/src/utils/snapUtils.ts | 15 ++++++++ .../starknet-snap/src/utils/starknetUtils.ts | 2 +- .../test/src/createAccount.test.ts | 3 ++ .../organism/DeployModal/DeployModal.view.tsx | 6 ++-- .../wallet-ui/src/services/useStarkNetSnap.ts | 18 ++++++++++ 7 files changed, 62 insertions(+), 20 deletions(-) diff --git a/packages/starknet-snap/src/createAccount.ts b/packages/starknet-snap/src/createAccount.ts index 185f1604..870b971d 100644 --- a/packages/starknet-snap/src/createAccount.ts +++ b/packages/starknet-snap/src/createAccount.ts @@ -5,20 +5,21 @@ import { deployAccount, waitForTransaction, getAccContractAddressAndCallDataLegacy, + estimateAccountDeployFee, } from './utils/starknetUtils'; import { getNetworkFromChainId, getValidNumber, upsertAccount, upsertTransaction, - addDialogTxt, + getSendTxnText, } from './utils/snapUtils'; import { AccContract, VoyagerTransactionType, Transaction, TransactionStatus } from './types/snapState'; import { ApiParams, CreateAccountRequestParams } from './types/snapApi'; -import { heading, panel, text, DialogType } from '@metamask/snaps-sdk'; +import { heading, panel, DialogType } from '@metamask/snaps-sdk'; import { logger } from './utils/logger'; -import { CAIRO_VERSION, CAIRO_VERSION_LEGACY } from './utils/constants'; -import { CairoContract, CairoVersion } from 'starknet'; +import { ACCOUNT_CLASS_HASH_LEGACY, CAIRO_VERSION, CAIRO_VERSION_LEGACY } from './utils/constants'; +import { CairoVersion, EstimateFee, num } from 'starknet'; /** * Create an starknet account. @@ -57,22 +58,30 @@ export async function createAccount( if (deploy) { if (!silentMode) { - const components = []; - addDialogTxt(components, 'Address', contractAddress); - addDialogTxt(components, 'Public Key', publicKey); - addDialogTxt(components, 'Address Index', addressIndex.toString()); + + logger.log( + `estimateAccountDeployFee:\ncontractAddress = ${contractAddress}\npublicKey = ${publicKey}\naddressIndex = ${addressIndexInUsed}`, + ); + + const estimateDeployFee: EstimateFee = await estimateAccountDeployFee( + network, + contractAddress, + contractCallData, + publicKey, + privateKey, + ); + logger.log(`estimateAccountDeployFee:\nestimateDeployFee: ${toJson(estimateDeployFee)}`); + let maxFee = num.toBigInt(estimateDeployFee.suggestedMaxFee.toString(10) ?? '0'); + const dialogComponents = getSendTxnText(state, ACCOUNT_CLASS_HASH_LEGACY, "deploy", contractCallData, contractAddress, maxFee, network); const response = await wallet.request({ method: 'snap_dialog', params: { type: DialogType.Confirmation, - content: panel([ - heading('Do you want to sign this deploy account transaction ?'), - text(`It will be signed with address: ${contractAddress}`), - ...components, - ]), + content: panel([heading('Do you want to sign this deploy transaction ?'), ...dialogComponents]), }, }); + if (!response) return { address: contractAddress, diff --git a/packages/starknet-snap/src/recoverAccounts.ts b/packages/starknet-snap/src/recoverAccounts.ts index 5c8e6edc..43571edf 100644 --- a/packages/starknet-snap/src/recoverAccounts.ts +++ b/packages/starknet-snap/src/recoverAccounts.ts @@ -3,8 +3,6 @@ import { num } from 'starknet'; import { getKeysFromAddressIndex, getCorrectContractAddress, - estimateAccountDeployFee, - estimateAccountUpgradeFee, } from './utils/starknetUtils'; import { getNetworkFromChainId, getValidNumber, upsertAccount } from './utils/snapUtils'; import { AccContract } from './types/snapState'; @@ -34,7 +32,6 @@ export async function recoverAccounts(params: ApiParams) { state, i, ); - // [TODO] : add fee here to getCorrectContractAddress const { address: contractAddress, signerPubKey: signerPublicKey, diff --git a/packages/starknet-snap/src/utils/snapUtils.ts b/packages/starknet-snap/src/utils/snapUtils.ts index ffe4f27a..52399222 100644 --- a/packages/starknet-snap/src/utils/snapUtils.ts +++ b/packages/starknet-snap/src/utils/snapUtils.ts @@ -772,3 +772,18 @@ export async function showUpgradeRequestModal(wallet) { }, }); } + +export async function showDeployRequestModal(wallet) { + await wallet.request({ + method: 'snap_dialog', + params: { + type: DialogType.Alert, + content: panel([ + heading('Account Deployment Mandatory!'), + text( + `Visit the [companion dapp for Starknet](${dappUrl(process.env.SNAP_ENV)}) to deploy pour account.\nThank you!`, + ), + ]), + }, + }); +} diff --git a/packages/starknet-snap/src/utils/starknetUtils.ts b/packages/starknet-snap/src/utils/starknetUtils.ts index 5adc7371..10ae0172 100644 --- a/packages/starknet-snap/src/utils/starknetUtils.ts +++ b/packages/starknet-snap/src/utils/starknetUtils.ts @@ -825,7 +825,7 @@ export const getCorrectContractAddress = async ( deployRequired = true; address = contractAddressLegacy; logger.log( - `getContractAddressByKey: no deployed cairo0 contract found with non-zero balance, force cairo ${CAIRO_VERSION_LEGACY}`, + `getContractAddressByKey: non deployed cairo0 contract found with non-zero balance, force cairo ${CAIRO_VERSION_LEGACY}`, ); } else { logger.log(`getContractAddressByKey: no deployed contract found, fallback to cairo ${CAIRO_VERSION}`); diff --git a/packages/starknet-snap/test/src/createAccount.test.ts b/packages/starknet-snap/test/src/createAccount.test.ts index c689b4a2..7b2170c5 100644 --- a/packages/starknet-snap/test/src/createAccount.test.ts +++ b/packages/starknet-snap/test/src/createAccount.test.ts @@ -53,6 +53,9 @@ describe('Test function: createAccount', function () { walletStub.rpcStubs.snap_manageState.resolves(state); waitForTransactionStub = sandbox.stub(utils, 'waitForTransaction'); waitForTransactionStub.resolves({} as unknown as GetTransactionReceiptResponse); + sandbox.stub(utils, 'estimateAccountDeployFee').callsFake(async () => { + return estimateDeployFeeResp; + }); }); afterEach(function () { diff --git a/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.view.tsx b/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.view.tsx index 5ab7de2d..39628390 100644 --- a/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.view.tsx +++ b/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.view.tsx @@ -20,7 +20,7 @@ enum Stage { export const DeployModalView = ({ address }: Props) => { const dispatch = useAppDispatch(); - const { deployAccount, waitForAccountUpdate } = useStarkNetSnap(); + const { deployAccount, waitForAccountCreation } = useStarkNetSnap(); const [txnHash, setTxnHash] = useState(''); const [stage, setStage] = useState(Stage.INIT); const networks = useAppSelector((state) => state.networks); @@ -50,7 +50,7 @@ export const DeployModalView = ({ address }: Props) => { useEffect(() => { if (txnHash) { setStage(Stage.WAITING_FOR_TXN); - waitForAccountUpdate(txnHash, address, chainId) + waitForAccountCreation(txnHash, address, chainId) .then((resp) => { setStage(resp === true ? Stage.SUCCESS : Stage.FAIL); }) @@ -63,7 +63,7 @@ export const DeployModalView = ({ address }: Props) => { useEffect(() => { if (stage === Stage.SUCCESS) { - toastr.success(`Account deployd successfully`); + toastr.success(`Account deployed successfully`); dispatch(setDeployModalVisible(false)); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/packages/wallet-ui/src/services/useStarkNetSnap.ts b/packages/wallet-ui/src/services/useStarkNetSnap.ts index a0500fae..a19e853d 100644 --- a/packages/wallet-ui/src/services/useStarkNetSnap.ts +++ b/packages/wallet-ui/src/services/useStarkNetSnap.ts @@ -680,6 +680,23 @@ export const useStarkNetSnap = () => { return txStatus; }; + const waitForAccountCreation = async (transactionHash: string, accountAddress: string, chainId: string) => { + dispatch(enableLoadingWithMessage('Waiting for transaction to be finalised.')); + try { + // read transaction to check if the txn is ready + await waitForTransaction(transactionHash, chainId); + await recoverAccounts(chainId); + dispatch(disableLoading()); + return true; + } catch (e) { + //eslint-disable-next-line no-console + console.log(`error while wait for transaction: ${e}`); + dispatch(disableLoading()); + return false; + } + + } + const waitForAccountUpdate = async (transactionHash: string, accountAddress: string, chainId: string) => { dispatch(enableLoadingWithMessage('Waiting for transaction to be finalised.')); const toastr = new Toastr(); @@ -804,6 +821,7 @@ export const useStarkNetSnap = () => { recoverAccounts, waitForTransaction, waitForAccountUpdate, + waitForAccountCreation, updateTokenBalance, getTokenBalance, addErc20Token, From b9aa51cf91aaccf4a1748255c0cebe89b0e63431 Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Mon, 8 Jul 2024 16:16:31 +0200 Subject: [PATCH 07/16] feat: check for required deploy in executeTxn --- packages/starknet-snap/src/executeTxn.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/starknet-snap/src/executeTxn.ts b/packages/starknet-snap/src/executeTxn.ts index cb1d97d5..afa3e36f 100644 --- a/packages/starknet-snap/src/executeTxn.ts +++ b/packages/starknet-snap/src/executeTxn.ts @@ -1,5 +1,5 @@ import { Invocations, TransactionType } from 'starknet'; -import { getNetworkFromChainId, getTxnSnapTxt, addDialogTxt, showUpgradeRequestModal } from './utils/snapUtils'; +import { getNetworkFromChainId, getTxnSnapTxt, addDialogTxt, showUpgradeRequestModal, showDeployRequestModal } from './utils/snapUtils'; import { getKeysFromAddress, executeTxn as executeTxnUtil, @@ -7,7 +7,7 @@ import { estimateFeeBulk, getAccContractAddressAndCallData, addFeesFromAllTransactions, - isUpgradeRequired, + getCorrectContractAddress, } from './utils/starknetUtils'; import { ApiParams, ExecuteTxnRequestParams } from './types/snapApi'; import { createAccount } from './createAccount'; @@ -27,7 +27,17 @@ export async function executeTxn(params: ApiParams) { addressIndex, } = await getKeysFromAddress(keyDeriver, network, state, senderAddress); - if (await isUpgradeRequired(network, senderAddress)) { + const { + upgradeRequired, + deployRequired, + } = await getCorrectContractAddress(network, publicKey, state); + + if (upgradeRequired && deployRequired) { // Edge case force cairo0 deploy because non-zero balance + await showDeployRequestModal(wallet); + throw new Error('Deploy required'); + } + + if (upgradeRequired && !deployRequired) { await showUpgradeRequestModal(wallet); throw new Error('Upgrade required'); } From 3b150995acc6439b8d8b2e0b6d08218c0003486c Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Mon, 8 Jul 2024 16:16:43 +0200 Subject: [PATCH 08/16] test: check for required deploy in executeTxn --- .../starknet-snap/test/src/executeTxn.test.ts | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/starknet-snap/test/src/executeTxn.test.ts b/packages/starknet-snap/test/src/executeTxn.test.ts index b01b2afc..ba0449a9 100644 --- a/packages/starknet-snap/test/src/executeTxn.test.ts +++ b/packages/starknet-snap/test/src/executeTxn.test.ts @@ -80,8 +80,8 @@ describe('Test function: executeTxn', function () { apiParams.requestParams = requestObject; }); - it('should 1) throw an error and 2) show upgrade modal if account deployed required', async function () { - const isUpgradeRequiredStub = sandbox.stub(utils, 'isUpgradeRequired').resolves(true); + it('should 1) throw an error and 2) show upgrade modal if account upgrade required', async function () { + const getCorrectContractAddressStub = sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: true, deployRequired: false}); const showUpgradeRequestModalStub = sandbox.stub(snapsUtil, 'showUpgradeRequestModal').resolves(); let result; try { @@ -89,14 +89,29 @@ describe('Test function: executeTxn', function () { } catch (err) { result = err; } finally { - expect(isUpgradeRequiredStub).to.have.been.calledOnceWith(STARKNET_MAINNET_NETWORK, account1.address); + expect(getCorrectContractAddressStub).to.have.been.calledOnce; expect(showUpgradeRequestModalStub).to.have.been.calledOnce; expect(result).to.be.an('Error'); } }); + it('should 1) throw an error and 2) show deploy modal if account deployed required', async function () { + const getCorrectContractAddressStub = sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: true, deployRequired: true}); + const showDeployRequestModalStub = sandbox.stub(snapsUtil, 'showDeployRequestModal').resolves(); + let result; + try { + result = await executeTxn(apiParams); + } catch (err) { + result = err; + } finally { + expect(getCorrectContractAddressStub).to.have.been.calledOnce; + expect(showDeployRequestModalStub).to.have.been.calledOnce; + expect(result).to.be.an('Error'); + } + }); + it('should executeTxn correctly and deploy an account', async function () { - sandbox.stub(utils, 'isUpgradeRequired').resolves(false); + sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: false, deployRequired: false}); sandbox.stub(utils, 'isAccountDeployed').resolves(false); const createAccountStub = sandbox.stub(createAccountUtils, 'createAccount').resolvesThis(); const stub = sandbox.stub(utils, 'executeTxn').resolves({ @@ -128,7 +143,7 @@ describe('Test function: executeTxn', function () { }); it('should executeTxn multiple and deploy an account', async function () { - sandbox.stub(utils, 'isUpgradeRequired').resolves(false); + sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: false, deployRequired: true}); sandbox.stub(utils, 'isAccountDeployed').resolves(false); const createAccountStub = sandbox.stub(createAccountUtils, 'createAccount').resolvesThis(); const stub = sandbox.stub(utils, 'executeTxn').resolves({ @@ -189,7 +204,7 @@ describe('Test function: executeTxn', function () { it('should executeTxn and not deploy an account', async function () { const createAccountStub = sandbox.stub(createAccountUtils, 'createAccount'); - sandbox.stub(utils, 'isUpgradeRequired').resolves(false); + sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: false, deployRequired: false}); sandbox.stub(utils, 'isAccountDeployed').resolves(true); const stub = sandbox.stub(utils, 'executeTxn').resolves({ transaction_hash: 'transaction_hash', @@ -223,7 +238,7 @@ describe('Test function: executeTxn', function () { it('should executeTxn multiple and not deploy an account', async function () { const createAccountStub = sandbox.stub(createAccountUtils, 'createAccount'); - sandbox.stub(utils, 'isUpgradeRequired').resolves(false); + sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: false, deployRequired: false}); sandbox.stub(utils, 'isAccountDeployed').resolves(true); const stub = sandbox.stub(utils, 'executeTxn').resolves({ transaction_hash: 'transaction_hash', @@ -282,7 +297,7 @@ describe('Test function: executeTxn', function () { }); it('should throw error if executeTxn fail', async function () { - sandbox.stub(utils, 'isUpgradeRequired').resolves(false); + sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: false, deployRequired: false}); sandbox.stub(utils, 'isAccountDeployed').resolves(true); const stub = sandbox.stub(utils, 'executeTxn').rejects('error'); const { privateKey } = await utils.getKeysFromAddress( @@ -315,8 +330,8 @@ describe('Test function: executeTxn', function () { }); it('should return false if user rejected to sign the transaction', async function () { + sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: false, deployRequired: false}); sandbox.stub(utils, 'isAccountDeployed').resolves(true); - sandbox.stub(utils, 'isUpgradeRequired').resolves(false); walletStub.rpcStubs.snap_dialog.resolves(false); const stub = sandbox.stub(utils, 'executeTxn').resolves({ transaction_hash: 'transaction_hash', From af8b74061ef715e8c420600d91a209b74e005040 Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Mon, 8 Jul 2024 16:17:35 +0200 Subject: [PATCH 09/16] chore: lint + prettier --- packages/starknet-snap/src/createAccount.ts | 15 ++++++--- packages/starknet-snap/src/executeTxn.ts | 16 ++++++---- packages/starknet-snap/src/recoverAccounts.ts | 5 +-- packages/starknet-snap/src/utils/snapUtils.ts | 4 ++- .../starknet-snap/src/utils/starknetUtils.ts | 4 +-- .../test/src/createAccount.test.ts | 2 -- .../starknet-snap/test/src/executeTxn.test.ts | 32 ++++++++++++++----- .../wallet-ui/src/services/useStarkNetSnap.ts | 3 +- 8 files changed, 51 insertions(+), 30 deletions(-) diff --git a/packages/starknet-snap/src/createAccount.ts b/packages/starknet-snap/src/createAccount.ts index 870b971d..9a6e18d8 100644 --- a/packages/starknet-snap/src/createAccount.ts +++ b/packages/starknet-snap/src/createAccount.ts @@ -58,11 +58,10 @@ export async function createAccount( if (deploy) { if (!silentMode) { - logger.log( `estimateAccountDeployFee:\ncontractAddress = ${contractAddress}\npublicKey = ${publicKey}\naddressIndex = ${addressIndexInUsed}`, ); - + const estimateDeployFee: EstimateFee = await estimateAccountDeployFee( network, contractAddress, @@ -71,8 +70,16 @@ export async function createAccount( privateKey, ); logger.log(`estimateAccountDeployFee:\nestimateDeployFee: ${toJson(estimateDeployFee)}`); - let maxFee = num.toBigInt(estimateDeployFee.suggestedMaxFee.toString(10) ?? '0'); - const dialogComponents = getSendTxnText(state, ACCOUNT_CLASS_HASH_LEGACY, "deploy", contractCallData, contractAddress, maxFee, network); + const maxFee = num.toBigInt(estimateDeployFee.suggestedMaxFee.toString(10) ?? '0'); + const dialogComponents = getSendTxnText( + state, + ACCOUNT_CLASS_HASH_LEGACY, + 'deploy', + contractCallData, + contractAddress, + maxFee, + network, + ); const response = await wallet.request({ method: 'snap_dialog', diff --git a/packages/starknet-snap/src/executeTxn.ts b/packages/starknet-snap/src/executeTxn.ts index afa3e36f..8489a950 100644 --- a/packages/starknet-snap/src/executeTxn.ts +++ b/packages/starknet-snap/src/executeTxn.ts @@ -1,5 +1,11 @@ import { Invocations, TransactionType } from 'starknet'; -import { getNetworkFromChainId, getTxnSnapTxt, addDialogTxt, showUpgradeRequestModal, showDeployRequestModal } from './utils/snapUtils'; +import { + getNetworkFromChainId, + getTxnSnapTxt, + addDialogTxt, + showUpgradeRequestModal, + showDeployRequestModal, +} from './utils/snapUtils'; import { getKeysFromAddress, executeTxn as executeTxnUtil, @@ -27,12 +33,10 @@ export async function executeTxn(params: ApiParams) { addressIndex, } = await getKeysFromAddress(keyDeriver, network, state, senderAddress); - const { - upgradeRequired, - deployRequired, - } = await getCorrectContractAddress(network, publicKey, state); + const { upgradeRequired, deployRequired } = await getCorrectContractAddress(network, publicKey, state); - if (upgradeRequired && deployRequired) { // Edge case force cairo0 deploy because non-zero balance + if (upgradeRequired && deployRequired) { + // Edge case force cairo0 deploy because non-zero balance await showDeployRequestModal(wallet); throw new Error('Deploy required'); } diff --git a/packages/starknet-snap/src/recoverAccounts.ts b/packages/starknet-snap/src/recoverAccounts.ts index 43571edf..7cd6bfb9 100644 --- a/packages/starknet-snap/src/recoverAccounts.ts +++ b/packages/starknet-snap/src/recoverAccounts.ts @@ -1,9 +1,6 @@ import { toJson } from './utils/serializer'; import { num } from 'starknet'; -import { - getKeysFromAddressIndex, - getCorrectContractAddress, -} from './utils/starknetUtils'; +import { getKeysFromAddressIndex, getCorrectContractAddress } from './utils/starknetUtils'; import { getNetworkFromChainId, getValidNumber, upsertAccount } from './utils/snapUtils'; import { AccContract } from './types/snapState'; import { ApiParams, RecoverAccountsRequestParams } from './types/snapApi'; diff --git a/packages/starknet-snap/src/utils/snapUtils.ts b/packages/starknet-snap/src/utils/snapUtils.ts index 52399222..dfe5b34c 100644 --- a/packages/starknet-snap/src/utils/snapUtils.ts +++ b/packages/starknet-snap/src/utils/snapUtils.ts @@ -781,7 +781,9 @@ export async function showDeployRequestModal(wallet) { content: panel([ heading('Account Deployment Mandatory!'), text( - `Visit the [companion dapp for Starknet](${dappUrl(process.env.SNAP_ENV)}) to deploy pour account.\nThank you!`, + `Visit the [companion dapp for Starknet](${dappUrl( + process.env.SNAP_ENV, + )}) to deploy pour account.\nThank you!`, ), ]), }, diff --git a/packages/starknet-snap/src/utils/starknetUtils.ts b/packages/starknet-snap/src/utils/starknetUtils.ts index 10ae0172..34a06827 100644 --- a/packages/starknet-snap/src/utils/starknetUtils.ts +++ b/packages/starknet-snap/src/utils/starknetUtils.ts @@ -52,7 +52,6 @@ import { getAddressKey } from './keyPair'; import { getAccount, getAccounts, - getErc20Token, getEtherErc20Token, getRPCUrl, getTransactionFromVoyagerUrl, @@ -62,7 +61,6 @@ import { import { logger } from './logger'; import { RpcV4GetTransactionReceiptResponse } from '../types/snapApi'; import { hexToString } from './formatterUtils'; -import { getErc20TokenBalance } from '../getErc20TokenBalance'; export const getCallDataArray = (callDataStr: string): string[] => { return (callDataStr ?? '') @@ -745,7 +743,7 @@ export function getUpgradeTxnInvocation(contractAddress: string) { * @returns The calculated transaction fee as a bigint. */ export async function estimateAccountUpgradeFee( - network: any, + network: Network, contractAddress: string, privateKey: string, maxFee: BigNumberish = constants.ZERO, diff --git a/packages/starknet-snap/test/src/createAccount.test.ts b/packages/starknet-snap/test/src/createAccount.test.ts index 7b2170c5..241002ba 100644 --- a/packages/starknet-snap/test/src/createAccount.test.ts +++ b/packages/starknet-snap/test/src/createAccount.test.ts @@ -17,8 +17,6 @@ import { estimateDeployFeeResp, getBalanceResp, account1, - estimateDeployFeeResp2, - estimateDeployFeeResp3, } from '../constants.test'; import { getAddressKeyDeriver } from '../../src/utils/keyPair'; import { Mutex } from 'async-mutex'; diff --git a/packages/starknet-snap/test/src/executeTxn.test.ts b/packages/starknet-snap/test/src/executeTxn.test.ts index ba0449a9..4acf3ab2 100644 --- a/packages/starknet-snap/test/src/executeTxn.test.ts +++ b/packages/starknet-snap/test/src/executeTxn.test.ts @@ -81,7 +81,9 @@ describe('Test function: executeTxn', function () { }); it('should 1) throw an error and 2) show upgrade modal if account upgrade required', async function () { - const getCorrectContractAddressStub = sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: true, deployRequired: false}); + const getCorrectContractAddressStub = sandbox + .stub(utils, 'getCorrectContractAddress') + .resolves({ address: '', signerPubKey: '', upgradeRequired: true, deployRequired: false }); const showUpgradeRequestModalStub = sandbox.stub(snapsUtil, 'showUpgradeRequestModal').resolves(); let result; try { @@ -96,7 +98,9 @@ describe('Test function: executeTxn', function () { }); it('should 1) throw an error and 2) show deploy modal if account deployed required', async function () { - const getCorrectContractAddressStub = sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: true, deployRequired: true}); + const getCorrectContractAddressStub = sandbox + .stub(utils, 'getCorrectContractAddress') + .resolves({ address: '', signerPubKey: '', upgradeRequired: true, deployRequired: true }); const showDeployRequestModalStub = sandbox.stub(snapsUtil, 'showDeployRequestModal').resolves(); let result; try { @@ -111,7 +115,9 @@ describe('Test function: executeTxn', function () { }); it('should executeTxn correctly and deploy an account', async function () { - sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: false, deployRequired: false}); + sandbox + .stub(utils, 'getCorrectContractAddress') + .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); sandbox.stub(utils, 'isAccountDeployed').resolves(false); const createAccountStub = sandbox.stub(createAccountUtils, 'createAccount').resolvesThis(); const stub = sandbox.stub(utils, 'executeTxn').resolves({ @@ -143,7 +149,9 @@ describe('Test function: executeTxn', function () { }); it('should executeTxn multiple and deploy an account', async function () { - sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: false, deployRequired: true}); + sandbox + .stub(utils, 'getCorrectContractAddress') + .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: true }); sandbox.stub(utils, 'isAccountDeployed').resolves(false); const createAccountStub = sandbox.stub(createAccountUtils, 'createAccount').resolvesThis(); const stub = sandbox.stub(utils, 'executeTxn').resolves({ @@ -204,7 +212,9 @@ describe('Test function: executeTxn', function () { it('should executeTxn and not deploy an account', async function () { const createAccountStub = sandbox.stub(createAccountUtils, 'createAccount'); - sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: false, deployRequired: false}); + sandbox + .stub(utils, 'getCorrectContractAddress') + .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); sandbox.stub(utils, 'isAccountDeployed').resolves(true); const stub = sandbox.stub(utils, 'executeTxn').resolves({ transaction_hash: 'transaction_hash', @@ -238,7 +248,9 @@ describe('Test function: executeTxn', function () { it('should executeTxn multiple and not deploy an account', async function () { const createAccountStub = sandbox.stub(createAccountUtils, 'createAccount'); - sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: false, deployRequired: false}); + sandbox + .stub(utils, 'getCorrectContractAddress') + .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); sandbox.stub(utils, 'isAccountDeployed').resolves(true); const stub = sandbox.stub(utils, 'executeTxn').resolves({ transaction_hash: 'transaction_hash', @@ -297,7 +309,9 @@ describe('Test function: executeTxn', function () { }); it('should throw error if executeTxn fail', async function () { - sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: false, deployRequired: false}); + sandbox + .stub(utils, 'getCorrectContractAddress') + .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); sandbox.stub(utils, 'isAccountDeployed').resolves(true); const stub = sandbox.stub(utils, 'executeTxn').rejects('error'); const { privateKey } = await utils.getKeysFromAddress( @@ -330,7 +344,9 @@ describe('Test function: executeTxn', function () { }); it('should return false if user rejected to sign the transaction', async function () { - sandbox.stub(utils, 'getCorrectContractAddress').resolves({address: "", signerPubKey: "", upgradeRequired: false, deployRequired: false}); + sandbox + .stub(utils, 'getCorrectContractAddress') + .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); sandbox.stub(utils, 'isAccountDeployed').resolves(true); walletStub.rpcStubs.snap_dialog.resolves(false); const stub = sandbox.stub(utils, 'executeTxn').resolves({ diff --git a/packages/wallet-ui/src/services/useStarkNetSnap.ts b/packages/wallet-ui/src/services/useStarkNetSnap.ts index a19e853d..5b7feada 100644 --- a/packages/wallet-ui/src/services/useStarkNetSnap.ts +++ b/packages/wallet-ui/src/services/useStarkNetSnap.ts @@ -694,8 +694,7 @@ export const useStarkNetSnap = () => { dispatch(disableLoading()); return false; } - - } + }; const waitForAccountUpdate = async (transactionHash: string, accountAddress: string, chainId: string) => { dispatch(enableLoadingWithMessage('Waiting for transaction to be finalised.')); From a40b72ca0a6239d9ca48a03c0e2c5065e191ea95 Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Tue, 9 Jul 2024 16:37:59 +0200 Subject: [PATCH 10/16] fix: review comment --- packages/starknet-snap/src/createAccount.ts | 7 +- packages/starknet-snap/src/executeTxn.ts | 4 +- packages/starknet-snap/src/recoverAccounts.ts | 2 +- packages/starknet-snap/src/utils/snapUtils.ts | 3 + .../starknet-snap/src/utils/starknetUtils.ts | 64 ++++++++----------- .../test/utils/starknetUtils.test.ts | 31 ++++----- .../UpgradeModel/UpgradeModel.view.tsx | 6 +- 7 files changed, 52 insertions(+), 65 deletions(-) diff --git a/packages/starknet-snap/src/createAccount.ts b/packages/starknet-snap/src/createAccount.ts index 9a6e18d8..720868f2 100644 --- a/packages/starknet-snap/src/createAccount.ts +++ b/packages/starknet-snap/src/createAccount.ts @@ -18,7 +18,7 @@ import { AccContract, VoyagerTransactionType, Transaction, TransactionStatus } f import { ApiParams, CreateAccountRequestParams } from './types/snapApi'; import { heading, panel, DialogType } from '@metamask/snaps-sdk'; import { logger } from './utils/logger'; -import { ACCOUNT_CLASS_HASH_LEGACY, CAIRO_VERSION, CAIRO_VERSION_LEGACY } from './utils/constants'; +import { CAIRO_VERSION, CAIRO_VERSION_LEGACY } from './utils/constants'; import { CairoVersion, EstimateFee, num } from 'starknet'; /** @@ -68,12 +68,13 @@ export async function createAccount( contractCallData, publicKey, privateKey, + cairoVersion, ); logger.log(`estimateAccountDeployFee:\nestimateDeployFee: ${toJson(estimateDeployFee)}`); const maxFee = num.toBigInt(estimateDeployFee.suggestedMaxFee.toString(10) ?? '0'); const dialogComponents = getSendTxnText( state, - ACCOUNT_CLASS_HASH_LEGACY, + contractAddress, 'deploy', contractCallData, contractAddress, @@ -114,6 +115,8 @@ export async function createAccount( derivationPath, deployTxnHash: deployResp.transaction_hash, chainId: network.chainId, + upgradeRequired: cairoVersion !== CAIRO_VERSION, + deployRequired: false, }; await upsertAccount(userAccount, wallet, saveMutex); diff --git a/packages/starknet-snap/src/executeTxn.ts b/packages/starknet-snap/src/executeTxn.ts index 8489a950..b3e4fd85 100644 --- a/packages/starknet-snap/src/executeTxn.ts +++ b/packages/starknet-snap/src/executeTxn.ts @@ -33,12 +33,12 @@ export async function executeTxn(params: ApiParams) { addressIndex, } = await getKeysFromAddress(keyDeriver, network, state, senderAddress); - const { upgradeRequired, deployRequired } = await getCorrectContractAddress(network, publicKey, state); + const { upgradeRequired, deployRequired, address } = await getCorrectContractAddress(network, publicKey); if (upgradeRequired && deployRequired) { // Edge case force cairo0 deploy because non-zero balance await showDeployRequestModal(wallet); - throw new Error('Deploy required'); + throw new Error(`Cairo 0 contract address ${address} balance is not empty, deploy required`); } if (upgradeRequired && !deployRequired) { diff --git a/packages/starknet-snap/src/recoverAccounts.ts b/packages/starknet-snap/src/recoverAccounts.ts index 7cd6bfb9..a33c051b 100644 --- a/packages/starknet-snap/src/recoverAccounts.ts +++ b/packages/starknet-snap/src/recoverAccounts.ts @@ -34,7 +34,7 @@ export async function recoverAccounts(params: ApiParams) { signerPubKey: signerPublicKey, upgradeRequired, deployRequired, - } = await getCorrectContractAddress(network, publicKey, state); + } = await getCorrectContractAddress(network, publicKey); logger.log( `recoverAccounts: index ${i}:\ncontractAddress = ${contractAddress}\npublicKey = ${publicKey}\nisUpgradeRequired = ${upgradeRequired}`, ); diff --git a/packages/starknet-snap/src/utils/snapUtils.ts b/packages/starknet-snap/src/utils/snapUtils.ts index dfe5b34c..7aa62150 100644 --- a/packages/starknet-snap/src/utils/snapUtils.ts +++ b/packages/starknet-snap/src/utils/snapUtils.ts @@ -259,6 +259,9 @@ export function getSendTxnText( logger.error(`getSigningTxnText: error found in amount conversion: ${err}`); } } + if (contractFuncName === 'deploy') { + // [TODO] handle specific deploy dialog aspects ? + } return components; } diff --git a/packages/starknet-snap/src/utils/starknetUtils.ts b/packages/starknet-snap/src/utils/starknetUtils.ts index 34a06827..ce0569d7 100644 --- a/packages/starknet-snap/src/utils/starknetUtils.ts +++ b/packages/starknet-snap/src/utils/starknetUtils.ts @@ -47,12 +47,13 @@ import { ACCOUNT_CLASS_HASH, CAIRO_VERSION, CAIRO_VERSION_LEGACY, + ETHER_MAINNET, + ETHER_SEPOLIA_TESTNET, } from './constants'; import { getAddressKey } from './keyPair'; import { getAccount, getAccounts, - getEtherErc20Token, getRPCUrl, getTransactionFromVoyagerUrl, getTransactionsFromVoyagerUrl, @@ -763,12 +764,7 @@ export async function estimateAccountUpgradeFee( * @param publicKey - address's public key. * @returns - address and address's public key. */ -export const getCorrectContractAddress = async ( - network: Network, - publicKey: string, - state: SnapState, - maxFee = constants.ZERO, -) => { +export const getCorrectContractAddress = async (network: Network, publicKey: string, maxFee = constants.ZERO) => { const { address: contractAddress, addressLegacy: contractAddressLegacy } = getPermutationAddresses(publicKey); logger.log( @@ -788,50 +784,44 @@ export const getCorrectContractAddress = async ( throw e; } - logger.log( - `getContractAddressByKey: cairo ${CAIRO_VERSION} contract cant found, try cairo ${CAIRO_VERSION_LEGACY}`, - ); + logger.log(`getContractAddressByKey: cairo ${CAIRO_VERSION} contract not found, try cairo ${CAIRO_VERSION_LEGACY}`); try { + address = contractAddressLegacy; const version = await getVersion(contractAddressLegacy, network); upgradeRequired = isGTEMinVersion(hexToString(version)) ? false : true; + console.log(`upgradeRequired: ${upgradeRequired}`); pk = await getContractOwner( contractAddressLegacy, network, upgradeRequired ? CAIRO_VERSION_LEGACY : CAIRO_VERSION, ); - address = contractAddressLegacy; } catch (e) { - if (e.message.includes('network error for getSigner')) { + if (!e.message.includes('Contract not found')) { throw e; } - const accountDeployed = await isAccountDeployed(network, address); - if (accountDeployed) { - address = contractAddressLegacy; - upgradeRequired = true; - deployRequired = false; - } else { - // Edge case detection - logger.log(`getContractAddressByKey: no deployed contract found, checking balance for edge cases`); - try { - const balance = num.toBigInt( - (await getBalance(contractAddressLegacy, getEtherErc20Token(state, network.chainId)?.address, network)) ?? - num.toBigInt(constants.ZERO), + // Here account is not deployed, proceed with edge case detection + logger.log(`getContractAddressByKey: no deployed contract found, checking balance for edge cases`); + try { + const etherErc20TokenAddress = + network.chainId === ETHER_SEPOLIA_TESTNET.chainId ? ETHER_SEPOLIA_TESTNET.address : ETHER_MAINNET.address; + + const balance = num.toBigInt( + (await getBalance(contractAddressLegacy, etherErc20TokenAddress, network)) ?? num.toBigInt(constants.ZERO), + ); + if (balance > maxFee) { + upgradeRequired = true; + deployRequired = true; + logger.log( + `getContractAddressByKey: non deployed cairo0 contract found with non-zero balance, force cairo ${CAIRO_VERSION_LEGACY}`, ); - if (balance > maxFee) { - upgradeRequired = true; - deployRequired = true; - address = contractAddressLegacy; - logger.log( - `getContractAddressByKey: non deployed cairo0 contract found with non-zero balance, force cairo ${CAIRO_VERSION_LEGACY}`, - ); - } else { - logger.log(`getContractAddressByKey: no deployed contract found, fallback to cairo ${CAIRO_VERSION}`); - } - } catch (err) { - logger.log(`getContractAddressByKey: balance check failed with error ${err}`); - throw err; + } else { + address = contractAddress; + logger.log(`getContractAddressByKey: no deployed contract found, fallback to cairo ${CAIRO_VERSION}`); } + } catch (err) { + logger.log(`getContractAddressByKey: balance check failed with error ${err}`); + throw err; } } } diff --git a/packages/starknet-snap/test/utils/starknetUtils.test.ts b/packages/starknet-snap/test/utils/starknetUtils.test.ts index 21956bf4..7c37d24d 100644 --- a/packages/starknet-snap/test/utils/starknetUtils.test.ts +++ b/packages/starknet-snap/test/utils/starknetUtils.test.ts @@ -12,7 +12,6 @@ import { account1, account2, account3, - token0, getBalanceResp, getNonZeroBalanceResp, } from '../constants.test'; @@ -426,12 +425,6 @@ describe('Test function: getContractOwner', function () { }); describe('Test function: getCorrectContractAddress', function () { - const state: SnapState = { - accContracts: [], - erc20Tokens: [token0], - networks: [STARKNET_SEPOLIA_TESTNET_NETWORK], - transactions: [], - }; const walletStub = new WalletMock(); let getAccContractAddressAndCallDataStub: sinon.SinonStub; let getAccContractAddressAndCallDataLegacyStub: sinon.SinonStub; @@ -462,7 +455,7 @@ describe('Test function: getCorrectContractAddress', function () { sandbox.stub(utils, 'getSigner').callsFake(async () => PK); sandbox.stub(utils, 'getVersion').callsFake(async () => cairoVersionHex); - await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); + await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); expect(getAccContractAddressAndCallDataStub).to.have.been.calledOnceWith(PK); expect(getAccContractAddressAndCallDataLegacyStub).to.have.been.calledOnceWith(PK); }); @@ -474,7 +467,7 @@ describe('Test function: getCorrectContractAddress', function () { let result = null; try { - await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); + await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); } catch (e) { result = e; } finally { @@ -497,7 +490,7 @@ describe('Test function: getCorrectContractAddress', function () { let result = null; try { - await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); + await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); } catch (e) { result = e; } finally { @@ -508,12 +501,12 @@ describe('Test function: getCorrectContractAddress', function () { }); describe(`when contact is Cairo${CAIRO_VERSION} has deployed`, function () { - it(`should return Cairo${CAIRO_VERSION} address with pubic key`, async function () { + it(`should return Cairo${CAIRO_VERSION} address with public key`, async function () { getVersionStub = sandbox.stub(utils, 'getVersion').resolves(cairoVersionHex); getSignerStub = sandbox.stub(utils, 'getSigner').resolves(PK); getOwnerStub = sandbox.stub(utils, 'getOwner').resolves(PK); - const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); + const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); expect(getVersionStub).to.have.been.calledOnceWith(account1.address, STARKNET_SEPOLIA_TESTNET_NETWORK); expect(getOwnerStub).to.have.been.calledOnceWith(account1.address, STARKNET_SEPOLIA_TESTNET_NETWORK); expect(getSignerStub).to.have.been.callCount(0); @@ -523,9 +516,9 @@ describe('Test function: getCorrectContractAddress', function () { }); }); - describe(`when contract is Cairo${CAIRO_VERSION} has not deployed`, function () { - describe(`when when is Cairo${CAIRO_VERSION_LEGACY} has deployed`, function () { - describe(`when when is Cairo${CAIRO_VERSION_LEGACY} has upgraded`, function () { + describe(`when Cairo${CAIRO_VERSION} has not deployed`, function () { + describe(`and Cairo${CAIRO_VERSION_LEGACY} has deployed`, function () { + describe(`and Cairo${CAIRO_VERSION_LEGACY} has upgraded`, function () { it(`should return Cairo${CAIRO_VERSION_LEGACY} address with upgrade = false`, async function () { sandbox .stub(utils, 'getVersion') @@ -537,7 +530,7 @@ describe('Test function: getCorrectContractAddress', function () { getSignerStub = sandbox.stub(utils, 'getSigner').resolves(PK); getOwnerStub = sandbox.stub(utils, 'getOwner').resolves(PK); - const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); + const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); expect(getOwnerStub).to.have.been.calledOnceWith(account2.address, STARKNET_SEPOLIA_TESTNET_NETWORK); expect(getSignerStub).to.have.been.callCount(0); @@ -559,7 +552,7 @@ describe('Test function: getCorrectContractAddress', function () { getSignerStub = sandbox.stub(utils, 'getSigner').resolves(PK); getOwnerStub = sandbox.stub(utils, 'getOwner').resolves(PK); - const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); + const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); expect(getSignerStub).to.have.been.calledOnceWith(account2.address, STARKNET_SEPOLIA_TESTNET_NETWORK); expect(getOwnerStub).to.have.been.callCount(0); @@ -578,7 +571,7 @@ describe('Test function: getCorrectContractAddress', function () { getSignerStub = sandbox.stub(utils, 'getSigner').resolves(PK); getOwnerStub = sandbox.stub(utils, 'getOwner').resolves(PK); - const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); + const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); expect(getSignerStub).to.have.been.callCount(0); expect(getOwnerStub).to.have.been.callCount(0); @@ -593,7 +586,7 @@ describe('Test function: getCorrectContractAddress', function () { getSignerStub = sandbox.stub(utils, 'getSigner').resolves(PK); getOwnerStub = sandbox.stub(utils, 'getOwner').resolves(PK); - const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK, state); + const result = await utils.getCorrectContractAddress(STARKNET_SEPOLIA_TESTNET_NETWORK, PK); expect(getSignerStub).to.have.been.callCount(0); expect(getOwnerStub).to.have.been.callCount(0); diff --git a/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx b/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx index 60c6653b..9b7016a4 100644 --- a/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx +++ b/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx @@ -69,8 +69,6 @@ export const UpgradeModelView = ({ address }: Props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [stage, dispatch]); - const upgradeTxt = 'Upgrade'; - const renderComponent = () => { switch (stage) { case Stage.INIT: @@ -85,11 +83,11 @@ export const UpgradeModelView = ({ address }: Props) => { this version.

- Click on the "{upgradeTxt}" button to install it. + Click on the Upgrade button to install it.
Thank you! - {upgradeTxt} + Upgrade ); case Stage.WAITING_FOR_TXN: From e5058b4f2d27cef40bc4ad64fa6efc94c65463b1 Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Tue, 9 Jul 2024 18:02:52 +0200 Subject: [PATCH 11/16] feat: replace upgradeRequired by getCorrectContractAddress to handle deploy and upgrade required --- packages/starknet-snap/src/declareContract.ts | 25 ++++++++++-- packages/starknet-snap/src/estimateFee.ts | 18 ++++++--- .../starknet-snap/src/extractPrivateKey.ts | 16 +++++--- .../starknet-snap/src/extractPublicKey.ts | 13 +++++-- .../src/signDeclareTransaction.ts | 21 ++++++++-- .../src/signDeployAccountTransaction.ts | 21 ++++++++-- packages/starknet-snap/src/signMessage.ts | 39 ++++++++++++------- packages/starknet-snap/src/signTransaction.ts | 21 ++++++++-- .../starknet-snap/src/verifySignedMessage.ts | 19 +++++++-- packages/starknet-snap/test/constants.test.ts | 4 +- .../test/src/declareContract.test.ts | 21 +++++++--- .../test/src/estimateFee.test.ts | 11 ++++-- .../test/src/extractPrivateKey.test.ts | 24 ++++++++---- .../test/src/extractPublicKey.test.ts | 24 ++++++++---- .../test/src/signDeclareTransaction.test.ts | 29 ++++++++++---- .../src/signDeployAccountTransaction.test.ts | 29 ++++++++++---- .../test/src/signMessage.test.ts | 28 +++++++++---- .../test/src/signTransaction.test.ts | 31 +++++++++++---- .../test/src/verifySignedMessage.test.ts | 22 ++++++++--- 19 files changed, 309 insertions(+), 107 deletions(-) diff --git a/packages/starknet-snap/src/declareContract.ts b/packages/starknet-snap/src/declareContract.ts index 516ed21a..86fe7b9a 100644 --- a/packages/starknet-snap/src/declareContract.ts +++ b/packages/starknet-snap/src/declareContract.ts @@ -1,7 +1,16 @@ import { toJson } from './utils/serializer'; import { ApiParams, DeclareContractRequestParams } from './types/snapApi'; -import { getNetworkFromChainId, getDeclareSnapTxt, showUpgradeRequestModal } from './utils/snapUtils'; -import { getKeysFromAddress, declareContract as declareContractUtil, isUpgradeRequired } from './utils/starknetUtils'; +import { + getNetworkFromChainId, + getDeclareSnapTxt, + showUpgradeRequestModal, + showDeployRequestModal, +} from './utils/snapUtils'; +import { + getKeysFromAddress, + declareContract as declareContractUtil, + getCorrectContractAddress, +} from './utils/starknetUtils'; import { heading, panel, DialogType } from '@metamask/snaps-sdk'; import { logger } from './utils/logger'; @@ -14,9 +23,17 @@ export async function declareContract(params: ApiParams) { const senderAddress = requestParamsObj.senderAddress; const network = getNetworkFromChainId(state, requestParamsObj.chainId); - const { privateKey } = await getKeysFromAddress(keyDeriver, network, state, senderAddress); + const { privateKey, publicKey } = await getKeysFromAddress(keyDeriver, network, state, senderAddress); - if (await isUpgradeRequired(network, senderAddress)) { + const { upgradeRequired, deployRequired, address } = await getCorrectContractAddress(network, publicKey); + + if (upgradeRequired && deployRequired) { + // Edge case force cairo0 deploy because non-zero balance + await showDeployRequestModal(wallet); + throw new Error(`Cairo 0 contract address ${address} balance is not empty, deploy required`); + } + + if (upgradeRequired && !deployRequired) { await showUpgradeRequestModal(wallet); throw new Error('Upgrade required'); } diff --git a/packages/starknet-snap/src/estimateFee.ts b/packages/starknet-snap/src/estimateFee.ts index c917c6e4..921fa94e 100644 --- a/packages/starknet-snap/src/estimateFee.ts +++ b/packages/starknet-snap/src/estimateFee.ts @@ -1,6 +1,6 @@ import { toJson } from './utils/serializer'; import { Invocations, TransactionType } from 'starknet'; -import { validateAndParseAddress } from '../src/utils/starknetUtils'; +import { getCorrectContractAddress, validateAndParseAddress } from '../src/utils/starknetUtils'; import { ApiParams, EstimateFeeRequestParams } from './types/snapApi'; import { getNetworkFromChainId } from './utils/snapUtils'; import { @@ -11,7 +11,6 @@ import { estimateFeeBulk, addFeesFromAllTransactions, isAccountDeployed, - isUpgradeRequired, } from './utils/starknetUtils'; import { ACCOUNT_CLASS_HASH } from './utils/constants'; import { logger } from './utils/logger'; @@ -45,10 +44,6 @@ export async function estimateFee(params: ApiParams) { throw new Error(`The given sender address is invalid: ${senderAddress}`); } - if (await isUpgradeRequired(network, senderAddress)) { - throw new Error('Upgrade required'); - } - const { privateKey: senderPrivateKey, publicKey } = await getKeysFromAddress( keyDeriver, network, @@ -56,6 +51,17 @@ export async function estimateFee(params: ApiParams) { senderAddress, ); + const { upgradeRequired, deployRequired, address } = await getCorrectContractAddress(network, publicKey); + + if (upgradeRequired && deployRequired) { + // Edge case force cairo0 deploy because non-zero balance + throw new Error(`Cairo 0 contract address ${address} balance is not empty, deploy required`); + } + + if (upgradeRequired && !deployRequired) { + throw new Error('Upgrade required'); + } + const txnInvocation = { contractAddress, entrypoint: contractFuncName, diff --git a/packages/starknet-snap/src/extractPrivateKey.ts b/packages/starknet-snap/src/extractPrivateKey.ts index ec449bf6..8234d691 100644 --- a/packages/starknet-snap/src/extractPrivateKey.ts +++ b/packages/starknet-snap/src/extractPrivateKey.ts @@ -1,8 +1,8 @@ import { toJson } from './utils/serializer'; -import { validateAndParseAddress } from '../src/utils/starknetUtils'; +import { getCorrectContractAddress, validateAndParseAddress } from '../src/utils/starknetUtils'; import { ApiParams, ExtractPrivateKeyRequestParams } from './types/snapApi'; import { getNetworkFromChainId } from './utils/snapUtils'; -import { getKeysFromAddress, isUpgradeRequired } from './utils/starknetUtils'; +import { getKeysFromAddress } from './utils/starknetUtils'; import { copyable, panel, text, DialogType } from '@metamask/snaps-sdk'; import { logger } from './utils/logger'; @@ -22,7 +22,15 @@ export async function extractPrivateKey(params: ApiParams) { throw new Error(`The given user address is invalid: ${userAddress}`); } - if (await isUpgradeRequired(network, userAddress)) { + const { privateKey: userPrivateKey, publicKey } = await getKeysFromAddress(keyDeriver, network, state, userAddress); + const { upgradeRequired, deployRequired, address } = await getCorrectContractAddress(network, publicKey); + + if (upgradeRequired && deployRequired) { + // Edge case force cairo0 deploy because non-zero balance + throw new Error(`Cairo 0 contract address ${address} balance is not empty, deploy required`); + } + + if (upgradeRequired && !deployRequired) { throw new Error('Upgrade required'); } @@ -35,8 +43,6 @@ export async function extractPrivateKey(params: ApiParams) { }); if (response === true) { - const { privateKey: userPrivateKey } = await getKeysFromAddress(keyDeriver, network, state, userAddress); - await wallet.request({ method: 'snap_dialog', params: { diff --git a/packages/starknet-snap/src/extractPublicKey.ts b/packages/starknet-snap/src/extractPublicKey.ts index 1fa67fe5..6a8548ac 100644 --- a/packages/starknet-snap/src/extractPublicKey.ts +++ b/packages/starknet-snap/src/extractPublicKey.ts @@ -1,6 +1,6 @@ import { toJson } from './utils/serializer'; import { constants, num } from 'starknet'; -import { validateAndParseAddress, isUpgradeRequired } from '../src/utils/starknetUtils'; +import { validateAndParseAddress, getCorrectContractAddress } from '../src/utils/starknetUtils'; import { ApiParams, ExtractPublicKeyRequestParams } from './types/snapApi'; import { getAccount, getNetworkFromChainId } from './utils/snapUtils'; import { getKeysFromAddress } from './utils/starknetUtils'; @@ -26,7 +26,15 @@ export async function extractPublicKey(params: ApiParams) { throw new Error(`The given user address is invalid: ${requestParamsObj.userAddress}`); } - if (await isUpgradeRequired(network, userAddress)) { + const { publicKey } = await getKeysFromAddress(keyDeriver, network, state, userAddress); + const { upgradeRequired, deployRequired, address } = await getCorrectContractAddress(network, publicKey); + + if (upgradeRequired && deployRequired) { + // Edge case force cairo0 deploy because non-zero balance + throw new Error(`Cairo 0 contract address ${address} balance is not empty, deploy required`); + } + + if (upgradeRequired && !deployRequired) { throw new Error('Upgrade required'); } @@ -34,7 +42,6 @@ export async function extractPublicKey(params: ApiParams) { const accContract = getAccount(state, userAddress, network.chainId); if (!accContract?.publicKey || num.toBigInt(accContract.publicKey) === constants.ZERO) { logger.log(`extractPublicKey: User address cannot be found or the signer public key is 0x0: ${userAddress}`); - const { publicKey } = await getKeysFromAddress(keyDeriver, network, state, userAddress); userPublicKey = publicKey; } else { userPublicKey = accContract.publicKey; diff --git a/packages/starknet-snap/src/signDeclareTransaction.ts b/packages/starknet-snap/src/signDeclareTransaction.ts index 681db510..280931a4 100644 --- a/packages/starknet-snap/src/signDeclareTransaction.ts +++ b/packages/starknet-snap/src/signDeclareTransaction.ts @@ -4,9 +4,14 @@ import { ApiParams, SignDeclareTransactionRequestParams } from './types/snapApi' import { getKeysFromAddress, signDeclareTransaction as signDeclareTransactionUtil, - isUpgradeRequired, + getCorrectContractAddress, } from './utils/starknetUtils'; -import { getNetworkFromChainId, getSignTxnTxt, showUpgradeRequestModal } from './utils/snapUtils'; +import { + getNetworkFromChainId, + getSignTxnTxt, + showDeployRequestModal, + showUpgradeRequestModal, +} from './utils/snapUtils'; import { heading, panel, DialogType } from '@metamask/snaps-sdk'; import { logger } from './utils/logger'; @@ -16,9 +21,17 @@ export async function signDeclareTransaction(params: ApiParams): Promise Date: Wed, 10 Jul 2024 09:10:57 +0200 Subject: [PATCH 12/16] fix: pr review comment --- packages/starknet-snap/src/createAccount.ts | 2 +- packages/starknet-snap/src/utils/snapUtils.ts | 1 + .../ui/organism/DeployModal/DeployModal.view.tsx | 8 +++----- .../ui/organism/UpgradeModel/UpgradeModel.view.tsx | 2 +- packages/wallet-ui/src/services/useStarkNetSnap.ts | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/starknet-snap/src/createAccount.ts b/packages/starknet-snap/src/createAccount.ts index 720868f2..03510f65 100644 --- a/packages/starknet-snap/src/createAccount.ts +++ b/packages/starknet-snap/src/createAccount.ts @@ -115,7 +115,7 @@ export async function createAccount( derivationPath, deployTxnHash: deployResp.transaction_hash, chainId: network.chainId, - upgradeRequired: cairoVersion !== CAIRO_VERSION, + upgradeRequired: cairoVersion === CAIRO_VERSION_LEGACY, deployRequired: false, }; diff --git a/packages/starknet-snap/src/utils/snapUtils.ts b/packages/starknet-snap/src/utils/snapUtils.ts index 7aa62150..31a7aabc 100644 --- a/packages/starknet-snap/src/utils/snapUtils.ts +++ b/packages/starknet-snap/src/utils/snapUtils.ts @@ -373,6 +373,7 @@ export async function upsertAccount(userAccount: AccContract, wallet, mutex: Mut storedAccount.publicKey = userAccount.publicKey; storedAccount.deployTxnHash = userAccount.deployTxnHash || storedAccount.deployTxnHash; storedAccount.upgradeRequired = userAccount.upgradeRequired; + storedAccount.deployRequired = userAccount.deployRequired; } await wallet.request({ diff --git a/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.view.tsx b/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.view.tsx index 39628390..e481996b 100644 --- a/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.view.tsx +++ b/packages/wallet-ui/src/components/ui/organism/DeployModal/DeployModal.view.tsx @@ -50,7 +50,7 @@ export const DeployModalView = ({ address }: Props) => { useEffect(() => { if (txnHash) { setStage(Stage.WAITING_FOR_TXN); - waitForAccountCreation(txnHash, address, chainId) + waitForAccountCreation(txnHash, chainId) .then((resp) => { setStage(resp === true ? Stage.SUCCESS : Stage.FAIL); }) @@ -69,8 +69,6 @@ export const DeployModalView = ({ address }: Props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [stage, dispatch]); - const deployTxt = 'Deploy'; - const renderComponent = () => { switch (stage) { case Stage.INIT: @@ -83,11 +81,11 @@ export const DeployModalView = ({ address }: Props) => { A deployment of your address is necessary to proceed with the Snap.

- Click on the "{deployTxt}" button to proceed. + Click on the "Deploy" button to proceed.
Thank you! - {deployTxt} + Deploy ); case Stage.WAITING_FOR_TXN: diff --git a/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx b/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx index 9b7016a4..55602fdb 100644 --- a/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx +++ b/packages/wallet-ui/src/components/ui/organism/UpgradeModel/UpgradeModel.view.tsx @@ -83,7 +83,7 @@ export const UpgradeModelView = ({ address }: Props) => { this version.

- Click on the Upgrade button to install it. + Click on the "Upgrade" button to install it.
Thank you! diff --git a/packages/wallet-ui/src/services/useStarkNetSnap.ts b/packages/wallet-ui/src/services/useStarkNetSnap.ts index 5b7feada..3262b9d7 100644 --- a/packages/wallet-ui/src/services/useStarkNetSnap.ts +++ b/packages/wallet-ui/src/services/useStarkNetSnap.ts @@ -680,7 +680,7 @@ export const useStarkNetSnap = () => { return txStatus; }; - const waitForAccountCreation = async (transactionHash: string, accountAddress: string, chainId: string) => { + const waitForAccountCreation = async (transactionHash: string, chainId: string) => { dispatch(enableLoadingWithMessage('Waiting for transaction to be finalised.')); try { // read transaction to check if the txn is ready From 3b1178d88f413d71b39067d82932f950c4392063 Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Wed, 10 Jul 2024 11:24:52 +0200 Subject: [PATCH 13/16] refactor: upgrade / deploy requirement in non get-starknet api --- packages/starknet-snap/src/declareContract.ts | 26 +++---- packages/starknet-snap/src/estimateFee.ts | 13 +--- .../starknet-snap/src/extractPrivateKey.ts | 13 +--- .../starknet-snap/src/extractPublicKey.ts | 13 +--- .../starknet-snap/src/utils/starknetUtils.ts | 72 ++++++++++++++++--- .../starknet-snap/src/verifySignedMessage.ts | 12 +--- .../test/src/declareContract.test.ts | 45 ++++++++---- .../test/src/estimateFee.test.ts | 23 ++++-- .../test/src/extractPrivateKey.test.ts | 22 +++--- .../test/src/extractPublicKey.test.ts | 22 +++--- .../test/src/verifySignedMessage.test.ts | 22 +++--- .../test/utils/starknetUtils.test.ts | 2 +- 12 files changed, 165 insertions(+), 120 deletions(-) diff --git a/packages/starknet-snap/src/declareContract.ts b/packages/starknet-snap/src/declareContract.ts index 86fe7b9a..e06e28c6 100644 --- a/packages/starknet-snap/src/declareContract.ts +++ b/packages/starknet-snap/src/declareContract.ts @@ -9,7 +9,9 @@ import { import { getKeysFromAddress, declareContract as declareContractUtil, - getCorrectContractAddress, + validateAccountRequireUpgradeOrDeploy, + DeployRequiredError, + UpgradeRequiredError, } from './utils/starknetUtils'; import { heading, panel, DialogType } from '@metamask/snaps-sdk'; import { logger } from './utils/logger'; @@ -25,17 +27,17 @@ export async function declareContract(params: ApiParams) { const network = getNetworkFromChainId(state, requestParamsObj.chainId); const { privateKey, publicKey } = await getKeysFromAddress(keyDeriver, network, state, senderAddress); - const { upgradeRequired, deployRequired, address } = await getCorrectContractAddress(network, publicKey); - - if (upgradeRequired && deployRequired) { - // Edge case force cairo0 deploy because non-zero balance - await showDeployRequestModal(wallet); - throw new Error(`Cairo 0 contract address ${address} balance is not empty, deploy required`); - } - - if (upgradeRequired && !deployRequired) { - await showUpgradeRequestModal(wallet); - throw new Error('Upgrade required'); + try { + await validateAccountRequireUpgradeOrDeploy(network, senderAddress, publicKey); + } catch (e) { + //we can move this part to a snap utils function + if (e instanceof DeployRequiredError) { + await showDeployRequestModal(wallet); + } + if (e instanceof UpgradeRequiredError) { + await showUpgradeRequestModal(wallet); + } + throw e } const snapComponents = getDeclareSnapTxt( diff --git a/packages/starknet-snap/src/estimateFee.ts b/packages/starknet-snap/src/estimateFee.ts index 921fa94e..dfde25f2 100644 --- a/packages/starknet-snap/src/estimateFee.ts +++ b/packages/starknet-snap/src/estimateFee.ts @@ -1,6 +1,6 @@ import { toJson } from './utils/serializer'; import { Invocations, TransactionType } from 'starknet'; -import { getCorrectContractAddress, validateAndParseAddress } from '../src/utils/starknetUtils'; +import { getCorrectContractAddress, validateAccountRequireUpgradeOrDeploy, validateAndParseAddress } from '../src/utils/starknetUtils'; import { ApiParams, EstimateFeeRequestParams } from './types/snapApi'; import { getNetworkFromChainId } from './utils/snapUtils'; import { @@ -51,16 +51,7 @@ export async function estimateFee(params: ApiParams) { senderAddress, ); - const { upgradeRequired, deployRequired, address } = await getCorrectContractAddress(network, publicKey); - - if (upgradeRequired && deployRequired) { - // Edge case force cairo0 deploy because non-zero balance - throw new Error(`Cairo 0 contract address ${address} balance is not empty, deploy required`); - } - - if (upgradeRequired && !deployRequired) { - throw new Error('Upgrade required'); - } + await validateAccountRequireUpgradeOrDeploy(network, senderAddress, publicKey); const txnInvocation = { contractAddress, diff --git a/packages/starknet-snap/src/extractPrivateKey.ts b/packages/starknet-snap/src/extractPrivateKey.ts index 8234d691..f557ec7e 100644 --- a/packages/starknet-snap/src/extractPrivateKey.ts +++ b/packages/starknet-snap/src/extractPrivateKey.ts @@ -1,5 +1,5 @@ import { toJson } from './utils/serializer'; -import { getCorrectContractAddress, validateAndParseAddress } from '../src/utils/starknetUtils'; +import { getCorrectContractAddress, validateAccountRequireUpgradeOrDeploy, validateAndParseAddress } from '../src/utils/starknetUtils'; import { ApiParams, ExtractPrivateKeyRequestParams } from './types/snapApi'; import { getNetworkFromChainId } from './utils/snapUtils'; import { getKeysFromAddress } from './utils/starknetUtils'; @@ -23,16 +23,7 @@ export async function extractPrivateKey(params: ApiParams) { } const { privateKey: userPrivateKey, publicKey } = await getKeysFromAddress(keyDeriver, network, state, userAddress); - const { upgradeRequired, deployRequired, address } = await getCorrectContractAddress(network, publicKey); - - if (upgradeRequired && deployRequired) { - // Edge case force cairo0 deploy because non-zero balance - throw new Error(`Cairo 0 contract address ${address} balance is not empty, deploy required`); - } - - if (upgradeRequired && !deployRequired) { - throw new Error('Upgrade required'); - } + await validateAccountRequireUpgradeOrDeploy(network,userAddress, publicKey); const response = await wallet.request({ method: 'snap_dialog', diff --git a/packages/starknet-snap/src/extractPublicKey.ts b/packages/starknet-snap/src/extractPublicKey.ts index 6a8548ac..7cbdc741 100644 --- a/packages/starknet-snap/src/extractPublicKey.ts +++ b/packages/starknet-snap/src/extractPublicKey.ts @@ -1,6 +1,6 @@ import { toJson } from './utils/serializer'; import { constants, num } from 'starknet'; -import { validateAndParseAddress, getCorrectContractAddress } from '../src/utils/starknetUtils'; +import { validateAndParseAddress, getCorrectContractAddress, validateAccountRequireUpgradeOrDeploy } from '../src/utils/starknetUtils'; import { ApiParams, ExtractPublicKeyRequestParams } from './types/snapApi'; import { getAccount, getNetworkFromChainId } from './utils/snapUtils'; import { getKeysFromAddress } from './utils/starknetUtils'; @@ -27,16 +27,7 @@ export async function extractPublicKey(params: ApiParams) { } const { publicKey } = await getKeysFromAddress(keyDeriver, network, state, userAddress); - const { upgradeRequired, deployRequired, address } = await getCorrectContractAddress(network, publicKey); - - if (upgradeRequired && deployRequired) { - // Edge case force cairo0 deploy because non-zero balance - throw new Error(`Cairo 0 contract address ${address} balance is not empty, deploy required`); - } - - if (upgradeRequired && !deployRequired) { - throw new Error('Upgrade required'); - } + await validateAccountRequireUpgradeOrDeploy(network, userAddress, publicKey); let userPublicKey; const accContract = getAccount(state, userAddress, network.chainId); diff --git a/packages/starknet-snap/src/utils/starknetUtils.ts b/packages/starknet-snap/src/utils/starknetUtils.ts index ce0569d7..af546b80 100644 --- a/packages/starknet-snap/src/utils/starknetUtils.ts +++ b/packages/starknet-snap/src/utils/starknetUtils.ts @@ -63,6 +63,18 @@ import { logger } from './logger'; import { RpcV4GetTransactionReceiptResponse } from '../types/snapApi'; import { hexToString } from './formatterUtils'; +export class UpgradeRequiredError extends Error { + constructor(msg:string) { + super(msg); + } +} + +export class DeployRequiredError extends Error { + constructor(msg:string) { + super(msg); + } +} + export const getCallDataArray = (callDataStr: string): string[] => { return (callDataStr ?? '') .split(',') @@ -253,6 +265,17 @@ export const getBalance = async (address: string, tokenAddress: string, network: return resp[0]; }; + +export const isEthBalanceEmpty = async (network: Network, address: string, maxFee: bigint = constants.ZERO) => { + const etherErc20TokenAddress = + network.chainId === ETHER_SEPOLIA_TESTNET.chainId ? ETHER_SEPOLIA_TESTNET.address : ETHER_MAINNET.address; + + return num.toBigInt( + (await getBalance(address, etherErc20TokenAddress, network)) ?? num.toBigInt(constants.ZERO), + ) <= maxFee; +}; + + export const getTransactionStatus = async (transactionHash: num.BigNumberish, network: Network) => { const provider = getProvider(network); const receipt = (await provider.getTransactionReceipt(transactionHash)) as RpcV4GetTransactionReceiptResponse; @@ -680,6 +703,30 @@ export const getPermutationAddresses = (pk: string) => { }; }; +/** + * Check address needed deploy by using getVersion and check if eth balance is non empty. + * + * @param network - Network. + * @param address - Input address. + * @returns - boolean. + */ +export const isDeployRequired = async (network: Network, address: string, pubKey: string) => { + logger.log(`isDeployRequired: address = ${address}`); + const { address: addressLegacy } = getAccContractAddressAndCallDataLegacy(pubKey); + + try { + if (address === addressLegacy) { + await getVersion(address, network); + } + return false; + } catch (err) { + if (!err.message.includes('Contract not found')) { + throw err; + } + return !await(isEthBalanceEmpty(network, address)); + } +}; + /** * Check address needed upgrade by using getVersion and compare with MIN_ACC_CONTRACT_VERSION * @@ -801,23 +848,18 @@ export const getCorrectContractAddress = async (network: Network, publicKey: str throw e; } // Here account is not deployed, proceed with edge case detection - logger.log(`getContractAddressByKey: no deployed contract found, checking balance for edge cases`); try { - const etherErc20TokenAddress = - network.chainId === ETHER_SEPOLIA_TESTNET.chainId ? ETHER_SEPOLIA_TESTNET.address : ETHER_MAINNET.address; - - const balance = num.toBigInt( - (await getBalance(contractAddressLegacy, etherErc20TokenAddress, network)) ?? num.toBigInt(constants.ZERO), - ); - if (balance > maxFee) { + if (await isEthBalanceEmpty(network, address, maxFee)){ + console.log("but not here") + address = contractAddress; + logger.log(`getContractAddressByKey: no deployed contract found, fallback to cairo ${CAIRO_VERSION}`); + } + else{ upgradeRequired = true; deployRequired = true; logger.log( `getContractAddressByKey: non deployed cairo0 contract found with non-zero balance, force cairo ${CAIRO_VERSION_LEGACY}`, ); - } else { - address = contractAddress; - logger.log(`getContractAddressByKey: no deployed contract found, fallback to cairo ${CAIRO_VERSION}`); } } catch (err) { logger.log(`getContractAddressByKey: balance check failed with error ${err}`); @@ -872,3 +914,11 @@ export const getStarkNameUtil = async (network: Network, userAddress: string) => const provider = getProvider(network); return Account.getStarkName(provider, userAddress); }; + +export const validateAccountRequireUpgradeOrDeploy = async (network: Network, address: string, pubKey: string, displayDialog = true) => { + if (await isUpgradeRequired(network, address)) { + throw new UpgradeRequiredError('Upgrade required') + } else if (!(await isDeployRequired(network, address, pubKey))) { + throw new DeployRequiredError(`Cairo 0 contract address ${address} balance is not empty, deploy required`) + } +}; \ No newline at end of file diff --git a/packages/starknet-snap/src/verifySignedMessage.ts b/packages/starknet-snap/src/verifySignedMessage.ts index 55f07590..f05ba842 100644 --- a/packages/starknet-snap/src/verifySignedMessage.ts +++ b/packages/starknet-snap/src/verifySignedMessage.ts @@ -5,6 +5,7 @@ import { getFullPublicKeyPairFromPrivateKey, getKeysFromAddress, getCorrectContractAddress, + validateAccountRequireUpgradeOrDeploy, } from './utils/starknetUtils'; import { getNetworkFromChainId } from './utils/snapUtils'; import { ApiParams, VerifySignedMessageRequestParams } from './types/snapApi'; @@ -46,16 +47,7 @@ export async function verifySignedMessage(params: ApiParams) { state, verifySignerAddress, ); - const { upgradeRequired, deployRequired, address } = await getCorrectContractAddress(network, publicKey); - - if (upgradeRequired && deployRequired) { - // Edge case force cairo0 deploy because non-zero balance - throw new Error(`Cairo 0 contract address ${address} balance is not empty, deploy required`); - } - - if (upgradeRequired && !deployRequired) { - throw new Error('Upgrade required'); - } + await validateAccountRequireUpgradeOrDeploy(network,verifySignerAddress, publicKey); const fullPublicKey = getFullPublicKeyPairFromPrivateKey(signerPrivateKey); diff --git a/packages/starknet-snap/test/src/declareContract.test.ts b/packages/starknet-snap/test/src/declareContract.test.ts index fc9a4ac8..4c13a5b2 100644 --- a/packages/starknet-snap/test/src/declareContract.test.ts +++ b/packages/starknet-snap/test/src/declareContract.test.ts @@ -56,10 +56,9 @@ describe('Test function: declareContract', function () { sandbox.restore(); }); - it('should 1) throw an error and 2) show upgrade modal if account deployed required', async function () { - const getCorrectContractAddressStub = sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: true, deployRequired: false }); + it('should 1) throw an error and 2) show upgrade modal if account upgrade required', async function () { + const validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError("Upgrade Required")) const showUpgradeRequestModalStub = sandbox.stub(snapsUtil, 'showUpgradeRequestModal').resolves(); let result; try { @@ -67,8 +66,9 @@ describe('Test function: declareContract', function () { } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, account1.publicKey, ); expect(showUpgradeRequestModalStub).to.have.been.calledOnce; @@ -76,10 +76,29 @@ describe('Test function: declareContract', function () { } }); + it('should 1) throw an error and 2) show deploy modal if account deployed required', async function () { + const validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.DeployRequiredError(`Cairo 0 contract address ${account1.address} balance is not empty, deploy required`)) + const showDeployRequestModalStub = sandbox.stub(snapsUtil, 'showDeployRequestModal').resolves(); + let result; + try { + result = await declareContract(apiParams); + } catch (err) { + result = err; + } finally { + expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( + STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, + account1.publicKey, + ); + expect(showDeployRequestModalStub).to.have.been.calledOnce; + expect(result).to.be.an('Error'); + } + }); + it('should declareContract correctly', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .resolves(null); const declareContractStub = sandbox.stub(utils, 'declareContract').resolves({ transaction_hash: 'transaction_hash', class_hash: 'class_hash', @@ -107,9 +126,8 @@ describe('Test function: declareContract', function () { }); it('should throw error if declareContract fail', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .resolves(null); const declareContractStub = sandbox.stub(utils, 'declareContract').rejects('error'); const { privateKey } = await utils.getKeysFromAddress( apiParams.keyDeriver, @@ -136,9 +154,8 @@ describe('Test function: declareContract', function () { }); it('should return false if user rejected to sign the transaction', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .resolves(null); walletStub.rpcStubs.snap_dialog.resolves(false); const declareContractStub = sandbox.stub(utils, 'declareContract').resolves({ transaction_hash: 'transaction_hash', diff --git a/packages/starknet-snap/test/src/estimateFee.test.ts b/packages/starknet-snap/test/src/estimateFee.test.ts index 4f0efbfd..ac017a44 100644 --- a/packages/starknet-snap/test/src/estimateFee.test.ts +++ b/packages/starknet-snap/test/src/estimateFee.test.ts @@ -48,6 +48,7 @@ describe('Test function: estimateFee', function () { walletStub.rpcStubs.snap_getBip44Entropy.callsFake(getBip44EntropyStub); apiParams.keyDeriver = await getAddressKeyDeriver(walletStub); sandbox.stub(utils, 'callContract').resolves(getBalanceResp); + sandbox.stub(utils, 'getAccContractAddressAndCallDataLegacy').resolves(account2.address) }); afterEach(function () { @@ -105,6 +106,12 @@ describe('Test function: estimateFee', function () { describe('when request param validation pass', function () { beforeEach(async function () { apiParams.requestParams = Object.assign({}, requestObject); + sandbox.stub(utils, 'getKeysFromAddress').resolves({ + privateKey: 'pk', + publicKey: account2.publicKey, + addressIndex: account2.addressIndex, + derivationPath: `m / bip32:1' / bip32:1' / bip32:1' / bip32:1'`, + }); }); afterEach(async function () { @@ -112,11 +119,10 @@ describe('Test function: estimateFee', function () { }); describe('when account require upgrade', function () { - let getCorrectContractAddressStub: sinon.SinonStub; + let validateAccountRequireUpgradeOrDeployStup: sinon.SinonStub; beforeEach(async function () { - getCorrectContractAddressStub = sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: true, deployRequired: false }); + validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError("Upgrade Required")) }); it('should throw error if upgrade required', async function () { @@ -126,11 +132,13 @@ describe('Test function: estimateFee', function () { } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, + account2.address, account2.publicKey, ); expect(result).to.be.an('Error'); + expect(result.message).to.equal('Upgrade Required'); } }); }); @@ -150,7 +158,8 @@ describe('Test function: estimateFee', function () { describe('when account is deployed', function () { beforeEach(async function () { estimateFeeBulkStub = sandbox.stub(utils, 'estimateFeeBulk'); - sandbox.stub(utils, 'isAccountDeployed').resolves(true); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .resolves(null); }); it('should estimate the fee correctly', async function () { @@ -165,6 +174,8 @@ describe('Test function: estimateFee', function () { describe('when account is not deployed', function () { beforeEach(async function () { estimateFeeStub = sandbox.stub(utils, 'estimateFee'); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .resolves(null); sandbox.stub(utils, 'isAccountDeployed').resolves(false); }); diff --git a/packages/starknet-snap/test/src/extractPrivateKey.test.ts b/packages/starknet-snap/test/src/extractPrivateKey.test.ts index d99811a1..98b74ce7 100644 --- a/packages/starknet-snap/test/src/extractPrivateKey.test.ts +++ b/packages/starknet-snap/test/src/extractPrivateKey.test.ts @@ -87,17 +87,18 @@ describe('Test function: extractPrivateKey', function () { apiParams.requestParams = Object.assign({}, requestObject); }); - describe('when getCorrectContractAddress fail', function () { + describe('when validateAccountRequireUpgradeOrDeploy fail', function () { it('should throw error', async function () { - const getCorrectContractAddressStub = sandbox.stub(utils, 'getCorrectContractAddress').throws('network error'); + const validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').throws('network error'); let result; try { result = await extractPrivateKey(apiParams); } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, account1.publicKey, ); expect(result).to.be.an('Error'); @@ -106,11 +107,10 @@ describe('Test function: extractPrivateKey', function () { }); describe('when account require upgrade', function () { - let getCorrectContractAddressStub: sinon.SinonStub; + let validateAccountRequireUpgradeOrDeployStup: sinon.SinonStub; beforeEach(async function () { - getCorrectContractAddressStub = sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: true, deployRequired: false }); + validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError("Upgrade Required")) }); it('should throw error if upgrade required', async function () { @@ -120,8 +120,9 @@ describe('Test function: extractPrivateKey', function () { } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, account1.publicKey, ); expect(result).to.be.an('Error'); @@ -131,9 +132,8 @@ describe('Test function: extractPrivateKey', function () { describe('when account is not require upgrade', function () { beforeEach(async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .resolves(null); }); it('should get the private key of the specified user account correctly', async function () { diff --git a/packages/starknet-snap/test/src/extractPublicKey.test.ts b/packages/starknet-snap/test/src/extractPublicKey.test.ts index 76fae694..a81e1d22 100644 --- a/packages/starknet-snap/test/src/extractPublicKey.test.ts +++ b/packages/starknet-snap/test/src/extractPublicKey.test.ts @@ -86,17 +86,18 @@ describe('Test function: extractPublicKey', function () { apiParams.requestParams = Object.assign({}, requestObject); }); - describe('when require upgrade checking fail', function () { + describe('when validateAccountRequireUpgradeOrDeploy checking fail', function () { it('should throw error', async function () { - const getCorrectContractAddressStub = sandbox.stub(utils, 'getCorrectContractAddress').throws('network error'); + const validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').throws('network error'); let result; try { result = await extractPublicKey(apiParams); } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, account1.publicKey, ); expect(result).to.be.an('Error'); @@ -105,11 +106,10 @@ describe('Test function: extractPublicKey', function () { }); describe('when account require upgrade', function () { - let getCorrectContractAddressStub: sinon.SinonStub; + let validateAccountRequireUpgradeOrDeployStup: sinon.SinonStub; beforeEach(async function () { - getCorrectContractAddressStub = sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: true, deployRequired: false }); + validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError("Upgrade Required")) }); it('should throw error if upgrade required', async function () { @@ -119,8 +119,9 @@ describe('Test function: extractPublicKey', function () { } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, account1.publicKey, ); expect(result).to.be.an('Error'); @@ -130,9 +131,8 @@ describe('Test function: extractPublicKey', function () { describe('when account does not require upgrade', function () { beforeEach(async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .resolves(null); }); it('should get the public key of the specified user account correctly', async function () { diff --git a/packages/starknet-snap/test/src/verifySignedMessage.test.ts b/packages/starknet-snap/test/src/verifySignedMessage.test.ts index 55147e42..1a76a626 100644 --- a/packages/starknet-snap/test/src/verifySignedMessage.test.ts +++ b/packages/starknet-snap/test/src/verifySignedMessage.test.ts @@ -91,17 +91,18 @@ describe('Test function: verifySignedMessage', function () { apiParams.requestParams = Object.assign({}, requestObject); }); - describe('when require upgrade checking fail', function () { + describe('when validateAccountRequireUpgradeOrDeploy fail', function () { it('should throw error', async function () { - const getCorrectContractAddressStub = sandbox.stub(utils, 'getCorrectContractAddress').throws('network error'); + const validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').throws('network error'); let result; try { result = await verifySignedMessage(apiParams); } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, account1.publicKey, ); expect(result).to.be.an('Error'); @@ -110,11 +111,10 @@ describe('Test function: verifySignedMessage', function () { }); describe('when account require upgrade', function () { - let getCorrectContractAddressStub: sinon.SinonStub; + let validateAccountRequireUpgradeOrDeployStup: sinon.SinonStub; beforeEach(async function () { - getCorrectContractAddressStub = sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: true, deployRequired: false }); + validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError("Upgrade Required")) }); it('should throw error if upgrade required', async function () { @@ -124,8 +124,9 @@ describe('Test function: verifySignedMessage', function () { } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, account1.publicKey, ); expect(result).to.be.an('Error'); @@ -135,9 +136,8 @@ describe('Test function: verifySignedMessage', function () { describe('when account is not require upgrade', function () { beforeEach(async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .resolves(null); }); it('should verify a signed message from an user account correctly', async function () { diff --git a/packages/starknet-snap/test/utils/starknetUtils.test.ts b/packages/starknet-snap/test/utils/starknetUtils.test.ts index 7c37d24d..02c135d6 100644 --- a/packages/starknet-snap/test/utils/starknetUtils.test.ts +++ b/packages/starknet-snap/test/utils/starknetUtils.test.ts @@ -581,7 +581,7 @@ describe('Test function: getCorrectContractAddress', function () { }); it(`should return Cairo${CAIRO_VERSION_LEGACY} address with upgrade = true and deploy = true if balance`, async function () { sandbox.stub(utils, 'getVersion').rejects(new Error('Contract not found')); - sandbox.stub(utils, 'getBalance').callsFake(async () => getNonZeroBalanceResp[0]); + sandbox.stub(utils, 'isEthBalanceEmpty').resolves(false); getSignerStub = sandbox.stub(utils, 'getSigner').resolves(PK); getOwnerStub = sandbox.stub(utils, 'getOwner').resolves(PK); From 185c71b403a44487f10140d648390db929d90cfe Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Wed, 10 Jul 2024 12:08:30 +0200 Subject: [PATCH 14/16] refactor: upgrade / deploy requirement in get-starknet api --- packages/starknet-snap/src/declareContract.ts | 7 +-- packages/starknet-snap/src/estimateFee.ts | 2 +- packages/starknet-snap/src/executeTxn.ts | 25 ++++---- .../starknet-snap/src/extractPrivateKey.ts | 4 +- .../starknet-snap/src/extractPublicKey.ts | 2 +- .../src/signDeclareTransaction.ts | 25 ++++---- .../src/signDeployAccountTransaction.ts | 25 ++++---- packages/starknet-snap/src/signMessage.ts | 25 ++++---- packages/starknet-snap/src/signTransaction.ts | 29 +++++---- .../starknet-snap/src/utils/starknetUtils.ts | 29 ++++----- .../starknet-snap/src/verifySignedMessage.ts | 3 +- packages/starknet-snap/test/constants.test.ts | 1 - .../test/src/declareContract.test.ts | 27 +++++---- .../test/src/estimateFee.test.ts | 17 +++--- .../starknet-snap/test/src/executeTxn.test.ts | 44 ++++++-------- .../test/src/extractPrivateKey.test.ts | 18 +++--- .../test/src/extractPublicKey.test.ts | 18 +++--- .../test/src/signDeclareTransaction.test.ts | 57 +++++++++++------- .../src/signDeployAccountTransaction.test.ts | 57 +++++++++++------- .../test/src/signMessage.test.ts | 59 +++++++++++++++---- .../test/src/signTransaction.test.ts | 57 +++++++++++------- .../test/src/verifySignedMessage.test.ts | 18 +++--- .../test/utils/starknetUtils.test.ts | 1 - 23 files changed, 318 insertions(+), 232 deletions(-) diff --git a/packages/starknet-snap/src/declareContract.ts b/packages/starknet-snap/src/declareContract.ts index e06e28c6..7d5cf868 100644 --- a/packages/starknet-snap/src/declareContract.ts +++ b/packages/starknet-snap/src/declareContract.ts @@ -30,14 +30,13 @@ export async function declareContract(params: ApiParams) { try { await validateAccountRequireUpgradeOrDeploy(network, senderAddress, publicKey); } catch (e) { - //we can move this part to a snap utils function - if (e instanceof DeployRequiredError) { + if (e instanceof DeployRequiredError) { await showDeployRequestModal(wallet); - } + } if (e instanceof UpgradeRequiredError) { await showUpgradeRequestModal(wallet); } - throw e + throw e; } const snapComponents = getDeclareSnapTxt( diff --git a/packages/starknet-snap/src/estimateFee.ts b/packages/starknet-snap/src/estimateFee.ts index dfde25f2..7dbd55c0 100644 --- a/packages/starknet-snap/src/estimateFee.ts +++ b/packages/starknet-snap/src/estimateFee.ts @@ -1,6 +1,6 @@ import { toJson } from './utils/serializer'; import { Invocations, TransactionType } from 'starknet'; -import { getCorrectContractAddress, validateAccountRequireUpgradeOrDeploy, validateAndParseAddress } from '../src/utils/starknetUtils'; +import { validateAccountRequireUpgradeOrDeploy, validateAndParseAddress } from '../src/utils/starknetUtils'; import { ApiParams, EstimateFeeRequestParams } from './types/snapApi'; import { getNetworkFromChainId } from './utils/snapUtils'; import { diff --git a/packages/starknet-snap/src/executeTxn.ts b/packages/starknet-snap/src/executeTxn.ts index b3e4fd85..98b2483e 100644 --- a/packages/starknet-snap/src/executeTxn.ts +++ b/packages/starknet-snap/src/executeTxn.ts @@ -13,7 +13,9 @@ import { estimateFeeBulk, getAccContractAddressAndCallData, addFeesFromAllTransactions, - getCorrectContractAddress, + validateAccountRequireUpgradeOrDeploy, + DeployRequiredError, + UpgradeRequiredError, } from './utils/starknetUtils'; import { ApiParams, ExecuteTxnRequestParams } from './types/snapApi'; import { createAccount } from './createAccount'; @@ -33,17 +35,16 @@ export async function executeTxn(params: ApiParams) { addressIndex, } = await getKeysFromAddress(keyDeriver, network, state, senderAddress); - const { upgradeRequired, deployRequired, address } = await getCorrectContractAddress(network, publicKey); - - if (upgradeRequired && deployRequired) { - // Edge case force cairo0 deploy because non-zero balance - await showDeployRequestModal(wallet); - throw new Error(`Cairo 0 contract address ${address} balance is not empty, deploy required`); - } - - if (upgradeRequired && !deployRequired) { - await showUpgradeRequestModal(wallet); - throw new Error('Upgrade required'); + try { + await validateAccountRequireUpgradeOrDeploy(network, senderAddress, publicKey); + } catch (e) { + if (e instanceof DeployRequiredError) { + await showDeployRequestModal(wallet); + } + if (e instanceof UpgradeRequiredError) { + await showUpgradeRequestModal(wallet); + } + throw e; } const txnInvocationArray = Array.isArray(requestParamsObj.txnInvocation) diff --git a/packages/starknet-snap/src/extractPrivateKey.ts b/packages/starknet-snap/src/extractPrivateKey.ts index f557ec7e..1225cb97 100644 --- a/packages/starknet-snap/src/extractPrivateKey.ts +++ b/packages/starknet-snap/src/extractPrivateKey.ts @@ -1,5 +1,5 @@ import { toJson } from './utils/serializer'; -import { getCorrectContractAddress, validateAccountRequireUpgradeOrDeploy, validateAndParseAddress } from '../src/utils/starknetUtils'; +import { validateAccountRequireUpgradeOrDeploy, validateAndParseAddress } from '../src/utils/starknetUtils'; import { ApiParams, ExtractPrivateKeyRequestParams } from './types/snapApi'; import { getNetworkFromChainId } from './utils/snapUtils'; import { getKeysFromAddress } from './utils/starknetUtils'; @@ -23,7 +23,7 @@ export async function extractPrivateKey(params: ApiParams) { } const { privateKey: userPrivateKey, publicKey } = await getKeysFromAddress(keyDeriver, network, state, userAddress); - await validateAccountRequireUpgradeOrDeploy(network,userAddress, publicKey); + await validateAccountRequireUpgradeOrDeploy(network, userAddress, publicKey); const response = await wallet.request({ method: 'snap_dialog', diff --git a/packages/starknet-snap/src/extractPublicKey.ts b/packages/starknet-snap/src/extractPublicKey.ts index 7cbdc741..b3e42de1 100644 --- a/packages/starknet-snap/src/extractPublicKey.ts +++ b/packages/starknet-snap/src/extractPublicKey.ts @@ -1,6 +1,6 @@ import { toJson } from './utils/serializer'; import { constants, num } from 'starknet'; -import { validateAndParseAddress, getCorrectContractAddress, validateAccountRequireUpgradeOrDeploy } from '../src/utils/starknetUtils'; +import { validateAndParseAddress, validateAccountRequireUpgradeOrDeploy } from '../src/utils/starknetUtils'; import { ApiParams, ExtractPublicKeyRequestParams } from './types/snapApi'; import { getAccount, getNetworkFromChainId } from './utils/snapUtils'; import { getKeysFromAddress } from './utils/starknetUtils'; diff --git a/packages/starknet-snap/src/signDeclareTransaction.ts b/packages/starknet-snap/src/signDeclareTransaction.ts index 280931a4..cdfa0273 100644 --- a/packages/starknet-snap/src/signDeclareTransaction.ts +++ b/packages/starknet-snap/src/signDeclareTransaction.ts @@ -4,7 +4,9 @@ import { ApiParams, SignDeclareTransactionRequestParams } from './types/snapApi' import { getKeysFromAddress, signDeclareTransaction as signDeclareTransactionUtil, - getCorrectContractAddress, + validateAccountRequireUpgradeOrDeploy, + DeployRequiredError, + UpgradeRequiredError, } from './utils/starknetUtils'; import { getNetworkFromChainId, @@ -23,17 +25,16 @@ export async function signDeclareTransaction(params: ApiParams): Promise { const etherErc20TokenAddress = network.chainId === ETHER_SEPOLIA_TESTNET.chainId ? ETHER_SEPOLIA_TESTNET.address : ETHER_MAINNET.address; - return num.toBigInt( - (await getBalance(address, etherErc20TokenAddress, network)) ?? num.toBigInt(constants.ZERO), - ) <= maxFee; + return ( + num.toBigInt((await getBalance(address, etherErc20TokenAddress, network)) ?? num.toBigInt(constants.ZERO)) <= maxFee + ); }; - export const getTransactionStatus = async (transactionHash: num.BigNumberish, network: Network) => { const provider = getProvider(network); const receipt = (await provider.getTransactionReceipt(transactionHash)) as RpcV4GetTransactionReceiptResponse; @@ -723,7 +721,7 @@ export const isDeployRequired = async (network: Network, address: string, pubKey if (!err.message.includes('Contract not found')) { throw err; } - return !await(isEthBalanceEmpty(network, address)); + return !(await isEthBalanceEmpty(network, address)); } }; @@ -849,12 +847,11 @@ export const getCorrectContractAddress = async (network: Network, publicKey: str } // Here account is not deployed, proceed with edge case detection try { - if (await isEthBalanceEmpty(network, address, maxFee)){ - console.log("but not here") + if (await isEthBalanceEmpty(network, address, maxFee)) { + console.log('but not here'); address = contractAddress; logger.log(`getContractAddressByKey: no deployed contract found, fallback to cairo ${CAIRO_VERSION}`); - } - else{ + } else { upgradeRequired = true; deployRequired = true; logger.log( @@ -915,10 +912,10 @@ export const getStarkNameUtil = async (network: Network, userAddress: string) => return Account.getStarkName(provider, userAddress); }; -export const validateAccountRequireUpgradeOrDeploy = async (network: Network, address: string, pubKey: string, displayDialog = true) => { +export const validateAccountRequireUpgradeOrDeploy = async (network: Network, address: string, pubKey: string) => { if (await isUpgradeRequired(network, address)) { - throw new UpgradeRequiredError('Upgrade required') + throw new UpgradeRequiredError('Upgrade required'); } else if (!(await isDeployRequired(network, address, pubKey))) { - throw new DeployRequiredError(`Cairo 0 contract address ${address} balance is not empty, deploy required`) + throw new DeployRequiredError(`Cairo 0 contract address ${address} balance is not empty, deploy required`); } -}; \ No newline at end of file +}; diff --git a/packages/starknet-snap/src/verifySignedMessage.ts b/packages/starknet-snap/src/verifySignedMessage.ts index f05ba842..a7264b7e 100644 --- a/packages/starknet-snap/src/verifySignedMessage.ts +++ b/packages/starknet-snap/src/verifySignedMessage.ts @@ -4,7 +4,6 @@ import { verifyTypedDataMessageSignature, getFullPublicKeyPairFromPrivateKey, getKeysFromAddress, - getCorrectContractAddress, validateAccountRequireUpgradeOrDeploy, } from './utils/starknetUtils'; import { getNetworkFromChainId } from './utils/snapUtils'; @@ -47,7 +46,7 @@ export async function verifySignedMessage(params: ApiParams) { state, verifySignerAddress, ); - await validateAccountRequireUpgradeOrDeploy(network,verifySignerAddress, publicKey); + await validateAccountRequireUpgradeOrDeploy(network, verifySignerAddress, publicKey); const fullPublicKey = getFullPublicKeyPairFromPrivateKey(signerPrivateKey); diff --git a/packages/starknet-snap/test/constants.test.ts b/packages/starknet-snap/test/constants.test.ts index e1a464b2..465745a5 100644 --- a/packages/starknet-snap/test/constants.test.ts +++ b/packages/starknet-snap/test/constants.test.ts @@ -370,7 +370,6 @@ export const mainnetTxn1: Transaction = { }; export const getBalanceResp = ['0x0', '0x0']; -export const getNonZeroBalanceResp = ['0x100000', '0x0']; export const estimateDeployFeeResp = { overall_fee: num.toBigInt('0x0'), diff --git a/packages/starknet-snap/test/src/declareContract.test.ts b/packages/starknet-snap/test/src/declareContract.test.ts index 4c13a5b2..27403ecd 100644 --- a/packages/starknet-snap/test/src/declareContract.test.ts +++ b/packages/starknet-snap/test/src/declareContract.test.ts @@ -57,8 +57,9 @@ describe('Test function: declareContract', function () { }); it('should 1) throw an error and 2) show upgrade modal if account upgrade required', async function () { - const validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError("Upgrade Required")) + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError('Upgrade Required')); const showUpgradeRequestModalStub = sandbox.stub(snapsUtil, 'showUpgradeRequestModal').resolves(); let result; try { @@ -66,7 +67,7 @@ describe('Test function: declareContract', function () { } catch (err) { result = err; } finally { - expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, account1.address, account1.publicKey, @@ -77,8 +78,13 @@ describe('Test function: declareContract', function () { }); it('should 1) throw an error and 2) show deploy modal if account deployed required', async function () { - const validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.DeployRequiredError(`Cairo 0 contract address ${account1.address} balance is not empty, deploy required`)) + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws( + new utils.DeployRequiredError( + `Cairo 0 contract address ${account1.address} balance is not empty, deploy required`, + ), + ); const showDeployRequestModalStub = sandbox.stub(snapsUtil, 'showDeployRequestModal').resolves(); let result; try { @@ -86,7 +92,7 @@ describe('Test function: declareContract', function () { } catch (err) { result = err; } finally { - expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, account1.address, account1.publicKey, @@ -97,8 +103,7 @@ describe('Test function: declareContract', function () { }); it('should declareContract correctly', async function () { - sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .resolves(null); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); const declareContractStub = sandbox.stub(utils, 'declareContract').resolves({ transaction_hash: 'transaction_hash', class_hash: 'class_hash', @@ -126,8 +131,7 @@ describe('Test function: declareContract', function () { }); it('should throw error if declareContract fail', async function () { - sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .resolves(null); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); const declareContractStub = sandbox.stub(utils, 'declareContract').rejects('error'); const { privateKey } = await utils.getKeysFromAddress( apiParams.keyDeriver, @@ -154,8 +158,7 @@ describe('Test function: declareContract', function () { }); it('should return false if user rejected to sign the transaction', async function () { - sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .resolves(null); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); walletStub.rpcStubs.snap_dialog.resolves(false); const declareContractStub = sandbox.stub(utils, 'declareContract').resolves({ transaction_hash: 'transaction_hash', diff --git a/packages/starknet-snap/test/src/estimateFee.test.ts b/packages/starknet-snap/test/src/estimateFee.test.ts index ac017a44..118b6484 100644 --- a/packages/starknet-snap/test/src/estimateFee.test.ts +++ b/packages/starknet-snap/test/src/estimateFee.test.ts @@ -48,7 +48,7 @@ describe('Test function: estimateFee', function () { walletStub.rpcStubs.snap_getBip44Entropy.callsFake(getBip44EntropyStub); apiParams.keyDeriver = await getAddressKeyDeriver(walletStub); sandbox.stub(utils, 'callContract').resolves(getBalanceResp); - sandbox.stub(utils, 'getAccContractAddressAndCallDataLegacy').resolves(account2.address) + sandbox.stub(utils, 'getAccContractAddressAndCallDataLegacy').resolves(account2.address); }); afterEach(function () { @@ -119,10 +119,11 @@ describe('Test function: estimateFee', function () { }); describe('when account require upgrade', function () { - let validateAccountRequireUpgradeOrDeployStup: sinon.SinonStub; + let validateAccountRequireUpgradeOrDeployStub: sinon.SinonStub; beforeEach(async function () { - validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError("Upgrade Required")) + validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError('Upgrade Required')); }); it('should throw error if upgrade required', async function () { @@ -132,7 +133,7 @@ describe('Test function: estimateFee', function () { } catch (err) { result = err; } finally { - expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, account2.address, account2.publicKey, @@ -158,8 +159,7 @@ describe('Test function: estimateFee', function () { describe('when account is deployed', function () { beforeEach(async function () { estimateFeeBulkStub = sandbox.stub(utils, 'estimateFeeBulk'); - sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .resolves(null); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); }); it('should estimate the fee correctly', async function () { @@ -174,8 +174,7 @@ describe('Test function: estimateFee', function () { describe('when account is not deployed', function () { beforeEach(async function () { estimateFeeStub = sandbox.stub(utils, 'estimateFee'); - sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .resolves(null); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'isAccountDeployed').resolves(false); }); diff --git a/packages/starknet-snap/test/src/executeTxn.test.ts b/packages/starknet-snap/test/src/executeTxn.test.ts index 4acf3ab2..3e87f91f 100644 --- a/packages/starknet-snap/test/src/executeTxn.test.ts +++ b/packages/starknet-snap/test/src/executeTxn.test.ts @@ -81,9 +81,9 @@ describe('Test function: executeTxn', function () { }); it('should 1) throw an error and 2) show upgrade modal if account upgrade required', async function () { - const getCorrectContractAddressStub = sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: true, deployRequired: false }); + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError('Upgrade Required')); const showUpgradeRequestModalStub = sandbox.stub(snapsUtil, 'showUpgradeRequestModal').resolves(); let result; try { @@ -91,16 +91,20 @@ describe('Test function: executeTxn', function () { } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnce; + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnce; expect(showUpgradeRequestModalStub).to.have.been.calledOnce; expect(result).to.be.an('Error'); } }); it('should 1) throw an error and 2) show deploy modal if account deployed required', async function () { - const getCorrectContractAddressStub = sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: true, deployRequired: true }); + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws( + new utils.DeployRequiredError( + `Cairo 0 contract address ${account1.address} balance is not empty, deploy required`, + ), + ); const showDeployRequestModalStub = sandbox.stub(snapsUtil, 'showDeployRequestModal').resolves(); let result; try { @@ -108,16 +112,14 @@ describe('Test function: executeTxn', function () { } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnce; + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnce; expect(showDeployRequestModalStub).to.have.been.calledOnce; expect(result).to.be.an('Error'); } }); it('should executeTxn correctly and deploy an account', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'isAccountDeployed').resolves(false); const createAccountStub = sandbox.stub(createAccountUtils, 'createAccount').resolvesThis(); const stub = sandbox.stub(utils, 'executeTxn').resolves({ @@ -149,9 +151,7 @@ describe('Test function: executeTxn', function () { }); it('should executeTxn multiple and deploy an account', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: true }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'isAccountDeployed').resolves(false); const createAccountStub = sandbox.stub(createAccountUtils, 'createAccount').resolvesThis(); const stub = sandbox.stub(utils, 'executeTxn').resolves({ @@ -212,9 +212,7 @@ describe('Test function: executeTxn', function () { it('should executeTxn and not deploy an account', async function () { const createAccountStub = sandbox.stub(createAccountUtils, 'createAccount'); - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'isAccountDeployed').resolves(true); const stub = sandbox.stub(utils, 'executeTxn').resolves({ transaction_hash: 'transaction_hash', @@ -248,9 +246,7 @@ describe('Test function: executeTxn', function () { it('should executeTxn multiple and not deploy an account', async function () { const createAccountStub = sandbox.stub(createAccountUtils, 'createAccount'); - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'isAccountDeployed').resolves(true); const stub = sandbox.stub(utils, 'executeTxn').resolves({ transaction_hash: 'transaction_hash', @@ -309,9 +305,7 @@ describe('Test function: executeTxn', function () { }); it('should throw error if executeTxn fail', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'isAccountDeployed').resolves(true); const stub = sandbox.stub(utils, 'executeTxn').rejects('error'); const { privateKey } = await utils.getKeysFromAddress( @@ -344,9 +338,7 @@ describe('Test function: executeTxn', function () { }); it('should return false if user rejected to sign the transaction', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'isAccountDeployed').resolves(true); walletStub.rpcStubs.snap_dialog.resolves(false); const stub = sandbox.stub(utils, 'executeTxn').resolves({ diff --git a/packages/starknet-snap/test/src/extractPrivateKey.test.ts b/packages/starknet-snap/test/src/extractPrivateKey.test.ts index 98b74ce7..61741208 100644 --- a/packages/starknet-snap/test/src/extractPrivateKey.test.ts +++ b/packages/starknet-snap/test/src/extractPrivateKey.test.ts @@ -89,14 +89,16 @@ describe('Test function: extractPrivateKey', function () { describe('when validateAccountRequireUpgradeOrDeploy fail', function () { it('should throw error', async function () { - const validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').throws('network error'); + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws('network error'); let result; try { result = await extractPrivateKey(apiParams); } catch (err) { result = err; } finally { - expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, account1.address, account1.publicKey, @@ -107,10 +109,11 @@ describe('Test function: extractPrivateKey', function () { }); describe('when account require upgrade', function () { - let validateAccountRequireUpgradeOrDeployStup: sinon.SinonStub; + let validateAccountRequireUpgradeOrDeployStub: sinon.SinonStub; beforeEach(async function () { - validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError("Upgrade Required")) + validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError('Upgrade Required')); }); it('should throw error if upgrade required', async function () { @@ -120,7 +123,7 @@ describe('Test function: extractPrivateKey', function () { } catch (err) { result = err; } finally { - expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, account1.address, account1.publicKey, @@ -132,8 +135,7 @@ describe('Test function: extractPrivateKey', function () { describe('when account is not require upgrade', function () { beforeEach(async function () { - sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .resolves(null); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); }); it('should get the private key of the specified user account correctly', async function () { diff --git a/packages/starknet-snap/test/src/extractPublicKey.test.ts b/packages/starknet-snap/test/src/extractPublicKey.test.ts index a81e1d22..4f45cd74 100644 --- a/packages/starknet-snap/test/src/extractPublicKey.test.ts +++ b/packages/starknet-snap/test/src/extractPublicKey.test.ts @@ -88,14 +88,16 @@ describe('Test function: extractPublicKey', function () { describe('when validateAccountRequireUpgradeOrDeploy checking fail', function () { it('should throw error', async function () { - const validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').throws('network error'); + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws('network error'); let result; try { result = await extractPublicKey(apiParams); } catch (err) { result = err; } finally { - expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, account1.address, account1.publicKey, @@ -106,10 +108,11 @@ describe('Test function: extractPublicKey', function () { }); describe('when account require upgrade', function () { - let validateAccountRequireUpgradeOrDeployStup: sinon.SinonStub; + let validateAccountRequireUpgradeOrDeployStub: sinon.SinonStub; beforeEach(async function () { - validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError("Upgrade Required")) + validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError('Upgrade Required')); }); it('should throw error if upgrade required', async function () { @@ -119,7 +122,7 @@ describe('Test function: extractPublicKey', function () { } catch (err) { result = err; } finally { - expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, account1.address, account1.publicKey, @@ -131,8 +134,7 @@ describe('Test function: extractPublicKey', function () { describe('when account does not require upgrade', function () { beforeEach(async function () { - sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .resolves(null); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); }); it('should get the public key of the specified user account correctly', async function () { diff --git a/packages/starknet-snap/test/src/signDeclareTransaction.test.ts b/packages/starknet-snap/test/src/signDeclareTransaction.test.ts index 97a757a5..1fc80e6d 100644 --- a/packages/starknet-snap/test/src/signDeclareTransaction.test.ts +++ b/packages/starknet-snap/test/src/signDeclareTransaction.test.ts @@ -64,19 +64,17 @@ describe('Test function: signDeclareTransaction', function () { }); it('should sign a transaction from an user account correctly', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'signDeclareTransaction').resolves(signature3); const result = await signDeclareTransaction(apiParams); expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; expect(result).to.be.eql(signature3); }); - it('should 1) throw an error and 2) show upgrade modal if account deployed required', async function () { - const getCorrectContractAddressStub = sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: true, deployRequired: false }); + it('should 1) throw an error and 2) show upgrade modal if account upgrade required', async function () { + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError('Upgrade Required')); const showUpgradeRequestModalStub = sandbox.stub(snapsUtil, 'showUpgradeRequestModal').resolves(); let result; try { @@ -84,19 +82,44 @@ describe('Test function: signDeclareTransaction', function () { } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, account1.publicKey, ); expect(showUpgradeRequestModalStub).to.have.been.calledOnce; expect(result).to.be.an('Error'); + expect(result.message).to.equal('Upgrade Required'); + } + }); + + it('should 1) throw an error and 2) show deploy modal if account deployed required', async function () { + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws( + new utils.DeployRequiredError( + `Cairo 0 contract address ${account1.address} balance is not empty, deploy required`, + ), + ); + const showDeployRequestModalStub = sandbox.stub(snapsUtil, 'showDeployRequestModal').resolves(); + let result; + try { + result = await signDeclareTransaction(apiParams); + } catch (err) { + result = err; + } finally { + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( + STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, + account1.publicKey, + ); + expect(showDeployRequestModalStub).to.have.been.calledOnce; + expect(result).to.be.an('Error'); } }); it('should throw error if signDeclareTransaction fail', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'signDeclareTransaction').throws(new Error()); let result; try { @@ -110,9 +133,7 @@ describe('Test function: signDeclareTransaction', function () { }); it('should return false if user deny to sign the transaction', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); const stub = sandbox.stub(utils, 'signDeclareTransaction'); walletStub.rpcStubs.snap_dialog.resolves(false); @@ -123,9 +144,7 @@ describe('Test function: signDeclareTransaction', function () { }); it('should skip dialog if enableAuthorize is false', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'signDeclareTransaction').resolves(signature3); const paramsObject = apiParams.requestParams as SignDeclareTransactionRequestParams; paramsObject.enableAuthorize = false; @@ -136,9 +155,7 @@ describe('Test function: signDeclareTransaction', function () { }); it('should skip dialog if enableAuthorize is omit', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'signDeclareTransaction').resolves(signature3); const paramsObject = apiParams.requestParams as SignDeclareTransactionRequestParams; paramsObject.enableAuthorize = undefined; diff --git a/packages/starknet-snap/test/src/signDeployAccountTransaction.test.ts b/packages/starknet-snap/test/src/signDeployAccountTransaction.test.ts index 53d43b7a..e91970b9 100644 --- a/packages/starknet-snap/test/src/signDeployAccountTransaction.test.ts +++ b/packages/starknet-snap/test/src/signDeployAccountTransaction.test.ts @@ -66,19 +66,17 @@ describe('Test function: signDeployAccountTransaction', function () { }); it('should sign a transaction from an user account correctly', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'signDeployAccountTransaction').resolves(signature3); const result = await signDeployAccountTransaction(apiParams); expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; expect(result).to.be.eql(signature3); }); - it('should 1) throw an error and 2) show upgrade modal if account deployed required', async function () { - const getCorrectContractAddressStub = sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: true, deployRequired: false }); + it('should 1) throw an error and 2) show upgrade modal if account upgrade required', async function () { + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError('Upgrade Required')); const showUpgradeRequestModalStub = sandbox.stub(snapsUtil, 'showUpgradeRequestModal').resolves(); let result; try { @@ -86,19 +84,44 @@ describe('Test function: signDeployAccountTransaction', function () { } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, account1.publicKey, ); expect(showUpgradeRequestModalStub).to.have.been.calledOnce; expect(result).to.be.an('Error'); + expect(result.message).to.equal('Upgrade Required'); + } + }); + + it('should 1) throw an error and 2) show deploy modal if account deployed required', async function () { + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws( + new utils.DeployRequiredError( + `Cairo 0 contract address ${account1.address} balance is not empty, deploy required`, + ), + ); + const showDeployRequestModalStub = sandbox.stub(snapsUtil, 'showDeployRequestModal').resolves(); + let result; + try { + result = await signDeployAccountTransaction(apiParams); + } catch (err) { + result = err; + } finally { + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( + STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, + account1.publicKey, + ); + expect(showDeployRequestModalStub).to.have.been.calledOnce; + expect(result).to.be.an('Error'); } }); it('should throw error if signDeployAccountTransaction fail', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'signDeployAccountTransaction').throws(new Error()); let result; try { @@ -112,9 +135,7 @@ describe('Test function: signDeployAccountTransaction', function () { }); it('should return false if user deny to sign the transaction', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); const stub = sandbox.stub(utils, 'signDeployAccountTransaction'); walletStub.rpcStubs.snap_dialog.resolves(false); @@ -125,9 +146,7 @@ describe('Test function: signDeployAccountTransaction', function () { }); it('should skip dialog if enableAuthorize is false', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'signDeployAccountTransaction').resolves(signature3); const paramsObject = apiParams.requestParams as SignDeployAccountTransactionRequestParams; paramsObject.enableAuthorize = false; @@ -138,9 +157,7 @@ describe('Test function: signDeployAccountTransaction', function () { }); it('should skip dialog if enableAuthorize is omit', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'signDeployAccountTransaction').resolves(signature3); const paramsObject = apiParams.requestParams as SignDeployAccountTransactionRequestParams; paramsObject.enableAuthorize = undefined; diff --git a/packages/starknet-snap/test/src/signMessage.test.ts b/packages/starknet-snap/test/src/signMessage.test.ts index bb005076..9eba763a 100644 --- a/packages/starknet-snap/test/src/signMessage.test.ts +++ b/packages/starknet-snap/test/src/signMessage.test.ts @@ -102,9 +102,7 @@ describe('Test function: signMessage', function () { }); it('skip dialog if enableAuthorize is false or omit', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); const paramsObject = apiParams.requestParams as SignMessageRequestParams; paramsObject.enableAuthorize = false; @@ -120,15 +118,18 @@ describe('Test function: signMessage', function () { describe('when require upgrade checking fail', function () { it('should throw error', async function () { - const getCorrectContractAddressStub = sandbox.stub(utils, 'getCorrectContractAddress').throws('network error'); + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws('network error'); let result; try { result = await signMessage(apiParams); } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, account1.publicKey, ); expect(result).to.be.an('Error'); @@ -137,11 +138,11 @@ describe('Test function: signMessage', function () { }); describe('when account require upgrade', function () { - let getCorrectContractAddressStub: sinon.SinonStub; + let validateAccountRequireUpgradeOrDeployStub: sinon.SinonStub; beforeEach(async function () { - getCorrectContractAddressStub = sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: true, deployRequired: false }); + validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError('Upgrade Required')); }); it('should throw error if upgrade required', async function () { @@ -151,11 +152,45 @@ describe('Test function: signMessage', function () { } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, account1.publicKey, ); expect(result).to.be.an('Error'); + expect(result.message).to.equal('Upgrade Required'); + } + }); + }); + + describe('when account require deploy', function () { + let validateAccountRequireUpgradeOrDeployStub: sinon.SinonStub; + beforeEach(async function () { + validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws( + new utils.DeployRequiredError( + `Cairo 0 contract address ${account1.address} balance is not empty, deploy required`, + ), + ); + }); + + it('should throw error if deploy required', async function () { + let result; + try { + result = await signMessage(apiParams); + } catch (err) { + result = err; + } finally { + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( + STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, + account1.publicKey, + ); + expect(result).to.be.an('Error'); + expect(result.message).to.equal( + `Cairo 0 contract address ${account1.address} balance is not empty, deploy required`, + ); } }); }); @@ -166,9 +201,7 @@ describe('Test function: signMessage', function () { ...apiParams.requestParams, signerAddress: Cairo1Account1.address, }; - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); }); it('should sign a message from an user account correctly', async function () { diff --git a/packages/starknet-snap/test/src/signTransaction.test.ts b/packages/starknet-snap/test/src/signTransaction.test.ts index eaa2585e..92af4536 100644 --- a/packages/starknet-snap/test/src/signTransaction.test.ts +++ b/packages/starknet-snap/test/src/signTransaction.test.ts @@ -74,18 +74,16 @@ describe('Test function: signTransaction', function () { }); it('should sign a transaction from an user account correctly', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); const result = await signTransaction(apiParams); expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; expect(result).to.be.eql(signature3); }); - it('should 1) throw an error and 2) show upgrade modal if account deployed required', async function () { - const getCorrectContractAddressStub = sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: true, deployRequired: false }); + it('should 1) throw an error and 2) show upgrade modal if account upgrade required', async function () { + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError('Upgrade Required')); const showUpgradeRequestModalStub = sandbox.stub(snapsUtil, 'showUpgradeRequestModal').resolves(); let result; try { @@ -93,19 +91,44 @@ describe('Test function: signTransaction', function () { } catch (err) { result = err; } finally { - expect(getCorrectContractAddressStub).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, account1.publicKey, ); expect(showUpgradeRequestModalStub).to.have.been.calledOnce; expect(result).to.be.an('Error'); + expect(result.message).to.equal('Upgrade Required'); + } + }); + + it('should 1) throw an error and 2) show deploy modal if account deployed required', async function () { + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws( + new utils.DeployRequiredError( + `Cairo 0 contract address ${account1.address} balance is not empty, deploy required`, + ), + ); + const showDeployRequestModalStub = sandbox.stub(snapsUtil, 'showDeployRequestModal').resolves(); + let result; + try { + result = await signTransaction(apiParams); + } catch (err) { + result = err; + } finally { + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( + STARKNET_SEPOLIA_TESTNET_NETWORK, + account1.address, + account1.publicKey, + ); + expect(showDeployRequestModalStub).to.have.been.calledOnce; + expect(result).to.be.an('Error'); } }); it('should throw error if signTransaction fail', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); sandbox.stub(utils, 'signTransactions').throws(new Error()); let result; try { @@ -119,9 +142,7 @@ describe('Test function: signTransaction', function () { }); it('should return false if user deny to sign the transaction', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); const stub = sandbox.stub(utils, 'signTransactions'); walletStub.rpcStubs.snap_dialog.resolves(false); @@ -132,9 +153,7 @@ describe('Test function: signTransaction', function () { }); it('should skip dialog if enableAuthorize is false', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); const paramsObject = apiParams.requestParams as SignTransactionRequestParams; paramsObject.enableAuthorize = false; const result = await signTransaction(apiParams); @@ -144,9 +163,7 @@ describe('Test function: signTransaction', function () { }); it('should skip dialog if enableAuthorize is omit', async function () { - sandbox - .stub(utils, 'getCorrectContractAddress') - .resolves({ address: '', signerPubKey: '', upgradeRequired: false, deployRequired: false }); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); const paramsObject = apiParams.requestParams as SignTransactionRequestParams; paramsObject.enableAuthorize = undefined; const result = await signTransaction(apiParams); diff --git a/packages/starknet-snap/test/src/verifySignedMessage.test.ts b/packages/starknet-snap/test/src/verifySignedMessage.test.ts index 1a76a626..0d8ed090 100644 --- a/packages/starknet-snap/test/src/verifySignedMessage.test.ts +++ b/packages/starknet-snap/test/src/verifySignedMessage.test.ts @@ -93,14 +93,16 @@ describe('Test function: verifySignedMessage', function () { describe('when validateAccountRequireUpgradeOrDeploy fail', function () { it('should throw error', async function () { - const validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').throws('network error'); + const validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws('network error'); let result; try { result = await verifySignedMessage(apiParams); } catch (err) { result = err; } finally { - expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, account1.address, account1.publicKey, @@ -111,10 +113,11 @@ describe('Test function: verifySignedMessage', function () { }); describe('when account require upgrade', function () { - let validateAccountRequireUpgradeOrDeployStup: sinon.SinonStub; + let validateAccountRequireUpgradeOrDeployStub: sinon.SinonStub; beforeEach(async function () { - validateAccountRequireUpgradeOrDeployStup = sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError("Upgrade Required")) + validateAccountRequireUpgradeOrDeployStub = sandbox + .stub(utils, 'validateAccountRequireUpgradeOrDeploy') + .throws(new utils.UpgradeRequiredError('Upgrade Required')); }); it('should throw error if upgrade required', async function () { @@ -124,7 +127,7 @@ describe('Test function: verifySignedMessage', function () { } catch (err) { result = err; } finally { - expect(validateAccountRequireUpgradeOrDeployStup).to.have.been.calledOnceWith( + expect(validateAccountRequireUpgradeOrDeployStub).to.have.been.calledOnceWith( STARKNET_SEPOLIA_TESTNET_NETWORK, account1.address, account1.publicKey, @@ -136,8 +139,7 @@ describe('Test function: verifySignedMessage', function () { describe('when account is not require upgrade', function () { beforeEach(async function () { - sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .resolves(null); + sandbox.stub(utils, 'validateAccountRequireUpgradeOrDeploy').resolves(null); }); it('should verify a signed message from an user account correctly', async function () { diff --git a/packages/starknet-snap/test/utils/starknetUtils.test.ts b/packages/starknet-snap/test/utils/starknetUtils.test.ts index 02c135d6..b56c0e98 100644 --- a/packages/starknet-snap/test/utils/starknetUtils.test.ts +++ b/packages/starknet-snap/test/utils/starknetUtils.test.ts @@ -13,7 +13,6 @@ import { account2, account3, getBalanceResp, - getNonZeroBalanceResp, } from '../constants.test'; import { SnapState } from '../../src/types/snapState'; import { Calldata, num, Account, Provider, GetTransactionReceiptResponse } from 'starknet'; From 21d817269a24a052d89ca7ea45e6dd2a8786811e Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Wed, 10 Jul 2024 12:12:04 +0200 Subject: [PATCH 15/16] chore: lint + prettier --- packages/starknet-snap/src/declareContract.ts | 2 +- packages/starknet-snap/src/executeTxn.ts | 2 +- packages/starknet-snap/src/signDeclareTransaction.ts | 2 +- packages/starknet-snap/src/signDeployAccountTransaction.ts | 2 +- packages/starknet-snap/src/signMessage.ts | 2 +- packages/starknet-snap/src/signTransaction.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/starknet-snap/src/declareContract.ts b/packages/starknet-snap/src/declareContract.ts index 7d5cf868..99501362 100644 --- a/packages/starknet-snap/src/declareContract.ts +++ b/packages/starknet-snap/src/declareContract.ts @@ -30,7 +30,7 @@ export async function declareContract(params: ApiParams) { try { await validateAccountRequireUpgradeOrDeploy(network, senderAddress, publicKey); } catch (e) { - if (e instanceof DeployRequiredError) { + if (e instanceof DeployRequiredError) { await showDeployRequestModal(wallet); } if (e instanceof UpgradeRequiredError) { diff --git a/packages/starknet-snap/src/executeTxn.ts b/packages/starknet-snap/src/executeTxn.ts index 98b2483e..a19a1b50 100644 --- a/packages/starknet-snap/src/executeTxn.ts +++ b/packages/starknet-snap/src/executeTxn.ts @@ -38,7 +38,7 @@ export async function executeTxn(params: ApiParams) { try { await validateAccountRequireUpgradeOrDeploy(network, senderAddress, publicKey); } catch (e) { - if (e instanceof DeployRequiredError) { + if (e instanceof DeployRequiredError) { await showDeployRequestModal(wallet); } if (e instanceof UpgradeRequiredError) { diff --git a/packages/starknet-snap/src/signDeclareTransaction.ts b/packages/starknet-snap/src/signDeclareTransaction.ts index cdfa0273..c9f26ff1 100644 --- a/packages/starknet-snap/src/signDeclareTransaction.ts +++ b/packages/starknet-snap/src/signDeclareTransaction.ts @@ -28,7 +28,7 @@ export async function signDeclareTransaction(params: ApiParams): Promise Date: Wed, 10 Jul 2024 13:22:45 +0200 Subject: [PATCH 16/16] refactor: added exceptions.ts file and utilities in snapUtils --- packages/starknet-snap/src/declareContract.ts | 16 ++-------- packages/starknet-snap/src/executeTxn.ts | 12 ++----- .../starknet-snap/src/extractPublicKey.ts | 1 + .../src/signDeclareTransaction.ts | 16 ++-------- .../src/signDeployAccountTransaction.ts | 16 ++-------- packages/starknet-snap/src/signMessage.ts | 32 ++++++------------- packages/starknet-snap/src/signTransaction.ts | 22 ++----------- .../starknet-snap/src/utils/exceptions.ts | 11 +++++++ packages/starknet-snap/src/utils/snapUtils.ts | 9 ++++++ .../starknet-snap/src/utils/starknetUtils.ts | 15 +-------- .../test/src/declareContract.test.ts | 15 ++++++--- .../test/src/estimateFee.test.ts | 3 +- .../starknet-snap/test/src/executeTxn.test.ts | 14 +++++--- .../test/src/extractPrivateKey.test.ts | 3 +- .../test/src/extractPublicKey.test.ts | 3 +- .../test/src/signDeclareTransaction.test.ts | 14 +++++--- .../src/signDeployAccountTransaction.test.ts | 14 +++++--- .../test/src/signMessage.test.ts | 5 +-- .../test/src/signTransaction.test.ts | 14 +++++--- .../test/src/verifySignedMessage.test.ts | 3 +- 20 files changed, 105 insertions(+), 133 deletions(-) create mode 100644 packages/starknet-snap/src/utils/exceptions.ts diff --git a/packages/starknet-snap/src/declareContract.ts b/packages/starknet-snap/src/declareContract.ts index 99501362..683eee6a 100644 --- a/packages/starknet-snap/src/declareContract.ts +++ b/packages/starknet-snap/src/declareContract.ts @@ -1,17 +1,10 @@ import { toJson } from './utils/serializer'; import { ApiParams, DeclareContractRequestParams } from './types/snapApi'; -import { - getNetworkFromChainId, - getDeclareSnapTxt, - showUpgradeRequestModal, - showDeployRequestModal, -} from './utils/snapUtils'; +import { getNetworkFromChainId, getDeclareSnapTxt, showAccountRequireUpgradeOrDeployModal } from './utils/snapUtils'; import { getKeysFromAddress, declareContract as declareContractUtil, validateAccountRequireUpgradeOrDeploy, - DeployRequiredError, - UpgradeRequiredError, } from './utils/starknetUtils'; import { heading, panel, DialogType } from '@metamask/snaps-sdk'; import { logger } from './utils/logger'; @@ -30,12 +23,7 @@ export async function declareContract(params: ApiParams) { try { await validateAccountRequireUpgradeOrDeploy(network, senderAddress, publicKey); } catch (e) { - if (e instanceof DeployRequiredError) { - await showDeployRequestModal(wallet); - } - if (e instanceof UpgradeRequiredError) { - await showUpgradeRequestModal(wallet); - } + await showAccountRequireUpgradeOrDeployModal(wallet, e); throw e; } diff --git a/packages/starknet-snap/src/executeTxn.ts b/packages/starknet-snap/src/executeTxn.ts index a19a1b50..946df03b 100644 --- a/packages/starknet-snap/src/executeTxn.ts +++ b/packages/starknet-snap/src/executeTxn.ts @@ -3,8 +3,7 @@ import { getNetworkFromChainId, getTxnSnapTxt, addDialogTxt, - showUpgradeRequestModal, - showDeployRequestModal, + showAccountRequireUpgradeOrDeployModal, } from './utils/snapUtils'; import { getKeysFromAddress, @@ -14,8 +13,6 @@ import { getAccContractAddressAndCallData, addFeesFromAllTransactions, validateAccountRequireUpgradeOrDeploy, - DeployRequiredError, - UpgradeRequiredError, } from './utils/starknetUtils'; import { ApiParams, ExecuteTxnRequestParams } from './types/snapApi'; import { createAccount } from './createAccount'; @@ -38,12 +35,7 @@ export async function executeTxn(params: ApiParams) { try { await validateAccountRequireUpgradeOrDeploy(network, senderAddress, publicKey); } catch (e) { - if (e instanceof DeployRequiredError) { - await showDeployRequestModal(wallet); - } - if (e instanceof UpgradeRequiredError) { - await showUpgradeRequestModal(wallet); - } + await showAccountRequireUpgradeOrDeployModal(wallet, e); throw e; } diff --git a/packages/starknet-snap/src/extractPublicKey.ts b/packages/starknet-snap/src/extractPublicKey.ts index b3e42de1..7909b564 100644 --- a/packages/starknet-snap/src/extractPublicKey.ts +++ b/packages/starknet-snap/src/extractPublicKey.ts @@ -26,6 +26,7 @@ export async function extractPublicKey(params: ApiParams) { throw new Error(`The given user address is invalid: ${requestParamsObj.userAddress}`); } + // [TODO] logic below is redundant, getKeysFromAddress is doing the same const { publicKey } = await getKeysFromAddress(keyDeriver, network, state, userAddress); await validateAccountRequireUpgradeOrDeploy(network, userAddress, publicKey); diff --git a/packages/starknet-snap/src/signDeclareTransaction.ts b/packages/starknet-snap/src/signDeclareTransaction.ts index c9f26ff1..e2261e84 100644 --- a/packages/starknet-snap/src/signDeclareTransaction.ts +++ b/packages/starknet-snap/src/signDeclareTransaction.ts @@ -5,15 +5,8 @@ import { getKeysFromAddress, signDeclareTransaction as signDeclareTransactionUtil, validateAccountRequireUpgradeOrDeploy, - DeployRequiredError, - UpgradeRequiredError, } from './utils/starknetUtils'; -import { - getNetworkFromChainId, - getSignTxnTxt, - showDeployRequestModal, - showUpgradeRequestModal, -} from './utils/snapUtils'; +import { getNetworkFromChainId, getSignTxnTxt, showAccountRequireUpgradeOrDeployModal } from './utils/snapUtils'; import { heading, panel, DialogType } from '@metamask/snaps-sdk'; import { logger } from './utils/logger'; @@ -28,12 +21,7 @@ export async function signDeclareTransaction(params: ApiParams): Promise { return (callDataStr ?? '') @@ -835,7 +824,6 @@ export const getCorrectContractAddress = async (network: Network, publicKey: str address = contractAddressLegacy; const version = await getVersion(contractAddressLegacy, network); upgradeRequired = isGTEMinVersion(hexToString(version)) ? false : true; - console.log(`upgradeRequired: ${upgradeRequired}`); pk = await getContractOwner( contractAddressLegacy, network, @@ -848,7 +836,6 @@ export const getCorrectContractAddress = async (network: Network, publicKey: str // Here account is not deployed, proceed with edge case detection try { if (await isEthBalanceEmpty(network, address, maxFee)) { - console.log('but not here'); address = contractAddress; logger.log(`getContractAddressByKey: no deployed contract found, fallback to cairo ${CAIRO_VERSION}`); } else { diff --git a/packages/starknet-snap/test/src/declareContract.test.ts b/packages/starknet-snap/test/src/declareContract.test.ts index 27403ecd..36ba7374 100644 --- a/packages/starknet-snap/test/src/declareContract.test.ts +++ b/packages/starknet-snap/test/src/declareContract.test.ts @@ -11,6 +11,7 @@ import { createAccountProxyTxn, getBip44EntropyStub, account1 } from '../constan import { getAddressKeyDeriver } from '../../src/utils/keyPair'; import { Mutex } from 'async-mutex'; import { ApiParams, DeclareContractRequestParams } from '../../src/types/snapApi'; +import { DeployRequiredError, UpgradeRequiredError } from '../../src/utils/exceptions'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); @@ -49,6 +50,14 @@ describe('Test function: declareContract', function () { sandbox.useFakeTimers(createAccountProxyTxn.timestamp); walletStub.rpcStubs.snap_dialog.resolves(true); walletStub.rpcStubs.snap_manageState.resolves(state); + + sandbox.stub(snapsUtil, 'showAccountRequireUpgradeOrDeployModal').callsFake(async (wallet, e) => { + if (e instanceof DeployRequiredError) { + await snapsUtil.showDeployRequestModal(wallet); + } else if (e instanceof UpgradeRequiredError) { + await snapsUtil.showUpgradeRequestModal(wallet); + } + }); }); afterEach(function () { @@ -59,7 +68,7 @@ describe('Test function: declareContract', function () { it('should 1) throw an error and 2) show upgrade modal if account upgrade required', async function () { const validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError('Upgrade Required')); + .throws(new UpgradeRequiredError('Upgrade Required')); const showUpgradeRequestModalStub = sandbox.stub(snapsUtil, 'showUpgradeRequestModal').resolves(); let result; try { @@ -81,9 +90,7 @@ describe('Test function: declareContract', function () { const validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') .throws( - new utils.DeployRequiredError( - `Cairo 0 contract address ${account1.address} balance is not empty, deploy required`, - ), + new DeployRequiredError(`Cairo 0 contract address ${account1.address} balance is not empty, deploy required`), ); const showDeployRequestModalStub = sandbox.stub(snapsUtil, 'showDeployRequestModal').resolves(); let result; diff --git a/packages/starknet-snap/test/src/estimateFee.test.ts b/packages/starknet-snap/test/src/estimateFee.test.ts index 118b6484..1e736080 100644 --- a/packages/starknet-snap/test/src/estimateFee.test.ts +++ b/packages/starknet-snap/test/src/estimateFee.test.ts @@ -18,6 +18,7 @@ import { import { Mutex } from 'async-mutex'; import { ApiParams, EstimateFeeRequestParams } from '../../src/types/snapApi'; import { TransactionType } from 'starknet'; +import { UpgradeRequiredError } from '../../src/utils/exceptions'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); @@ -123,7 +124,7 @@ describe('Test function: estimateFee', function () { beforeEach(async function () { validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError('Upgrade Required')); + .throws(new UpgradeRequiredError('Upgrade Required')); }); it('should throw error if upgrade required', async function () { diff --git a/packages/starknet-snap/test/src/executeTxn.test.ts b/packages/starknet-snap/test/src/executeTxn.test.ts index 3e87f91f..9510a734 100644 --- a/packages/starknet-snap/test/src/executeTxn.test.ts +++ b/packages/starknet-snap/test/src/executeTxn.test.ts @@ -19,6 +19,7 @@ import { getAddressKeyDeriver } from '../../src/utils/keyPair'; import { Mutex } from 'async-mutex'; import { ApiParams, ExecuteTxnRequestParams } from '../../src/types/snapApi'; import { GetTransactionReceiptResponse } from 'starknet'; +import { DeployRequiredError, UpgradeRequiredError } from '../../src/utils/exceptions'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); @@ -72,6 +73,13 @@ describe('Test function: executeTxn', function () { walletStub.rpcStubs.snap_dialog.resolves(true); walletStub.rpcStubs.snap_manageState.resolves(state); sandbox.stub(utils, 'waitForTransaction').resolves({} as unknown as GetTransactionReceiptResponse); + sandbox.stub(snapsUtil, 'showAccountRequireUpgradeOrDeployModal').callsFake(async (wallet, e) => { + if (e instanceof DeployRequiredError) { + await snapsUtil.showDeployRequestModal(wallet); + } else if (e instanceof UpgradeRequiredError) { + await snapsUtil.showUpgradeRequestModal(wallet); + } + }); }); afterEach(function () { @@ -83,7 +91,7 @@ describe('Test function: executeTxn', function () { it('should 1) throw an error and 2) show upgrade modal if account upgrade required', async function () { const validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError('Upgrade Required')); + .throws(new UpgradeRequiredError('Upgrade Required')); const showUpgradeRequestModalStub = sandbox.stub(snapsUtil, 'showUpgradeRequestModal').resolves(); let result; try { @@ -101,9 +109,7 @@ describe('Test function: executeTxn', function () { const validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') .throws( - new utils.DeployRequiredError( - `Cairo 0 contract address ${account1.address} balance is not empty, deploy required`, - ), + new DeployRequiredError(`Cairo 0 contract address ${account1.address} balance is not empty, deploy required`), ); const showDeployRequestModalStub = sandbox.stub(snapsUtil, 'showDeployRequestModal').resolves(); let result; diff --git a/packages/starknet-snap/test/src/extractPrivateKey.test.ts b/packages/starknet-snap/test/src/extractPrivateKey.test.ts index 61741208..ea29b98e 100644 --- a/packages/starknet-snap/test/src/extractPrivateKey.test.ts +++ b/packages/starknet-snap/test/src/extractPrivateKey.test.ts @@ -10,6 +10,7 @@ import { getAddressKeyDeriver } from '../../src/utils/keyPair'; import * as utils from '../../src/utils/starknetUtils'; import { Mutex } from 'async-mutex'; import { ApiParams, ExtractPrivateKeyRequestParams } from '../../src/types/snapApi'; +import { UpgradeRequiredError } from '../../src/utils/exceptions'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); @@ -113,7 +114,7 @@ describe('Test function: extractPrivateKey', function () { beforeEach(async function () { validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError('Upgrade Required')); + .throws(new UpgradeRequiredError('Upgrade Required')); }); it('should throw error if upgrade required', async function () { diff --git a/packages/starknet-snap/test/src/extractPublicKey.test.ts b/packages/starknet-snap/test/src/extractPublicKey.test.ts index 4f45cd74..a79df0f4 100644 --- a/packages/starknet-snap/test/src/extractPublicKey.test.ts +++ b/packages/starknet-snap/test/src/extractPublicKey.test.ts @@ -10,6 +10,7 @@ import { getAddressKeyDeriver } from '../../src/utils/keyPair'; import * as utils from '../../src/utils/starknetUtils'; import { Mutex } from 'async-mutex'; import { ApiParams, ExtractPublicKeyRequestParams } from '../../src/types/snapApi'; +import { UpgradeRequiredError } from '../../src/utils/exceptions'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); @@ -112,7 +113,7 @@ describe('Test function: extractPublicKey', function () { beforeEach(async function () { validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError('Upgrade Required')); + .throws(new UpgradeRequiredError('Upgrade Required')); }); it('should throw error if upgrade required', async function () { diff --git a/packages/starknet-snap/test/src/signDeclareTransaction.test.ts b/packages/starknet-snap/test/src/signDeclareTransaction.test.ts index 1fc80e6d..8b50a32e 100644 --- a/packages/starknet-snap/test/src/signDeclareTransaction.test.ts +++ b/packages/starknet-snap/test/src/signDeclareTransaction.test.ts @@ -12,6 +12,7 @@ import { ApiParams, SignDeclareTransactionRequestParams } from '../../src/types/ import { DeclareSignerDetails, constants } from 'starknet'; import * as utils from '../../src/utils/starknetUtils'; import * as snapsUtil from '../../src/utils/snapUtils'; +import { DeployRequiredError, UpgradeRequiredError } from '../../src/utils/exceptions'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); @@ -55,6 +56,13 @@ describe('Test function: signDeclareTransaction', function () { sandbox.useFakeTimers(createAccountProxyTxn.timestamp); walletStub.rpcStubs.snap_dialog.resolves(true); walletStub.rpcStubs.snap_manageState.resolves(state); + sandbox.stub(snapsUtil, 'showAccountRequireUpgradeOrDeployModal').callsFake(async (wallet, e) => { + if (e instanceof DeployRequiredError) { + await snapsUtil.showDeployRequestModal(wallet); + } else if (e instanceof UpgradeRequiredError) { + await snapsUtil.showUpgradeRequestModal(wallet); + } + }); }); afterEach(function () { @@ -74,7 +82,7 @@ describe('Test function: signDeclareTransaction', function () { it('should 1) throw an error and 2) show upgrade modal if account upgrade required', async function () { const validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError('Upgrade Required')); + .throws(new UpgradeRequiredError('Upgrade Required')); const showUpgradeRequestModalStub = sandbox.stub(snapsUtil, 'showUpgradeRequestModal').resolves(); let result; try { @@ -97,9 +105,7 @@ describe('Test function: signDeclareTransaction', function () { const validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') .throws( - new utils.DeployRequiredError( - `Cairo 0 contract address ${account1.address} balance is not empty, deploy required`, - ), + new DeployRequiredError(`Cairo 0 contract address ${account1.address} balance is not empty, deploy required`), ); const showDeployRequestModalStub = sandbox.stub(snapsUtil, 'showDeployRequestModal').resolves(); let result; diff --git a/packages/starknet-snap/test/src/signDeployAccountTransaction.test.ts b/packages/starknet-snap/test/src/signDeployAccountTransaction.test.ts index e91970b9..66f307cd 100644 --- a/packages/starknet-snap/test/src/signDeployAccountTransaction.test.ts +++ b/packages/starknet-snap/test/src/signDeployAccountTransaction.test.ts @@ -12,6 +12,7 @@ import { ApiParams, SignDeployAccountTransactionRequestParams } from '../../src/ import { DeployAccountSignerDetails, constants } from 'starknet'; import * as utils from '../../src/utils/starknetUtils'; import * as snapsUtil from '../../src/utils/snapUtils'; +import { DeployRequiredError, UpgradeRequiredError } from '../../src/utils/exceptions'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); @@ -57,6 +58,13 @@ describe('Test function: signDeployAccountTransaction', function () { sandbox.useFakeTimers(createAccountProxyTxn.timestamp); walletStub.rpcStubs.snap_dialog.resolves(true); walletStub.rpcStubs.snap_manageState.resolves(state); + sandbox.stub(snapsUtil, 'showAccountRequireUpgradeOrDeployModal').callsFake(async (wallet, e) => { + if (e instanceof DeployRequiredError) { + await snapsUtil.showDeployRequestModal(wallet); + } else if (e instanceof UpgradeRequiredError) { + await snapsUtil.showUpgradeRequestModal(wallet); + } + }); }); afterEach(function () { @@ -76,7 +84,7 @@ describe('Test function: signDeployAccountTransaction', function () { it('should 1) throw an error and 2) show upgrade modal if account upgrade required', async function () { const validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError('Upgrade Required')); + .throws(new UpgradeRequiredError('Upgrade Required')); const showUpgradeRequestModalStub = sandbox.stub(snapsUtil, 'showUpgradeRequestModal').resolves(); let result; try { @@ -99,9 +107,7 @@ describe('Test function: signDeployAccountTransaction', function () { const validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') .throws( - new utils.DeployRequiredError( - `Cairo 0 contract address ${account1.address} balance is not empty, deploy required`, - ), + new DeployRequiredError(`Cairo 0 contract address ${account1.address} balance is not empty, deploy required`), ); const showDeployRequestModalStub = sandbox.stub(snapsUtil, 'showDeployRequestModal').resolves(); let result; diff --git a/packages/starknet-snap/test/src/signMessage.test.ts b/packages/starknet-snap/test/src/signMessage.test.ts index 9eba763a..b1dec77a 100644 --- a/packages/starknet-snap/test/src/signMessage.test.ts +++ b/packages/starknet-snap/test/src/signMessage.test.ts @@ -18,6 +18,7 @@ import { getAddressKeyDeriver } from '../../src/utils/keyPair'; import * as utils from '../../src/utils/starknetUtils'; import { Mutex } from 'async-mutex'; import { ApiParams, SignMessageRequestParams } from '../../src/types/snapApi'; +import { DeployRequiredError, UpgradeRequiredError } from '../../src/utils/exceptions'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); @@ -142,7 +143,7 @@ describe('Test function: signMessage', function () { beforeEach(async function () { validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError('Upgrade Required')); + .throws(new UpgradeRequiredError('Upgrade Required')); }); it('should throw error if upgrade required', async function () { @@ -169,7 +170,7 @@ describe('Test function: signMessage', function () { validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') .throws( - new utils.DeployRequiredError( + new DeployRequiredError( `Cairo 0 contract address ${account1.address} balance is not empty, deploy required`, ), ); diff --git a/packages/starknet-snap/test/src/signTransaction.test.ts b/packages/starknet-snap/test/src/signTransaction.test.ts index 92af4536..05e3d5b9 100644 --- a/packages/starknet-snap/test/src/signTransaction.test.ts +++ b/packages/starknet-snap/test/src/signTransaction.test.ts @@ -12,6 +12,7 @@ import { ApiParams, SignTransactionRequestParams } from '../../src/types/snapApi import { constants } from 'starknet'; import * as utils from '../../src/utils/starknetUtils'; import * as snapsUtil from '../../src/utils/snapUtils'; +import { DeployRequiredError, UpgradeRequiredError } from '../../src/utils/exceptions'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); @@ -65,6 +66,13 @@ describe('Test function: signTransaction', function () { sandbox.useFakeTimers(createAccountProxyTxn.timestamp); walletStub.rpcStubs.snap_dialog.resolves(true); walletStub.rpcStubs.snap_manageState.resolves(state); + sandbox.stub(snapsUtil, 'showAccountRequireUpgradeOrDeployModal').callsFake(async (wallet, e) => { + if (e instanceof DeployRequiredError) { + await snapsUtil.showDeployRequestModal(wallet); + } else if (e instanceof UpgradeRequiredError) { + await snapsUtil.showUpgradeRequestModal(wallet); + } + }); }); afterEach(function () { @@ -83,7 +91,7 @@ describe('Test function: signTransaction', function () { it('should 1) throw an error and 2) show upgrade modal if account upgrade required', async function () { const validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError('Upgrade Required')); + .throws(new UpgradeRequiredError('Upgrade Required')); const showUpgradeRequestModalStub = sandbox.stub(snapsUtil, 'showUpgradeRequestModal').resolves(); let result; try { @@ -106,9 +114,7 @@ describe('Test function: signTransaction', function () { const validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') .throws( - new utils.DeployRequiredError( - `Cairo 0 contract address ${account1.address} balance is not empty, deploy required`, - ), + new DeployRequiredError(`Cairo 0 contract address ${account1.address} balance is not empty, deploy required`), ); const showDeployRequestModalStub = sandbox.stub(snapsUtil, 'showDeployRequestModal').resolves(); let result; diff --git a/packages/starknet-snap/test/src/verifySignedMessage.test.ts b/packages/starknet-snap/test/src/verifySignedMessage.test.ts index 0d8ed090..8ff58c0c 100644 --- a/packages/starknet-snap/test/src/verifySignedMessage.test.ts +++ b/packages/starknet-snap/test/src/verifySignedMessage.test.ts @@ -10,6 +10,7 @@ import { getAddressKeyDeriver } from '../../src/utils/keyPair'; import * as utils from '../../src/utils/starknetUtils'; import { Mutex } from 'async-mutex'; import { ApiParams, VerifySignedMessageRequestParams } from '../../src/types/snapApi'; +import { UpgradeRequiredError } from '../../src/utils/exceptions'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); @@ -117,7 +118,7 @@ describe('Test function: verifySignedMessage', function () { beforeEach(async function () { validateAccountRequireUpgradeOrDeployStub = sandbox .stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new utils.UpgradeRequiredError('Upgrade Required')); + .throws(new UpgradeRequiredError('Upgrade Required')); }); it('should throw error if upgrade required', async function () {