Skip to content

Commit

Permalink
feat: allow recovery of undeployed accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
janek26 committed Feb 2, 2023
1 parent 2084724 commit a6de240
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 19 deletions.
2 changes: 1 addition & 1 deletion actions/transferAll/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 0 additions & 2 deletions execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
}
Expand Down
20 changes: 19 additions & 1 deletion getAccounts.ts
Original file line number Diff line number Diff line change
@@ -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 = [
Expand Down Expand Up @@ -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())),
};
}
Expand All @@ -72,6 +88,7 @@ export async function getAccountsBySeedPhrase(

const accounts: {
address: string;
deployImplementation?: string;
networkId: string;
derivationPath: string;
privateKey: string;
Expand Down Expand Up @@ -129,6 +146,7 @@ export async function getAccountsByPrivateKey(

const accounts: {
address: string;
deployImplementation?: string;
networkId: string;
privateKey?: string;
}[] = [];
Expand Down
31 changes: 21 additions & 10 deletions getTokenBalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
),
};
Expand Down
10 changes: 10 additions & 0 deletions issues/deploy/detect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Account } from "../../ui/pickAccounts";

export const detect = async (accounts: Account[]): Promise<string[]> => {
return accounts
.filter(
({ signer, implementation, version }) =>
signer === null && implementation === null && version === null
)
.map(({ address }) => address);
};
65 changes: 65 additions & 0 deletions issues/deploy/fix.ts
Original file line number Diff line number Diff line change
@@ -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<void> => {
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`);
};
16 changes: 12 additions & 4 deletions issues/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,39 @@ 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(
accounts: Account[]
): Promise<IssuesMap> {
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(
accounts: Account[],
network: "mainnet-alpha" | "goerli-alpha",
issues: IssuesMap
): Promise<void> {
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);
}
}
2 changes: 1 addition & 1 deletion issues/signer0/detect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { Account } from "../../ui/pickAccounts";

export const detect = async (accounts: Account[]): Promise<string[]> => {
return accounts
.filter(({ signer }) => BigNumber.from(signer).eq(0))
.filter(({ signer }) => signer && BigNumber.from(signer).eq(0))
.map(({ address }) => address);
};
1 change: 1 addition & 0 deletions ui/pickAccounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface BaseAccount {
version: string | null;
privateKey?: string;
derivationPath?: string;
deployImplementation?: string;
}

export interface Account extends BaseAccount {
Expand Down

0 comments on commit a6de240

Please sign in to comment.