trident icon indicating copy to clipboard operation
trident copied to clipboard

πŸ› [BUG] - Trident codegen merges multiple programs with same type/instruction names => duplicate/conflicting structs in types.rs

Open Ectario opened this issue 5 months ago β€’ 3 comments

Description

Summary

When a workspace contains multiple Anchor programs that reuse the same type/instruction names but with different field layouts, trident init appears to flatten the IDLs into a single fuzz scaffold. The generated fuzz_0/types.rs defines the same struct name twice with different shapes, leading to duplicate/conflicting derives and compilation failures.

PoC (minimal)

Workspace layout

β”œβ”€β”€ Anchor.toml
β”œβ”€β”€ Cargo.lock
β”œβ”€β”€ Cargo.toml
β”œβ”€β”€ programs
β”‚   β”œβ”€β”€ program_a
β”‚   β”‚   β”œβ”€β”€ Cargo.toml
β”‚   β”‚   └── src
β”‚   β”‚       └── lib.rs
β”‚   └── program_b
β”‚       β”œβ”€β”€ Cargo.toml
β”‚       └── src
β”‚           └── lib.rs

programs/program_a/src/lib.rs

use anchor_lang::prelude::*;

declare_id!("2zT2BuYjCaiMvrYyuyG3EWAJpBJgtaV5n78wZdkDUxGw");

#[program]
pub mod program_a {
    use super::*;
    pub fn create_item(_ctx: Context<CreateItem>, _params: CreateItemParams) -> Result<()> {
        Ok(())
    }
}

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct CreateItemParams {
    pub name: String,
}

#[derive(Accounts)]
pub struct CreateItem {}

programs/program_b/src/lib.rs

use anchor_lang::prelude::*;

declare_id!("2zT2BuYwyEanAzmxp6wVLCxUx5zhbEZzngYqYEHRnK2P");

#[program]
pub mod program_b {
    use super::*;
    pub fn create_item(_ctx: Context<CreateItem>, _params: CreateItemParams) -> Result<()> {
        Ok(())
    }
}

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct CreateItemParams {
    pub name: String,
    pub extra: u64,
}

#[derive(Accounts)]
pub struct CreateItem {}

Suggested fix

Codegen should namespace per program (e.g., types/program_a.rs, types/program_b.rs, and similarly for instructions/ & transactions/), so identical names across programs don’t collide and each struct’s shape matches its program’s IDL.

Reproduction steps

1. Build to produce IDLs:


anchor build


2. Generate Trident fuzz scaffold:


trident init


3. Observe generated `fuzz_0/types.rs` contains **two** `CreateItemParams`:


#[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
pub struct CreateItemParams {
    pub name: String,
    pub extra: u64,
}

#[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
pub struct CreateItemParams {
    pub name: String,
}


4. Run fuzzer:


trident fuzz run fuzz_0

Reproduction URL

No response

Screenshots


Logs

error[E0119]: conflicting implementations of trait `Default` for type `types::CreateItemParams`
  --> fuzz_0/types.rs:15:58
   |
9  | #[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
   |                                                          ------- first implementation here
...
15 | #[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
   |                                                          ^^^^^^^ conflicting implementation for `types::CreateItemParams`

error[E0119]: conflicting implementations of trait `std::fmt::Debug` for type `types::CreateItemParams`
  --> fuzz_0/types.rs:15:10
   |
9  | #[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
   |          ----- first implementation here
...
15 | #[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
   |          ^^^^^ conflicting implementation for `types::CreateItemParams`

error[E0119]: conflicting implementations of trait `Clone` for type `types::CreateItemParams`
  --> fuzz_0/types.rs:15:51
   |
9  | #[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
   |                                                   ----- first implementation here
...
15 | #[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
   |                                                   ^^^^^ conflicting implementation for `types::CreateItemParams`

error[E0119]: conflicting implementations of trait `BorshDeserialize` for type `types::CreateItemParams`
  --> fuzz_0/types.rs:15:17
   |
9  | #[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
   |                 ---------------- first implementation here
...
15 | #[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
   |                 ^^^^^^^^^^^^^^^^ conflicting implementation for `types::CreateItemParams`
   |
   = note: this error originates in the derive macro `BorshDeserialize` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0119]: conflicting implementations of trait `BorshSerialize` for type `types::CreateItemParams`
  --> fuzz_0/types.rs:15:35
   |
9  | #[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
   |                                   -------------- first implementation here
...
15 | #[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
   |                                   ^^^^^^^^^^^^^^ conflicting implementation for `types::CreateItemParams`
   |
   = note: this error originates in the derive macro `BorshSerialize` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0063]: missing field `extra` in initializer of `types::CreateItemParams`
  --> fuzz_0/types.rs:15:17
   |
15 | #[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
   |                 ^^^^^^^^^^^^^^^^ missing `extra`
   |
   = note: this error originates in the derive macro `BorshDeserialize` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0063]: missing field `extra` in initializer of `types::CreateItemParams`
  --> fuzz_0/types.rs:15:51
   |
15 | #[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
   |                                                   ^ missing `extra`

error[E0063]: missing field `extra` in initializer of `types::CreateItemParams`
  --> fuzz_0/types.rs:15:58
   |
15 | #[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default)]
   |                                                          ^ missing `extra`

OS

Linux

Ectario avatar Sep 04 '25 15:09 Ectario

Thanks for saving the time of your fellow Trident user and reporting this issue for us all, @Ectario!

@lukacan, wdyt about adding the program name to the name of the type derived from it, in order to disambiguate the same-name-different-programs types?

e.g. [PROGRAM_NAME]_[TYPE_NAME]

IaroslavMazur avatar Sep 25 '25 07:09 IaroslavMazur

Hey guys, thanks for the issue, ye, I'm aware of this. Adding the program name at the start would help, but on the other hand, we are still exploring other options for how the folder layout could be structured, something that would make transaction creation much easier. Hopefully, this is nota big blocker for you, as I'm not 100% sure we will implement the fix immediately.

lukacan avatar Sep 28 '25 11:09 lukacan

we are still exploring other options for how the folder layout could be structured

No worries, take your time! A better solution > a fast one πŸ˜‰

Hopefully, this is nota big blocker for you

For now, I've fixed the issue manually.

IaroslavMazur avatar Sep 29 '25 09:09 IaroslavMazur