Skip to content

Improve testing workflow #175

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

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from
Draft

Improve testing workflow #175

wants to merge 24 commits into from

Conversation

andrew-fleming
Copy link
Contributor

The following PR proposes to improve the testing workflow with the following changes:

1. Create a testing/ workspace

  • Exports an abstract class simulator
    • Provides proxy wrappers for pure and impure circuits using the ContextlessCircuits type
    • The ContextlessCircuits type removes the ctx arg from circuits
      • This allows the caller ctx to be dynamically injected into circuit calls

Simulator example

This allows us to change this:

  public transferFrom(
    from: Either<ZswapCoinPublicKey, ContractAddress>,
    to: Either<ZswapCoinPublicKey, ContractAddress>,
    tokenId: bigint,
    sender?: CoinPublicKey,
  ) {
    const res = this.contract.impureCircuits.transferFrom(
      {
        ...this.circuitContext,
        currentZswapLocalState: sender
          ? emptyZswapLocalState(sender)
          : this.circuitContext.currentZswapLocalState,
      },
      from,
      to,
      tokenId,
    );

    this.circuitContext = res.context;
  }

into this:

  public transferFrom(
    from: Either<ZswapCoinPublicKey, ContractAddress>,
    to: Either<ZswapCoinPublicKey, ContractAddress>,
    tokenId: bigint,
  ) {
    return this.circuits.impure.transferFrom(from, to, tokenId);
  }

Test example

In the actual tests, we can dynamically set the caller context and remove the extra caller arg.
So again we can change this:

_caller = SPENDER;
token.transferFrom(Z_OWNER, Z_SPENDER, TOKENID_1, _caller);
token.transferFrom(Z_OWNER, Z_SPENDER, TOKENID_2, _caller);
token.transferFrom(Z_OWNER, Z_SPENDER, TOKENID_3, _caller);

into this:

token.setCaller(SPENDER);
token.transferFrom(Z_OWNER, Z_SPENDER, TOKENID_1);
token.transferFrom(Z_OWNER, Z_SPENDER, TOKENID_2);
token.transferFrom(Z_OWNER, Z_SPENDER, TOKENID_3);

Address utils

A little less sexy: All contract tests can import from one source for testing utils such as toHexPadded, encodeToPk, etc

import {
  ZERO_ADDRESS,
  ZERO_KEY,
  createEitherTestContractAddress,
  createEitherTestUser,
  toHexPadded,
} from '@openzeppelin-compact/testing';

BUT the really really good part of this is that it allows the Foo test/ directory to change from:

test
  ├──mocks
       └──MockFoo.compact
  ├──simulators
       └──FooSimulator.ts
  ├──types
       ├──string.ts
       └──test.ts
  ├──utils
       ├──address.ts
       └──test.ts
  └──Foo.test

to this:

test
  ├──mocks
       └──MockFoo.compact
  ├──simulators
       └──FooSimulator.ts
  └──Foo.test

2. Add compact-std

This removes the need to export compact types from mocks (credit to @0xisk 🙌 )

3. Move all packages to packages/

  • contracts/
  • compact/
  • compact-std/
  • testing/

It looks super unorganized having all these different directories sitting in root. This improves organization


Finally, this PR only proposes to apply the testing changes to one module (nonFungibleToken) as a proof of concept. This reduces the PR noise to make it easier to review. This allows any discussions to focus mostly on design instead of getting lost in 12498129423498 file diffs. We can open a new PR applying the changes to the rest if/when this is merged (or in this PR if preferred)

If this is too much, happy to break this up into separate PRs

Fixes #32
Fixes #58
Fixes #59
Fixes #74

PR Checklist

  • Tests
  • Documentation (in code)

@andrew-fleming andrew-fleming requested a review from a team as a code owner July 14, 2025 00:15
@andrew-fleming
Copy link
Contributor Author

If we like the idea with setCaller, we can add more methods like setCallerOnce or set caller for n calls (not sure of a good name...setCallerFor?)

Copy link
Member

@0xisk 0xisk left a comment

Choose a reason for hiding this comment

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

Very nice work @andrew-fleming, great initiative and thought on getting the testing experience smoother! Enjoyed reviewing the code.
CheersPepe

Some points to consider:

  • (IN-SCOPE) I'd argue if it is possible to have two separate workspace:
    • contracts/: a workspace for all the Compact contract packages.
    • packages/: a workspace for all the helper TS packages (which are eventually going to be moved to another repo).
  • (OUT-SCOPE) I am thinking it might also worth introducing a general class that overrides the witnesses to make it malicious for testing.

Comment on lines 3 to 7
/**
* Utility type that removes the context argument from circuits,
* returning a version callable without the CircuitContext.
*/
export type ContextlessCircuits<Circuits, TState> = {
Copy link
Member

Choose a reason for hiding this comment

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

Please let us elaborate more on the tsdocs on the generics.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! abe5fc8

Copy link
Member

Choose a reason for hiding this comment

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

General comments on the testing package:

  1. I think we are missing a README for this package that elaborates on how to use.
  2. We need to add testings here.
  3. Since the package is small, I would argue to to not have a sub directory simulator and just have all in the root rightaway.
  4. I would have one types.ts script that combines all ContextlessCircuits, IContractSimulator, and in case we agree from the suggestions we may also add both ExtractPureCircuits and ExtractImpureCircuits.
  5. So if we agree on point 3, and 4 the directory flow would look like this,
src/
  AbstractContractSimulator.ts
  types.ts
  address.ts
  index.ts

Copy link
Contributor Author

@andrew-fleming andrew-fleming Jul 18, 2025

Choose a reason for hiding this comment

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

Agreed on all points

Since the package is small, I would argue to to not have a sub directory simulator and just have all in the root rightaway.

I wasn't sure how big this was going to be, but yeah. For now, I think flat makes sense 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Points 3, 4, 5 ✅

Tests and readme are forthcoming

"name": "@openzeppelin-compact/testing",
"private": true,
"version": "0.0.1",
"description": "",
Copy link
Member

Choose a reason for hiding this comment

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

Please let us add description here.

@0xisk
Copy link
Member

0xisk commented Jul 16, 2025

If we like the idea with setCaller, we can add more methods like setCallerOnce or set caller for n calls (not sure of a good name...setCallerFor?)

I like the idea, I would vote for yes. But I am thinking that could be anther class to be abstracted and the implementing Simulator have the option to extend/implement it.

@andrew-fleming
Copy link
Contributor Author

Thank you for the review @0xisk!

(IN-SCOPE) I'd argue if it is possible to have two separate workspace:
contracts/: a workspace for all the Compact contract packages.
packages/: a workspace for all the helper TS packages (which are eventually going to be moved to another repo).

I initially had it set up this way^. I thought it looked weird with a packages/ and a contracts/ in root. Since the idea is to move the other packages to another repo, I agree. It's better to have contracts separate. Will update

(OUT-SCOPE) I am thinking it might also worth introducing a general class that overrides the witnesses to make it malicious for testing.

100%! Life will be much easier :)

@andrew-fleming andrew-fleming marked this pull request as draft July 20, 2025 23:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants