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

Add support for calling private view functions #390

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

xbtmatt
Copy link
Contributor

@xbtmatt xbtmatt commented May 8, 2024

Description

Private view functions, e.g. a function like the following:

#[view]
fun hidden_from_abis(): u64 {
    0
}

are callable off-chain the same way private entry functions are. However, they aren't included in the module's function ABIs. You can confirm this here with a simple curl request (included jq for easy viewing):

curl --request GET \
     --url https://api.testnet.aptoslabs.com/v1/accounts/0x635f592857fd8c57d10f92c49e858ad6f4e92b3d24ec521fccad459289f38208/module/main \
     --header 'Accept: application/json' | jq -r .

You should see an empty exposed_functions array, but you can still call the function:

aptos move view --function-id 0x635f592857fd8c57d10f92c49e858ad6f4e92b3d24ec521fccad459289f38208::main::hidden_from_abis --url https://fullnode.testnet.aptoslabs.com

Which returns with:

{
  "Result": [
    "0"
  ]
}

Note that the reason this wasn't an issue before is because view functions weren't BCS-serializable and thus did not use the fetchABIs call

Changes

The SDK currently checks the ABIs regardless of what's passed in to view. This means that there's no way to call a private view function, even if you already have the ABIs or the serialized entry function arguments.

In order to circumvent this, I've updated view in src/internal/view.ts to check if an abi is passed in. If so, it circumvents the ABI lookup failure in generateViewFunctionPayload by calling generateViewFunctionPayloadWithABI directly.

The most relevant changes are below:

// Skip ABI fetch (and thus the error) and use ABIs passed in, run ABI checks still
if (typeof abi !== "undefined") {
   viewFunctionPayload = generateViewFunctionPayloadWithABI({
      ...payload,
      abi,
   });
}
// Otherwise, check if every argument is BCS-encoded
else {
   const everyFunctionArgumentIsEncoded = payload.functionArguments?.every(isEncodedEntryFunctionArgument);
   // If so, skip ABI fetch/check, serialize arguments directly
   if (everyFunctionArgumentIsEncoded) {
      viewFunctionPayload = await generateViewFunctionPayloadWithNoABI({
         ...(payload as InputViewFunctionDataWithNoABI),
         functionArguments: payload.functionArguments as Array<EntryFunctionArgumentTypes>,
      });
   // Otherwise make the call like it is made right now, with the potential for error
   } else {
      try {
         viewFunctionPayload = await generateViewFunctionPayload({ ... });
      } catch (e) {
         // if (ViewFunctionNotFoundError) throw more specific error msg. see code
      }
   }
}

I've also added type discriminators in src/transactions/types.ts to discourage incorrect combinations at compile time if the user decides to invoke any of the generateViewFunctionPayload_ functions directly.

Test Plan

Now that view functions are BCS-serializable, I added unit tests in transactionArguments.test.ts to cover them, as well as Move unit tests in the contract itself. I've also updated the module bytecode for the module used in transactionArguments.test.ts:

pnpm jest transactionArguments.test.ts

They cover all of the combinations of the ABI being provided/not provided and the arguments being simple, mixed, and serializable.

I also exposed a simple test-only clearMemoizedCache function to facilitate these unit tests, since memoization will cause errors with the previously-fetched ABIs

xbtmatt added 5 commits May 6, 2024 19:55
…function ABI, and with exclusively BCS-encoded arguments, depending on its visibility
Add a helper test function to clear the memoized cache so we can test public/private functions with the same module

Type the JSON response from the view functions and assert they're correct
@xbtmatt xbtmatt requested a review from a team as a code owner May 8, 2024 02:38
@xbtmatt xbtmatt changed the title Matt/private view Add support for private BCS-serialized view functions May 8, 2024
@xbtmatt xbtmatt changed the title Add support for private BCS-serialized view functions Add support for private view functions May 8, 2024
@xbtmatt xbtmatt changed the title Add support for private view functions Add support for calling private view functions May 8, 2024
Copy link
Collaborator

@0xmaayan 0xmaayan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a great first step in handling the abi/non-abi/remote-abi flow in the SDK. Just need to organize it a bit more and add some tests for the different view function use cases

@@ -189,7 +202,7 @@ export async function generateViewFunctionPayload(args: InputViewFunctionDataWit
});

// Fill in the ABI
return generateViewFunctionPayloadWithABI({ abi: functionAbi, ...args });
return generateViewFunctionPayloadWithABI({ ...args, abi: functionAbi });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason for this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah oops I actually meant to note this in the PR, I'm not sure if it's intended or not, but basically if you pass in your own abi it overwrites the fetched ABI because of the order of the spread on ...args

basically if you pass abi: undefined it ignores the fetched ABI and tries to use undefined

It's kind of a small thing, since most often people won't pass abi: undefined, but in my case I was doing that, I just figured if you're fetching it it should use the results

const { abi } = payload;

// If the ABI is provided, we can skip the fetch ABI request and generate the payload.
if (typeof abi !== "undefined") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this logic live in the transactionBuilder.ts file instead of here?

* @param key The key to clear from the cache
* @returns void
*/
export function clearMemoizedCache(key: string): void {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

import { Serializer } from "../bcs";
import { postAptosFullNode } from "../client";
import { ViewFunctionNotFoundError, postAptosFullNode } from "../client";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is time for us to create tests/e2e/api/view.test.tsx file, move this in there, and add bunch of tests with this abi non-abi functionality

// If the ABI isn't provided
} else {
// We can skip both the fetch ABI request and the ABI check if all function arguments are BCS-serializable.
const everyFunctionArgumentIsEncoded = payload.functionArguments?.every(isEncodedEntryFunctionArgument);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can benefit from this logic for entry function also, I think the SDK currently fetches and parses the remote ABI even for submitting an entry function with encoded arguments - let's pull it into its own helper function

} catch (e) {
if (e instanceof ViewFunctionNotFoundError) {
// Throw the same error with a more specific error message.
const errorMsg =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it override the messages being thrown from the transactionBuilder flow?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants