Skip to content

Conversation

@ElliotFriend
Copy link

Stellar supports __constructor functions in a smart contract, but most of the wizard contracts still hard-code the token name and symbol in the constructor function. this makes sharing the contract executables between multiple tokens more difficult, and will lead to more fees paid long-term.

This PR aims to put all the hard-coded bits of these wizard contracts into the constructor arguments. It also provides a helpful deploy command in the outputted Rust code, instructing the user how they can deploy the contract with their configured name, symbol, etc. for all the constructor arguments required.

I started in on adding some tests to cover the new functionality, but wanted to get this PR out there for a sanity check first. I can add more tests, if it seems like i'm moving in the right direction.

If there's any changes that need to be made, I'm definitely happy to do so. Just let me know 😁

@ElliotFriend ElliotFriend requested review from a team as code owners November 7, 2025 23:11
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 7, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The changes introduce a new ConstructorArgument type that extends Argument with optional value metadata. Constructor methods across fungible, non-fungible, and stablecoin contract types are refactored to accept metadata (name, symbol) and configuration parameters (owner, recipient, premint, admin, roles) instead of hard-coded values. Deploy command generation for Stellar CLI is added to constructor output, and all contract builders and test fixtures are updated accordingly.

Changes

Cohort / File(s) Change Summary
Core Type System
packages/core/stellar/src/contract.ts
Introduced ConstructorArgument interface extending Argument with optional value property. Updated Contract.constructorArgs type from Argument[] to ConstructorArgument[]. Updated ContractBuilder.constructorArgs and addConstructorArgument() signatures to use ConstructorArgument[].
Code Generation
packages/core/stellar/src/print.ts, packages/core/stellar/src/zip-shared.ts
Added ConstructorArgument import. Implemented printDeployCommand() to generate Stellar CLI deploy sequences and threaded deploy commands into constructor output via printFunction2(). Added getConstructorArgs() helper to map constructor arguments to Rust expressions with type-specific defaults. Refactored printRustNameTest() to use getConstructorArgs() instead of getAddressArgs().
Contract Builders
packages/core/stellar/src/fungible.ts, packages/core/stellar/src/non-fungible.ts, packages/core/stellar/src/set-access-control.ts
Updated addBase() and addPremint() to add constructor arguments for metadata (name, symbol) and minting parameters (recipient, premint). Updated set-access-control to include default placeholder values for owner, admin, and role-based constructor arguments.
Documentation
packages/core/stellar/README.md
Added "stablecoin" to supported contract types list.
Test Fixtures
packages/core/stellar/src/fungible.test.ts.md, packages/core/stellar/src/non-fungible.test.ts.md, packages/core/stellar/src/stablecoin.test.ts.md, packages/core/stellar/src/zip-rust.compile.test.ts.md
Updated constructor signatures across all contract variants from zero-argument or minimal-argument forms to parameterized constructors accepting metadata and configuration (name, symbol, owner, recipient, premint, admin, pauser, upgrader, minter). Added deployment CLI comments and adjusted corresponding initialization calls.
Tests
packages/core/stellar/src/zip-shared.test.ts
Added new test case for single String constructor argument handling. Fixed minor typos ("bellow" → "below").

Sequence Diagram

sequenceDiagram
    participant Builder as Contract Builder
    participant GenCode as Code Generator
    participant Print as Print Module
    participant Deploy as Deploy Command

    rect rgb(200, 220, 255)
    Note over Builder,Deploy: New Constructor Argument Flow
    Builder->>Builder: addConstructorArgument(arg: ConstructorArgument)
    Builder->>GenCode: contract.constructorArgs: ConstructorArgument[]
    GenCode->>Print: printConstructor(contract)
    Print->>Deploy: printDeployCommand(contract.constructorArgs)
    Deploy->>Print: deploy: string[] (CLI instructions)
    Print->>Print: printFunction2(..., deploy)
    Print->>Print: Inject deploy comments into constructor body
    end
    
    rect rgb(220, 255, 220)
    Note over Builder,GenCode: Constructor Parameter Propagation
    Builder->>Builder: addBase() adds name, symbol args
    Builder->>Builder: addPremint() adds recipient, premint args
    GenCode->>GenCode: mapConstructorArgs to Rust expressions
    GenCode->>GenCode: Apply type-specific defaults (Address, String, i128)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Core logic changes (contract.ts, print.ts, zip-shared.ts): Type system extension and code generation pipeline updates require verification of correct type propagation and deploy command injection logic.
  • Systematic parameter propagation across multiple contract builders and test fixtures follows a consistent pattern, reducing complexity but requiring cross-file consistency checks.
  • Test fixture scope: Extensive updates to constructor signatures across fungible, non-fungible, and stablecoin variants—verify parameter ordering and initialization logic remain correct across all combinations (basic, with owner, with minting, with roles, etc.).

Areas requiring extra attention:

  • Parameter ordering consistency across all contract builder variants (fungible, non-fungible, stablecoin)
  • Correctness of deploy command generation and injection into constructor output
  • Test fixture parameter alignment—ensure new signatures match corresponding builder implementations
  • Type safety of ConstructorArgument.value field propagation in code generation

Suggested reviewers

  • ericglau
  • ernestognw
  • gonzaotc

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description provides relevant context about the motivation (reducing fees, sharing contract executables) and explains what the PR does (moving hard-coded values to constructor arguments and adding deploy commands).
Title check ✅ Passed The title 'Use constructor arguments in Stellar contracts' directly and clearly summarizes the main change: moving from hard-coded values to constructor arguments in Stellar contracts throughout the codebase.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 7, 2025

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/core/stellar/src/non-fungible.ts (1)

124-127: Consider parameterizing the URI value.

The uri constructor argument uses a hard-coded placeholder value 'www.mytoken.com', while name and symbol are parameterized. For consistency and to fully realize the PR's objective of making values configurable at deployment, consider accepting a uri parameter in addBase and passing it through from the options.

Apply this diff to parameterize the URI:

-function addBase(c: ContractBuilder, name: string, symbol: string, pausable: boolean) {
+function addBase(c: ContractBuilder, uri: string, name: string, symbol: string, pausable: boolean) {
   // Set metadata
-  c.addConstructorArgument({ name: 'uri', type: 'String', value: 'www.mytoken.com' });
+  c.addConstructorArgument({ name: 'uri', type: 'String', value: uri });
   c.addConstructorArgument({ name: 'name', type: 'String', value: name });
   c.addConstructorArgument({ name: 'symbol', type: 'String', value: symbol });
   c.addConstructorCode(`Base::set_metadata(e, uri, name, symbol);`);

Then update the call at line 90:

-  addBase(c, toByteArray(allOpts.name), toByteArray(allOpts.symbol), allOpts.pausable);
+  addBase(c, toByteArray('www.mytoken.com'), toByteArray(allOpts.name), toByteArray(allOpts.symbol), allOpts.pausable);
packages/core/stellar/src/print.ts (1)

381-403: Consider shell escaping for constructor argument values.

At line 394, arg.value is concatenated directly without shell escaping. Values containing spaces or special characters (like "Custom $ Token" shown in test snapshots at lines 726, 924, 1158) could produce invalid or unsafe shell commands if users copy-paste these comments.

Consider wrapping values in quotes or applying proper shell escaping for robustness.

Example approach:

 function printDeployCommand(args: ConstructorArgument[]): string[] {
   if (args.length === 0) {
     return [];
   }
 
   const cmd = [];
   cmd.push('// deploy this smart contract with the Stellar CLI:');
   cmd.push('//');
   cmd.push('// stellar contract deploy \\');
   cmd.push('// --wasm path/to/file.wasm \\');
   cmd.push('// -- \\');
   args.map((arg, i) => {
     if (arg.value) {
-      let formattedArg = `// --${arg.name} ${arg.value}`;
+      // Quote values that contain spaces or special characters
+      const needsQuoting = /[\s"'$\\]/.test(arg.value);
+      const quotedValue = needsQuoting ? `"${arg.value.replace(/"/g, '\\"')}"` : arg.value;
+      let formattedArg = `// --${arg.name} ${quotedValue}`;
       if (i !== args.length - 1) {
         formattedArg += ' \\';
       }
       cmd.push(formattedArg);
     }
   });
 
   return cmd;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c1fa0f and 019d4c5.

⛔ Files ignored due to path filters (4)
  • packages/core/stellar/src/fungible.test.ts.snap is excluded by !**/*.snap
  • packages/core/stellar/src/non-fungible.test.ts.snap is excluded by !**/*.snap
  • packages/core/stellar/src/stablecoin.test.ts.snap is excluded by !**/*.snap
  • packages/core/stellar/src/zip-rust.compile.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (12)
  • packages/core/stellar/README.md (1 hunks)
  • packages/core/stellar/src/contract.ts (4 hunks)
  • packages/core/stellar/src/fungible.test.ts.md (12 hunks)
  • packages/core/stellar/src/fungible.ts (2 hunks)
  • packages/core/stellar/src/non-fungible.test.ts.md (14 hunks)
  • packages/core/stellar/src/non-fungible.ts (1 hunks)
  • packages/core/stellar/src/print.ts (6 hunks)
  • packages/core/stellar/src/set-access-control.ts (3 hunks)
  • packages/core/stellar/src/stablecoin.test.ts.md (16 hunks)
  • packages/core/stellar/src/zip-rust.compile.test.ts.md (2 hunks)
  • packages/core/stellar/src/zip-shared.test.ts (3 hunks)
  • packages/core/stellar/src/zip-shared.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-12T20:50:43.101Z
Learnt from: CoveMB
Repo: OpenZeppelin/contracts-wizard PR: 644
File: packages/ui/api/services/open-ai.ts:43-56
Timestamp: 2025-09-12T20:50:43.101Z
Learning: In the OpenZeppelin contracts-wizard project, the current streaming implementation in packages/ui/api/services/open-ai.ts intentionally handles text deltas and function_calls with different formats (raw text chunks vs JSON objects). While this can cause JSON.parse issues, harmonizing the response format is considered outside scope of dependency updates as it requires UI refactoring to handle unified streaming responses.

Applied to files:

  • packages/core/stellar/src/zip-shared.test.ts
🧬 Code graph analysis (2)
packages/core/stellar/src/zip-shared.test.ts (1)
packages/core/stellar/src/zip-shared.ts (1)
  • printRustNameTest (46-67)
packages/core/stellar/src/print.ts (2)
packages/core/stellar/src/utils/format-lines.ts (1)
  • Lines (1-1)
packages/core/stellar/src/contract.ts (1)
  • ConstructorArgument (74-76)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (30)
packages/core/stellar/README.md (1)

16-16: LGTM!

The addition of "stablecoin" to the list of supported contract types is appropriate and aligns with the broader changes in the PR.

packages/core/stellar/src/set-access-control.ts (3)

35-35: LGTM!

The addition of the value field with a descriptive placeholder '<owner address>' provides helpful guidance for users during deployment.


53-53: LGTM!

Consistent use of the value field with placeholder '<admin address>' aligns with the ownable pattern.


96-96: LGTM!

The dynamic placeholder <${role} address> is a good pattern for role-based access control, making it clear what value should be provided.

packages/core/stellar/src/zip-shared.test.ts (2)

137-137: LGTM!

Typo correction from "bellow" to "below" improves code quality.


202-230: LGTM!

The new test case for single String constructor argument provides good coverage for the expanded constructor argument handling. The test validates that String-type arguments are properly formatted in the generated test scaffolding.

packages/core/stellar/src/fungible.ts (2)

92-94: LGTM!

The refactoring moves metadata initialization from hard-coded values to constructor arguments, enabling better contract reusability. The implementation correctly uses the parameters passed to addBase.


203-205: LGTM!

The premint handling now uses constructor arguments with appropriate placeholder values for recipient and the calculated premintAbsolute for premint. The simplification of the Base::mint call to use the constructor argument names improves readability.

packages/core/stellar/src/zip-shared.ts (3)

11-13: LGTM!

Helper function for formatting Rust String literals is well-scoped and reusable.


58-60: LGTM!

The refactoring to use getConstructorArgs instead of getAddressArgs properly handles all constructor argument types, not just Address types. This improves the test scaffolding generation.


23-44: <!-- [scratchpad_start]
[task verification_stellar_types 2/2 COMPLETE]
[observations]

  • Argument type in Stellar (contract.ts line 69-72): { name: string; type?: string }
  • ConstructorArgument extends Argument (line 74+)
  • getConstructorArgs handles only: 'Address', 'String', 'i128'
  • Test file shows getAddressArgs (different function) also silently filters unsupported types
  • Actual Stellar usage: ONLY 'Address', 'String', 'i128' used across all Stellar contracts
  • No instances of unsupported types found in Stellar codebase
  • Default case in getConstructorArgs CAN theoretically return empty string if type not in switch

[analysis]

  • Default case is reachable only if a type value outside {Address, String, i128} is used
  • Currently: NO unsupported types in use → default case never hits
  • Potentially: Risk if new Stellar types added in future (u64, bool, etc.)
  • Codebase pattern: getAddressArgs also silently filters, suggesting established approach
  • Current behavior: Works correctly for all actual use cases but lacks validation

[pending NONE - analysis complete]

[done]

  • Confirmed no unsupported types currently used in Stellar
  • Confirmed default case exists but not triggered in practice
  • Review concern is theoretically valid but not actively causing issues
    </scratchpad_end] -->

No unsupported constructor argument types are currently used in Stellar contracts.

The review comment's concern is theoretical: all constructor arguments in the Stellar codebase use only 'Address', 'String', or 'i128' types, so the default case that returns an empty string (line 40) is never reached in practice.

However, the underlying point about defensive programming is valid. If a new Stellar type were introduced but not added to the switch statement, the empty string would silently be filtered out, potentially resulting in generated Rust code with fewer arguments than expected. This could be addressed by adding explicit error handling for unsupported types rather than relying on filtering empty strings.

The codebase already follows a similar pattern elsewhere (e.g., getAddressArgs also silently filters unmatched types), suggesting this is an established approach rather than an oversight.

packages/core/stellar/src/zip-rust.compile.test.ts.md (3)

25-33: LGTM!

The addition of the deploy command comment in the generated constructor is valuable documentation that shows users exactly how to deploy the contract with the Stellar CLI, including all required arguments.


34-36: LGTM!

The constructor signature correctly reflects the parameterization of metadata (name, symbol) and minting parameters (recipient, premint), enabling contract reusability as described in the PR objectives.


67-67: LGTM!

The test setup correctly passes all four constructor arguments in the expected order, validating the new constructor signature.

packages/core/stellar/src/contract.ts (4)

74-76: LGTM!

The new ConstructorArgument interface extends Argument with an optional value field, enabling constructor arguments to carry default or placeholder values. This is a clean, backward-compatible extension that supports the PR's objectives.


10-10: LGTM!

Updating the constructorArgs type from Argument[] to ConstructorArgument[] properly reflects the new capability while maintaining backward compatibility.


86-86: LGTM!

The ContractBuilder property type update is consistent with the interface change.


248-248: LGTM!

The method signature correctly uses the new ConstructorArgument type, enabling callers to pass arguments with optional values.

packages/core/stellar/src/print.ts (3)

251-251: LGTM!

Correctly passes undefined for the deploy parameter when printing regular functions, since deployment commands are only relevant for constructors.


281-303: LGTM!

The constructor printing logic correctly generates deployment commands via printDeployCommand and passes them to printFunction2 for injection into the generated constructor.


307-326: LGTM!

The signature expansion and deployment command injection logic is correct. Deploy commands are conditionally injected only for constructors, maintaining clean output for regular functions.

packages/core/stellar/src/non-fungible.test.ts.md (3)

7-43: LGTM!

The basic non-fungible token test correctly demonstrates the updated constructor pattern with parameterized metadata (uri, name, symbol) and helpful deployment guidance comments.


91-165: LGTM!

The pausable non-fungible token test correctly extends the base pattern to include ownership configuration. The deploy command appropriately uses a placeholder for the owner address since it's a deployment-time decision.


899-990: Test correctly validates complex name handling.

This test demonstrates that the system handles token names with spaces and special characters. The deploy command comment at line 924 shows the name value without shell escaping, which relates to the escaping concern flagged in the print.ts review.

packages/core/stellar/src/stablecoin.test.ts.md (3)

7-42: LGTM!

The basic stablecoin test correctly demonstrates the parameterized constructor with name and symbol. The hardcoded 18 decimals at line 32 is appropriate for stablecoins.


256-294: LGTM!

The preminted stablecoin test correctly demonstrates the constructor extension with recipient and premint parameters. The deploy command at line 281 appropriately shows the configured premint value.


862-996: LGTM!

The comprehensive stablecoin test correctly demonstrates the most complex configuration with multiple roles. The constructor properly initializes metadata, minting, admin, and all role assignments using grant_role_no_auth at lines 910-912, which is appropriate for constructor-time initialization.

packages/core/stellar/src/fungible.test.ts.md (3)

7-42: LGTM!

The basic fungible token test correctly demonstrates the parameterized constructor pattern with name and symbol parameters.


296-331: LGTM - Smart optimization for zero premint.

This test correctly demonstrates that when premint is zero, the constructor omits the recipient and premint parameters entirely. This keeps the constructor interface minimal and avoids unnecessary parameters.


574-698: LGTM - Comprehensive role-based configuration.

This test correctly validates the most complex fungible token setup with multiple roles and upgradeability. The constructor signature at lines 608-618 is appropriately formatted across multiple lines for readability given the number of parameters.

@ElliotFriend
Copy link
Author

I confirm that I have read and hereby agree to the OpenZeppelin Contributor License Agreement

@ElliotFriend ElliotFriend changed the title Use constructor arguments in Stellar contracts [Stellar] Use constructor arguments in Stellar contracts Nov 11, 2025
return constructorArg.value
? stringFromStr(constructorArg.value)
: constructorArg.name === 'name'
? stringFromStr('MyToken')
Copy link
Contributor

Choose a reason for hiding this comment

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

In wich conditions would we want to return those hardcoded value?

Should we make it that only

return constructorArg.value
            ? stringFromStr(constructorArg.value)

is needed (maybe having default value upstream?

Copy link
Author

Choose a reason for hiding this comment

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

The situation here is essentially: use value if provided and giving a default/placeholder symbol/name/other if it's not.

Having a default would probably be a good approach, though. Since the value in the constructor arg type is optional, I wasn't really sure how to give it a default. I can make it a required parameter, if that would be better?

@CoveMB CoveMB requested review from a team as code owners November 13, 2025 17:32
@CoveMB CoveMB requested review from a team and removed request for a team November 13, 2025 17:33
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.

3 participants