Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TS Errors while requesting a contract's wasm code #166

Open
tammaroivan opened this issue Oct 24, 2023 · 3 comments
Open

TS Errors while requesting a contract's wasm code #166

tammaroivan opened this issue Oct 24, 2023 · 3 comments
Labels
bug Something isn't working

Comments

@tammaroivan
Copy link

Describe the bug
I'm trying to generate a contract's Wasm Code following the official docs. The problem I have is that TS is complaining about different issues specifically in this function:

function getLedgerKeyWasmId(contractCodeLedgerEntryData: string) {
  const entry = xdr.LedgerEntryData.fromXDR(
    contractCodeLedgerEntryData,
    'base64',
  );

  const instance = new xdr.ScContractInstance({
    executable: entry.contractData().val(),
  });

  const ledgerKey = xdr.LedgerKey.contractCode(
    new xdr.LedgerKeyContractCode({
      hash: xdr.ContractExecutable.contractExecutableWasm(instance.executable())
        .wasmHash()
        .instance()
        .executable()
        .wasmHash(),
    }),
  );

  return ledgerKey;
}

I receive 2 errors when creating the instance:

  • Property 'wasmHash' is missing in type 'ScVal' but required in type 'ContractExecutable'.
  • Even if I ignore or cast the executable as any I see the following error. Any idea what storage is in this case?
    • Property 'storage' is missing in type '{ executable: any; }' but required in type '{ executable: ContractExecutable; storage: ScMapEntry[]; }'

When getting the wasm hash I get 2 more errors:

  • First one is happening specifically here instance.executable()
    • Argument of type 'ContractExecutable' is not assignable to parameter of type 'Buffer'. Type 'ContractExecutable' is missing the following properties from type 'Buffer': write, toJSON, equals, compare, and 97 more.ts(2345)
  • Second one is happening when trying to call instace()
    • Property 'instance' does not exist on type 'Buffer'

In JS files the function is working properly so I'm not sure how should I procede using TypeScript.

What version are you on?

  • ^1.0.0-beta.3

To Reproduce
Steps to reproduce the behavior:

  1. Setup a TS project
  2. Follow the official docs to get the ledger key wasm id
  3. Your IDE should throw the errors mentioned above

Expected behavior

  • Shouldn't receive any errors because the JS version is working properly.

Happy to provide additional code or information if needed!

@tammaroivan tammaroivan added the bug Something isn't working label Oct 24, 2023
@Shaptic
Copy link
Contributor

Shaptic commented Oct 26, 2023

It's complaining about types because your types aren't matching up.

There are a few issues here, starting on this line:

    executable: entry.contractData().val(),

the .val() here returns an ScVal, but executable expects, well, a ContractExecutable.

There's also this line:

      hash: xdr.ContractExecutable.contractExecutableWasm(instance.executable())

You can see that the signature of the contractExecutableWasm constructor is (from types/curr.d.ts here, but this should be auto-filled by your IDE):

  class ContractExecutable {
    // etc...
    static contractExecutableWasm(value: Buffer): ContractExecutable;

so we need a Buffer. But you are passing instance.executable(), which is a ContractExecutable, not a Buffer (from here).

@Shaptic
Copy link
Contributor

Shaptic commented Oct 26, 2023

The tutorial you're following is, unfortunately, slightly outdated. I've rewritten it slightly in a way that is catered towards your issue and is hopefully easier to understand!


Requesting a Contract's Wasm Code

We are trying to answer the following question:

Given a contract ID, how do I retrieve the WASM code backing it?

This is answered in two parts, because multiple contracts can refer to the same WASM blob on the ledger:

  1. Retrieve the "contract instance" corresponding to this ID.
  2. Then, retrieve the WASM blob corresponding to that instance.

Retrieve the instance

The lookup LedgerKey that corresponds to a contract instance is of the type LedgerKeyContractData, which we need to form from the contract ID and fetch its corresponding LedgerEntryData like so:

import { xdr, Server } from 'soroban-client';

async function getInstanceValue(contractId: string): xdr.ContractDataEntry {
    const instanceKey = xdr.LedgerKey.contractData(
        new LedgerKeyContractData({
            contract: new Address(contractId).toScAddress(),
            key: xdr.ScVal.scvLedgerKeyContractInstance(),
            durability: xdr.ContractDataDurability.persistent(),
        })
    );

    // warning: ignoring error checks
    const response = await Server('<rpc url>').getLedgerEntries(instanceKey);
    const dataEntry = response.entries[0].val.contractData()
    return dataEntry;
}

Retrieve the WASM code

Now that we have the inner details of the contract instance, we can fetch the corresponding WASM. The .val() of the returned entry data is an ScVal whose inner guts are an ScContractInstance (via .instance()). That instance contains the executable which in turn contains the hash of the WASM code, which is the key we need to look up:

async function getWasmCode(instance: xdr.ScContractInstance): Buffer {
    const codeKey = xdr.LedgerKey.contractCode(
        new xdr.LedgerKeyContractCode({
            hash: instance.wasmHash()
        })
    );

    // warning: ignoring error checks
    const response = await Server('<rpc url>').getLedgerEntries(codeKey);
    const wasmCode = response.entries[0].val.contractCode().code();
    return wasmCode;
}

const code = getInstanceValue().then((value) => {
    return getWasmCode(value.val().instance());
});

@tammaroivan
Copy link
Author

Thanks @Shaptic for your detailed guide. I was able to implement both functions:

async getInstanceValue(contractId: string): Promise<xdr.ContractDataEntry> {
    try {
      const instanceKey = xdr.LedgerKey.contractData(
        new xdr.LedgerKeyContractData({
          contract: new Address(contractId).toScAddress(),
          key: xdr.ScVal.scvLedgerKeyContractInstance(),
          durability: xdr.ContractDataDurability.persistent(),
        }),
      );

      const response = await this.server.getLedgerEntries(instanceKey);
      const dataEntry = response.entries[0].val.contractData();
      return dataEntry;
    } catch (error) {
      console.log('Error while getting instance value: ', error);
    }
  }

  async getWasmCode(instance: xdr.ScContractInstance): Promise<Buffer> {
    try {
      const codeKey = xdr.LedgerKey.contractCode(
        new xdr.LedgerKeyContractCode({
          hash: instance.executable().wasmHash(),
        }),
      );

      const response = await this.server.getLedgerEntries(codeKey);
      const wasmCode = response.entries[0].val.contractCode().code();
      return wasmCode;
    } catch (error) {
      console.log('Error while getting wasm code: ', error);
    }
  }

The only problem I get now is that sometimes and it seems to be random I get the next error:

TypeError: expiration not set

It happens in any of the two functions,

Error while getting instance value: TypeError: expiration not set
at ChildUnion.get ([...]/node_modules/js-xdr/lib/webpack:/XDR/src/union.js:26:13)
at ChildUnion.get [as expiration] ([...]/node_modules/js-xdr/lib/webpack:/XDR/src/union.js:156:25)
at [...]/node_modules/soroban-client/lib/server.js:636:104
at Array.forEach (<anonymous>)
at mergeResponseExpirationLedgers ([...]/node_modules/soroban-client/lib/server.js:626:135)
at [...]/node_modules/soroban-client/lib/server.js:230:24
at processTicksAndRejections (node:internal/process/task_queues:95:5)
at StellarService.getInstanceValue ([...]/src/common/application/service/stellar.service.ts:149:24)
at StellarService.getContractSpecEntries ([...]/src/common/application/service/stellar.service.ts:175:29)
at StellarService.runInvocation ([...]/src/common/application/service/stellar.service.ts:217:25)

Error while getting wasm code: TypeError: expiration not set
at ChildUnion.get ([...]/node_modules/js-xdr/lib/webpack:/XDR/src/union.js:26:13)
at ChildUnion.get [as expiration] ([...]/node_modules/js-xdr/lib/webpack:/XDR/src/union.js:156:25)
at [...]/node_modules/soroban-client/lib/server.js:636:104
at Array.forEach (<anonymous>)
at mergeResponseExpirationLedgers ([...]/node_modules/soroban-client/lib/server.js:626:135)
at [...]/node_modules/soroban-client/lib/server.js:230:24
at processTicksAndRejections (node:internal/process/task_queues:95:5)
at StellarService.getWasmCode ([...]/src/common/application/service/stellar.service.ts:165:24)
at StellarService.getContractSpecEntries ([...]/src/common/application/service/stellar.service.ts:176:28)
at StellarService.runInvocation ([...]/src/common/application/service/stellar.service.ts:217:25)

And it seems to be random, because if I retry most of the times it works without any problem 🤔 Any idea what could this be?

tammaroivan added a commit to keizai-tools/keizai-api that referenced this issue Nov 3, 2023
### Summary
- We want to replace the current way of getting the contract code,
according to [this
comment](stellar/js-soroban-client#166 (comment))
we should do it in a different way.

### Changes
- Remove `getLedgerKeyWasmId` function
- Add `getInstanceValue` and `getWasmCode` functions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants