Skip to content

Commit

Permalink
add demo for CPI
Browse files Browse the repository at this point in the history
  • Loading branch information
URANI committed Mar 27, 2024
1 parent 17216af commit 152a9d7
Show file tree
Hide file tree
Showing 17 changed files with 4,238 additions and 9 deletions.
16 changes: 8 additions & 8 deletions chapters/03_anchor.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ anchor --version
* An Anchor program consists of three parts:
- The `program` module: where the logic is written.
- The account structs, marked with `#[derive(Accounts)]`, where accounts are validated.
- The `declare_id` macro, which creates an `ID` field to store the address of the program.
- The `declare_id` macro, creates an `ID` field to store the address of the program.

<br>

Expand All @@ -61,7 +61,7 @@ anchor --version

<br>

* Where you define the business logic, by writing functions that can be called by clietns or other programs.
* Where you define the business logic by writing functions that clients or other programs can call:

```rust
#[program]
Expand All @@ -80,20 +80,20 @@ mod hello_anchor {

* Each endpoint takes a `Context` type as its first argument, so it can access:
- the accounts (`ctx.accounts`)
- the program_id (`ctx.program_id`) of the executing programs
- remaining accounts (`ctx.remaining_accounts`)
- the program_id (`ctx.program_id`) of the executing program
- remaining accounts (`ctx.remaining_accounts`)

<br>

* If a function requires instruction data, they can be added through arguments to the function after the context argument.
* If a function requires instruction data, it can be added through arguments to the function after the context argument.

<br>

#### The Accounts Struct

<br>

* Define which accounts a instruction expects, and their constraints.
* Define which accounts an instruction expects, and their constraints.

* There are two constructs:
- `Types`: have a specific use case.
Expand All @@ -120,7 +120,7 @@ pub struct SetData<'info> {

<br>

* Account types are not dynamic enough to handle all the security checks that the program requires
* Account types are not dynamic enough to handle all the security checks that the program requires.

* Constraints can be added through:

Expand All @@ -140,7 +140,7 @@ pub account: AccountType
- `AnchorErrors`: divided into anchor internal errors and custom errors
- non-anchor errors

* You can use the `require` macro to simplify writting errors.
* You can use the `require` macro to simplify writing errors.

```rust
#[program]
Expand Down
2 changes: 2 additions & 0 deletions demos/01_hello_world/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target
Cargo.lock
277 changes: 276 additions & 1 deletion demos/02_anchor_cpi/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,276 @@
# 🛹 Anchor and the Interface Definition Language [IN CONSTRUCTION]
# 🛹 Demo 2: Anchor and Cross-Program Invocations

<br>


### Program 1: "Puppet"

<br>

* To show how CPI works, let's create a simple program called puppet. You can use this demo or run `anchor init puppet`.

* Inside `src/lib.rs`, we write the following:

<br>


```rust
use anchor_lang::prelude::*;

declare_id!("<some string>");

#[program]
pub mod puppet {
use super::*;
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
Ok(())
}

pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
let puppet = &mut ctx.accounts.puppet;
puppet.data = data;
Ok(())
}
}

#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub puppet: Account<'info, Data>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct SetData<'info> {
#[account(mut)]
pub puppet: Account<'info, Data>,
}

#[account]
pub struct Data {
pub data: u64,
}
```

<br>

* Build the program with:

<br>

```
anchor build
```

<br>

---

### Program 2: "Puppet-Master"

<br>

* Let's create a second program called puppet-master, and write the following inside `src/lib.rs`:

<br>

```rust
use anchor_lang::prelude::*;
use puppet::cpi::accounts::SetData;
use puppet::program::Puppet;
use puppet::{self, Data};

declare_id!("<another string>");

#[program]
mod puppet_master {
use super::*;
pub fn pull_strings(ctx: Context<PullStrings>, data: u64) -> Result<()> {
let cpi_program = ctx.accounts.puppet_program.to_account_info();
let cpi_accounts = SetData {
puppet: ctx.accounts.puppet.to_account_info(),
};
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
puppet::cpi::set_data(cpi_ctx, data)
}
}

#[derive(Accounts)]
pub struct PullStrings<'info> {
#[account(mut)]
pub puppet: Account<'info, Data>,
pub puppet_program: Program<'info, Puppet>,
}
```

<br>

* To make it work, add the following inside the program's `Cargo.toml`:


<br>

```yaml
[package]
name = "puppet-master"
version = "0.1.0"
description = "Created with Anchor"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "puppet_master"

[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []

[dependencies]
anchor-lang = "0.29.0"
puppet = { path = "../../../puppet/programs/puppet", features = ["cpi"]}

```

<br>

* Build the program with:

```
anchor build
```

<br>


---

### Explanation

<br>

* Inside the puppet program, puppet-master uses the `SetData` instruction builder `struct` provided by the `puppet::cpi::accounts` module to submit the accounts the `SetData` instruction of the puppet program expects.

* This means that puppet-master creates a new cpi context and passes it to the `puppet::cpi::set_data` cpi function.

* This function has the exact same function as the `set_data` function in the puppet program with the exception that it expects a `CpiContext` instead of a `Context`.

<br>


---

### Testing

<br>

* Test the program with this file inside `puppet-master/test/puppet-master.ts`:

<br>

```typescript
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Keypair } from "@solana/web3.js";
import { expect } from "chai";
import { Puppet } from "../target/types/puppet";
import { PuppetMaster } from "../target/types/puppet_master";

describe("puppet", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);

const puppetProgram = anchor.workspace.Puppet as Program<Puppet>;
const puppetMasterProgram = anchor.workspace
.PuppetMaster as Program<PuppetMaster>;

const puppetKeypair = Keypair.generate();

it("Does CPI!", async () => {
await puppetProgram.methods
.initialize()
.accounts({
puppet: puppetKeypair.publicKey,
user: provider.wallet.publicKey,
})
.signers([puppetKeypair])
.rpc();

await puppetMasterProgram.methods
.pullStrings(new anchor.BN(42))
.accounts({
puppetProgram: puppetProgram.programId,
puppet: puppetKeypair.publicKey,
})
.rpc();

expect(
(
await puppetProgram.account.data.fetch(puppetKeypair.publicKey)
).data.toNumber()
).to.equal(42);
});
});
```

<br>

* Running:

<br>

```shell
cargo test
```

<br>

----

### Improvements

<br>

* It's recommended to move the CPI setup into the `impl` block of the instruction. The puppet-master program then looks like this:

<br>

```rust
use anchor_lang::prelude::*;
use puppet::cpi::accounts::SetData;
use puppet::program::Puppet;
use puppet::{self, Data};

declare_id!("HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L");

#[program]
mod puppet_master {
use super::*;
pub fn pull_strings(ctx: Context<PullStrings>, data: u64) -> Result<()> {
puppet::cpi::set_data(ctx.accounts.set_data_ctx(), data)
}
}

#[derive(Accounts)]
pub struct PullStrings<'info> {
#[account(mut)]
pub puppet: Account<'info, Data>,
pub puppet_program: Program<'info, Puppet>,
}


impl<'info> PullStrings<'info> {
pub fn set_data_ctx(&self) -> CpiContext<'_, '_, '_, 'info, SetData<'info>> {
let cpi_program = self.puppet_program.to_account_info();
let cpi_accounts = SetData {
puppet: self.puppet.to_account_info()
};
CpiContext::new(cpi_program, cpi_accounts)
}
}
```

7 changes: 7 additions & 0 deletions demos/02_anchor_cpi/puppet-master/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.anchor
.DS_Store
target
**/*.rs.bk
node_modules
test-ledger
.yarn
18 changes: 18 additions & 0 deletions demos/02_anchor_cpi/puppet-master/Anchor.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[toolchain]

[features]
seeds = false
skip-lint = false

[programs.localnet]
puppet_master = "DrL7fRZQKqF7GQuvzxYe4K6MV4inzodj8dhr1PPV8Fqr"

[registry]
url = "https://api.apr.dev"

[provider]
cluster = "Localnet"
wallet = "/Users/m/.config/solana/id.json"

[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
Loading

0 comments on commit 152a9d7

Please sign in to comment.