From a6de240925ff264a8dac781d848015d0d4fe9368 Mon Sep 17 00:00:00 2001 From: Janek Date: Thu, 2 Feb 2023 18:20:14 +0100 Subject: [PATCH] feat: allow recovery of undeployed accounts --- actions/transferAll/core.ts | 2 +- execute.ts | 2 -- getAccounts.ts | 20 +++++++++++- getTokenBalance.ts | 31 ++++++++++++------ issues/deploy/detect.ts | 10 ++++++ issues/deploy/fix.ts | 65 +++++++++++++++++++++++++++++++++++++ issues/index.ts | 16 ++++++--- issues/signer0/detect.ts | 2 +- ui/pickAccounts.ts | 1 + 9 files changed, 130 insertions(+), 19 deletions(-) create mode 100644 issues/deploy/detect.ts create mode 100644 issues/deploy/fix.ts diff --git a/actions/transferAll/core.ts b/actions/transferAll/core.ts index 4aefe1f..e071110 100644 --- a/actions/transferAll/core.ts +++ b/actions/transferAll/core.ts @@ -54,7 +54,7 @@ export async function transferAll(acc: Account, newAddress: string, ora: Ora) { return { ...c, calldata: compileCalldata({ - to: newAddress, + to: newAddress.toLowerCase(), value: { type: "struct", ...uint256.bnToUint256( diff --git a/execute.ts b/execute.ts index e011d52..7ac4831 100644 --- a/execute.ts +++ b/execute.ts @@ -47,11 +47,9 @@ export async function execute(account: Account, call: Call[] | Call) { const a = new OldAccount(oldProvider, lowerCaseAddress, keyPair); return await a.execute(calls); } catch (e) { - console.warn("old failed", e); const newProvider = new NewProvider({ network: account.networkId as any }); const a = new NewAccount(newProvider, lowerCaseAddress, keyPair); return a.execute(calls).catch((e) => { - console.warn("new failed", e); throw e; }); } diff --git a/getAccounts.ts b/getAccounts.ts index 2200bd7..952e606 100644 --- a/getAccounts.ts +++ b/getAccounts.ts @@ -1,14 +1,18 @@ import { Wallet } from "ethers"; import { ec, hash, number, SequencerProvider, stark } from "starknet"; +import { getBalances } from "./getTokenBalance"; import { getPathForIndex, getStarkPair } from "./keyDerivation"; const CHECK_OFFSET = 10; -const PROXY_CONTRACT_CLASS_HASHES = [ +export const PROXY_CONTRACT_CLASS_HASHES = [ "0x25ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918", ]; const ARGENT_ACCOUNT_CONTRACT_CLASS_HASHES = [ + "0x1a7820094feaf82d53f53f214b81292d717e7bb9a92bb2488092cd306f3993f", + "0x7e28fb0161d10d1cf7fe1f13e7ca57bce062731a3bd04494dfd2d0412699727", "0x3e327de1c40540b98d05cbcb13552008e36f0ec8d61d46956d2f9752c294328", + "0x33434ad846cdd5f23eb73ff09fe6fddd568284a0fb7d1be20ee482f044dabe2", ]; export const BASE_DERIVATION_PATHS = [ @@ -41,12 +45,24 @@ async function getAccountByKeyPair( ); try { + const ethBalance = await getBalances([address], network, ["ETH"]); + + if (ethBalance[0].rawBalance !== "0") { + return { + address, + deployImplementation: accountClassHash, + networkId: network, + privateKey: number.toHex(number.toBN(keyPair.getPrivate().toString())), + }; + } + const code = await provider.getCode(address); if (code.bytecode.length > 0) { return { address, networkId: network, + deployImplementation: accountClassHash, privateKey: number.toHex(number.toBN(keyPair.getPrivate().toString())), }; } @@ -72,6 +88,7 @@ export async function getAccountsBySeedPhrase( const accounts: { address: string; + deployImplementation?: string; networkId: string; derivationPath: string; privateKey: string; @@ -129,6 +146,7 @@ export async function getAccountsByPrivateKey( const accounts: { address: string; + deployImplementation?: string; networkId: string; privateKey?: string; }[] = []; diff --git a/getTokenBalance.ts b/getTokenBalance.ts index 2949423..0ecee3b 100644 --- a/getTokenBalance.ts +++ b/getTokenBalance.ts @@ -5,9 +5,18 @@ import { Multicall } from "@argent/x-multicall"; export async function getBalances( addresses: string[], - network: "mainnet-alpha" | "goerli-alpha" + network: "mainnet-alpha" | "goerli-alpha", + tokenWhiteList: string[] = [] ) { - const tokens = TOKENS.filter((token) => token.network === network); + const tokens = TOKENS.filter((token) => token.network === network).filter( + (token) => { + if (tokenWhiteList.length) { + return tokenWhiteList.includes(token.symbol); + } else { + return true; + } + } + ); const tokenAddresses = tokens.map((token) => token.address); const provider = new SequencerProvider({ network }); const multicallProvider = new Multicall(provider as any); @@ -40,18 +49,20 @@ export async function getBalances( return addressesTokensCombinations.map((addressToken, index) => { const balance = results[index]; + const rawBalance = balance + ? uint256 + .uint256ToBN({ + low: encode.addHexPrefix(balance[0]), + high: encode.addHexPrefix(balance[1]), + }) + .toString() + : "0"; return { address: addressToken.address, token: addressToken.token, + rawBalance, balance: formatTokenBalance( - balance - ? uint256 - .uint256ToBN({ - low: encode.addHexPrefix(balance[0]), - high: encode.addHexPrefix(balance[1]), - }) - .toString() - : "0", + rawBalance, tokens.find((x) => x.address === addressToken.token)!.decimals ), }; diff --git a/issues/deploy/detect.ts b/issues/deploy/detect.ts new file mode 100644 index 0000000..5dde79a --- /dev/null +++ b/issues/deploy/detect.ts @@ -0,0 +1,10 @@ +import { Account } from "../../ui/pickAccounts"; + +export const detect = async (accounts: Account[]): Promise => { + return accounts + .filter( + ({ signer, implementation, version }) => + signer === null && implementation === null && version === null + ) + .map(({ address }) => address); +}; diff --git a/issues/deploy/fix.ts b/issues/deploy/fix.ts new file mode 100644 index 0000000..5eee02b --- /dev/null +++ b/issues/deploy/fix.ts @@ -0,0 +1,65 @@ +import ora from "ora"; +import { + Account as SNAccount, + ec, + SequencerProvider, + stark, + hash, +} from "starknet-490"; +import { PROXY_CONTRACT_CLASS_HASHES } from "../../getAccounts"; +import { getVersion } from "../../getVersion"; +import { oraLog } from "../../oraLog"; +import { Account } from "../../ui/pickAccounts"; + +export const fix = async ( + accounts: Account[], + network: "mainnet-alpha" | "goerli-alpha", + accountsToRecover: string[] +): Promise => { + const spinner = ora(`Deploying accounts (this may take some time)`).start(); + const provider = new SequencerProvider({ network }); + + for (const address of accountsToRecover) { + const account = accounts.find((a) => a.address === address); + if (!account) { + throw new Error(`Account ${address} not found`); + } + if (!account.deployImplementation) { + throw new Error(`Account ${address} has no deployImplementation`); + } + const keyPair = ec.getKeyPair(account.privateKey); + const starkKey = ec.getStarkKey(keyPair); + const snAccount = new SNAccount(provider, account.address, keyPair); + + const constructorCallData = { + implementation: account.deployImplementation, + selector: hash.getSelectorFromName("initialize"), + calldata: stark.compileCalldata({ signer: starkKey, guardian: "0" }), + }; + + const deployAccountPayload = { + classHash: PROXY_CONTRACT_CLASS_HASHES[0], + contractAddress: account.address, + constructorCalldata: stark.compileCalldata(constructorCallData), + addressSalt: starkKey, + }; + + const transaction = await snAccount.deployAccount(deployAccountPayload); + oraLog(spinner, `Transaction ${transaction.transaction_hash} created`); + await provider.waitForTransaction(transaction.transaction_hash); + + if (account) { + account.signer = starkKey; + account.implementation = account.deployImplementation; + const [newVersion] = await getVersion( + [account.address], + account.networkId as any + ); + account.version = newVersion; + } + + // wait 1 minute extra to make sure the transaction is mined + await new Promise((resolve) => setTimeout(resolve, 60000)); + } + spinner.succeed(`Deployed accounts`); +}; diff --git a/issues/index.ts b/issues/index.ts index 351bb3a..919793a 100644 --- a/issues/index.ts +++ b/issues/index.ts @@ -2,11 +2,14 @@ import { Account } from "../ui/pickAccounts"; import { detect as detectOldHashAlgo } from "./oldHashAlgo/detect"; import { fix as fixOldHashAlgo } from "./oldHashAlgo/fix"; import { detect as detectSigner0 } from "./signer0/detect"; +import { detect as detectDeploy } from "./deploy/detect"; import { fix as fixSigner0 } from "./signer0/fix"; +import { fix as fixDeploy } from "./deploy/fix"; interface IssuesMap { oldHashAlgo?: string[]; signer0?: string[]; + deploy?: string[]; } export async function detectAccountIssues( @@ -14,7 +17,8 @@ export async function detectAccountIssues( ): Promise { const oldHashAlgo = await detectOldHashAlgo(accounts); const signer0 = await detectSigner0(accounts); - return { oldHashAlgo, signer0 }; + const deploy = await detectDeploy(accounts); + return { oldHashAlgo, signer0, deploy }; } export async function fixAccountIssues( @@ -22,11 +26,15 @@ export async function fixAccountIssues( network: "mainnet-alpha" | "goerli-alpha", issues: IssuesMap ): Promise { - const { oldHashAlgo } = issues; + const { oldHashAlgo, signer0, deploy } = issues; + + if (deploy?.length && deploy?.length > 0) { + await fixDeploy(accounts, network, deploy); + } if (oldHashAlgo?.length && oldHashAlgo?.length > 0) { await fixOldHashAlgo(accounts, network, oldHashAlgo); } - if (issues.signer0?.length && issues.signer0?.length > 0) { - await fixSigner0(accounts, network, issues.signer0); + if (signer0?.length && signer0?.length > 0) { + await fixSigner0(accounts, network, signer0); } } diff --git a/issues/signer0/detect.ts b/issues/signer0/detect.ts index ba9bec7..dfa27bb 100644 --- a/issues/signer0/detect.ts +++ b/issues/signer0/detect.ts @@ -3,6 +3,6 @@ import { Account } from "../../ui/pickAccounts"; export const detect = async (accounts: Account[]): Promise => { return accounts - .filter(({ signer }) => BigNumber.from(signer).eq(0)) + .filter(({ signer }) => signer && BigNumber.from(signer).eq(0)) .map(({ address }) => address); }; diff --git a/ui/pickAccounts.ts b/ui/pickAccounts.ts index f2230de..646d81d 100644 --- a/ui/pickAccounts.ts +++ b/ui/pickAccounts.ts @@ -9,6 +9,7 @@ export interface BaseAccount { version: string | null; privateKey?: string; derivationPath?: string; + deployImplementation?: string; } export interface Account extends BaseAccount {