Browse Source

feat(contract_manager): derive starknet account address (#1720)

Pavel Strakhov 1 year ago
parent
commit
af3ae92cfe
2 changed files with 54 additions and 47 deletions
  1. 34 5
      contract_manager/src/chains.ts
  2. 20 42
      contract_manager/src/contracts/starknet.ts

+ 34 - 5
contract_manager/src/chains.ts

@@ -24,7 +24,7 @@ import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
 import { TokenId } from "./token";
 import { BN, Provider, Wallet, WalletUnlocked } from "fuels";
 import { FUEL_ETH_ASSET_ID } from "@pythnetwork/pyth-fuel-js";
-import { RpcProvider } from "starknet";
+import { Contract, RpcProvider, Signer, ec, shortString } from "starknet";
 
 export type ChainConfig = Record<string, string> & {
   mainnet: boolean;
@@ -676,14 +676,43 @@ export class StarknetChain extends Chain {
     return new UpgradeContract256Bit(this.wormholeChainName, digest).encode();
   }
 
-  // Account address derivation on Starknet depends
-  // on the wallet application and constructor arguments used.
   async getAccountAddress(privateKey: PrivateKey): Promise<string> {
-    throw new Error("Unsupported");
+    const ARGENT_CLASS_HASH =
+      "0x029927c8af6bccf3f6fda035981e765a7bdbf18a2dc0d630494f8758aa908e2b";
+    const ADDR_BOUND =
+      0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00n;
+
+    function computeHashOnElements(elements: string[]): string {
+      let hash = "0";
+      for (const item of elements) {
+        hash = ec.starkCurve.pedersen(hash, item);
+      }
+      return ec.starkCurve.pedersen(hash, elements.length);
+    }
+
+    const publicKey = await new Signer("0x" + privateKey).getPubKey();
+
+    const value = computeHashOnElements([
+      shortString.encodeShortString("STARKNET_CONTRACT_ADDRESS"),
+      "0",
+      publicKey,
+      ARGENT_CLASS_HASH,
+      computeHashOnElements([publicKey, "0"]),
+    ]);
+    return (BigInt(value) % ADDR_BOUND).toString(16).padStart(64, "0");
   }
 
   async getAccountBalance(privateKey: PrivateKey): Promise<number> {
-    throw new Error("Unsupported");
+    const ETH =
+      "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7";
+
+    const address = await this.getAccountAddress(privateKey);
+    const provider = this.getProvider();
+    const tokenClassData = await provider.getClassAt(ETH);
+    const tokenContract = new Contract(tokenClassData.abi, ETH, provider);
+    const decimals = await tokenContract.decimals();
+    const amount = await tokenContract.balanceOf("0x" + address);
+    return Number(amount) / Number(10n ** decimals);
   }
 
   getProvider(): RpcProvider {

+ 20 - 42
contract_manager/src/contracts/starknet.ts

@@ -62,7 +62,7 @@ export class StarknetPriceFeedContract extends PriceFeedContract {
     return sources.map((source) => {
       return {
         emitterChain: Number(source.emitter_chain_id),
-        emitterAddress: source.emitter_address.toString(16),
+        emitterAddress: source.emitter_address.toString(16).padStart(64, "0"),
       };
     });
   }
@@ -82,7 +82,7 @@ export class StarknetPriceFeedContract extends PriceFeedContract {
   async getFeeTokenAddresses(): Promise<string[]> {
     const contract = await this.getContractClient();
     const tokens: bigint[] = await contract.fee_token_addresses();
-    return tokens.map((t) => t.toString(16));
+    return tokens.map((t) => t.toString(16).padStart(64, "0"));
   }
 
   /**
@@ -126,21 +126,7 @@ export class StarknetPriceFeedContract extends PriceFeedContract {
     senderPrivateKey: PrivateKey,
     vaas: Buffer[]
   ): Promise<TxResult> {
-    // We need the account address to send transactions.
-    throw new Error("Use executeUpdatePriceFeedWithAddress instead");
-  }
-
-  /**
-   * Executes the update instructions contained in the VAAs using the sender credentials
-   * @param senderPrivateKey private key of the sender in hex format without 0x prefix
-   * @param senderAddress address of the sender's account in hex format without 0x prefix
-   * @param vaa VAA containing price update messages to execute
-   */
-  async executeUpdatePriceFeedWithAddress(
-    senderPrivateKey: PrivateKey,
-    senderAddress: string,
-    vaa: Buffer
-  ): Promise<TxResult> {
+    const senderAddress = await this.chain.getAccountAddress(senderPrivateKey);
     const provider = this.chain.getProvider();
     const contract = await this.getContractClient();
     const account = new Account(
@@ -155,35 +141,27 @@ export class StarknetPriceFeedContract extends PriceFeedContract {
     const tokenContract = new Contract(tokenClassData.abi, feeToken, provider);
     tokenContract.connect(account);
 
-    const updateData = ByteBuffer.fromBuffer(vaa);
-    const feeAmount = await contract.get_update_fee(updateData, feeToken);
-    const feeTx = await tokenContract.approve(this.address, feeAmount);
-    await provider.waitForTransaction(feeTx.transaction_hash);
-
-    const tx = await contract.update_price_feeds(updateData);
-    const info = await provider.waitForTransaction(tx.transaction_hash);
-    return { id: tx.transaction_hash, info };
-  }
-
-  executeGovernanceInstruction(
-    senderPrivateKey: PrivateKey,
-    vaa: Buffer
-  ): Promise<TxResult> {
-    // We need the account address to send transactions.
-    throw new Error("Use executeGovernanceInstructionWithAddress instead");
+    const ids = [];
+    const infos = [];
+    for (const vaa of vaas) {
+      const updateData = ByteBuffer.fromBuffer(vaa);
+      const feeAmount = await contract.get_update_fee(updateData, feeToken);
+      const feeTx = await tokenContract.approve(this.address, feeAmount);
+      await provider.waitForTransaction(feeTx.transaction_hash);
+
+      const tx = await contract.update_price_feeds(updateData);
+      const info = await provider.waitForTransaction(tx.transaction_hash);
+      ids.push(tx.transaction_hash);
+      infos.push(info);
+    }
+    return { id: ids.join(","), info: infos };
   }
 
-  /**
-   * Executes the governance instruction contained in the VAA using the sender credentials
-   * @param senderPrivateKey private key of the sender in hex format without 0x prefix
-   * @param senderAddress address of the sender's account in hex format without 0x prefix
-   * @param vaa the VAA to execute
-   */
-  async executeGovernanceInstructionWithAddress(
+  async executeGovernanceInstruction(
     senderPrivateKey: PrivateKey,
-    senderAddress: string,
     vaa: Buffer
   ): Promise<TxResult> {
+    const senderAddress = await this.chain.getAccountAddress(senderPrivateKey);
     const provider = this.chain.getProvider();
     const contract = await this.getContractClient();
     const account = new Account(
@@ -205,7 +183,7 @@ export class StarknetPriceFeedContract extends PriceFeedContract {
       await contract.governance_data_source();
     return {
       emitterChain: Number(source.emitter_chain_id),
-      emitterAddress: source.emitter_address.toString(16),
+      emitterAddress: source.emitter_address.toString(16).padStart(64, "0"),
     };
   }