-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
lang: Program composition #454
Comments
Another important consideration is how program composition should work in the context of statically defined program IDs as suggested in #645, which is useful so that all CPI accounts and clients can have program ownership validation checks--which would eliminate easy to miss footguns that are exposed to anyone invoking the program via CPI. One way of accommodating this use case is by using meta macros when defining a library component, so that the statically defined program ID is available to all parts being composed. For example, we could define the initialize program above as // Initialize crate.
contract! {
#[program]
mod initialize {
fn initialize(Context<Initialize>) { ... }
}
#[account]
pub MyAccount {...}
}
// Update crate.
contract! {
#[program]
mod update {
fn update(Context<Update>) { ... }
}
#[account]
pub MyOtherAccount {...}
} Which would generate a new // These import lines can be hidden/called inside the program macro.
initialize::import!();
update::import!();
declare_id!("81ikgMpXEEjUH9pBWPvFM5uBKv4FQgYBa4bMpJryY3Sd");
#[program(initialize, update)]
mod composed {
fn delete(Context<Delete>) { ... }
} |
If I can ask it, why is this even necessary?? If I'm not completely wrong, programs are not so long in terms of number of instructions, therefore doing something like the following does not imply too many more code or maintenance, even if you use many programs (like I do). #[program]
mod foo {
fn initialize(Context<Initialize>) { ... }
} #[program]
mod bar {
fn update(Context<Update>) { ... }
} #[program]
mod composed {
fn initialize(state: Context<foo::state::Initialize>) { foo::instructions::initialize(state) }
fn update(state: Context<bar::state::Update>) { bar::instructions::update(state) }
fn delete(Context<Delete>) {...}
} With this simple approach you get rid of almost all your points, the only one that remains is (2) or how to make the Anchor CLI read imported modules to get the necessary info from them. |
One should be able to compose programs together similar to traits or mixins to combine the behavior of different programs.
For example, suppose you had the following two programs
Then one could combine these two programs into one with some syntax like
Where the program would have three instructions:
initialize
,update
, anddelete
.There are several important questions to answer:
sha256(<mod-name>:<method-name>)[..8]
. Then one could use instructions with duplicate identifiers as long as the mod names were distinct. I prefer the last option.[idl]
section to the Anchor.toml that tells the CLI the location of the IDLs of all the programs being composed together. The CLI will take that information and merge together all IDLs for each program being used into a single IDL. This "location" can be a program id, a filepath, or a git url. To start, we can simply use a filepath.update
in the example above, we can do something likeprogram.bar.rpc.update
. We could also consider putting the methods on the main program object, e.g.,program.rpc.update
if there are no collisions and so the name is not ambiguous. (Tangent. Instead of putting the client namespaces, e.g.,rpc
before the instruction name, we should move them to the end, which feels more naturally, particularly in the context of composing programs. Though this is a separate issue.)cpi
namespaces for each composed program under the program's generatedcpi
module. So to callupdate
in the example above, we would havecomposed::cpi::bar::update
(or maybecomposed::cpi::update
depending on if/how conflict resolution is done).accounts
andinstructions
generated modules.Edit. A slight modification we might want to make here is to leverage the trait system. So we can wrap all the instructions here with a struct and impl block that implements a trait, similar to the current
#[interface]
mechanism.The text was updated successfully, but these errors were encountered: