Skip to content

Commit

Permalink
docs updates
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec committed Aug 15, 2023
1 parent ab34a86 commit 68ec0fa
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 36 deletions.
66 changes: 64 additions & 2 deletions docs/src/token-2022/extensions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1347,6 +1347,10 @@ A usable example program exists at
Token-2022 uses this example program in tests to ensure that it properly uses
the transfer hook interface.

The example program and the interface are powered by the
[spl-tlv-account-resolution](https://github.com/solana-labs/solana-program-library/tree/master/libraries/tlv-account-resolution/README.md)
library, which is explained a bit more below.

#### How to use

Developers must implement the `Execute` instruction, and optionally the
Expand All @@ -1358,8 +1362,66 @@ at that instruction descriminator. Your program may implement multiple interface
so any other instruction in your program can create the account at the program-derived
address!

More information in the
[spl-transfer-hook-interface README](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook-interface/README.md).
When your program stores configurations for extra required accounts in the
well-defined program-derived address, it's possible to send an instruction -
such as `Execute` (transfer) - to your program with only accounts required
for the interface instruction, and all extra required accounts are
automatically resolved!

#### Account Resolution

Implementers of the transfer-hook interface are encouraged to make use of the
[spl-tlv-account-resolution](https://github.com/solana-labs/solana-program-library/tree/master/libraries/tlv-account-resolution/README.md)
library to manage the additional required accounts for their transfer hook
program.

This library is capable of storing two types of configurations for additional
required accounts:

- Accounts with a fixed address
- Accounts with a **dynamic program-derived address** derived from seeds that
may come from any combination of the following:
- Hard-coded values, such as string literals or integers
- A slice of the instruction data provided to the transfer-hook program
- The address of another account in the total list of accounts

When you store configurations for a dynamic Program-Derived Address within the
additional required accounts, the PDA itself is evaluated (or resolved) at the
time of instruction invocation using the instruction itself. This
occurs in the offchain and onchain helpers mentioned below, which leverage
the SPL TLV Account Resolution library to perform this resolution
automatically.

#### An Example

You have created a DAO to govern a community. Your DAO's authority is a
multisig account, and you want to ensure that any transfer of your token is
approved by the DAO. You also want to make sure that someone who intends to
transfer your token has the proper permissions to do so.

Let's assume the DAO multisig has some **fixed address**. And let's assume that
in order to have the `can_transfer` permission, a user must have this
**dynamic program-derived address** associated with their wallet via the
following seeds: `"can_transfer" + <wallet_address>`.

Using the transfer-hook interface, you can store these configurations in the
well-defined program-derived address for your mint and program id.

When a user attempts to transfer your token, they might provide to Token-2022:

```rust
[source, mint, destination, owner/delegate]
```

Token-2022 will then call into your program,
**resolving the extra required accounts automatically** from your stored
configurations, to result in the following accounts being provided to your
program:

```rust
[source, mint, destination, owner/delegate, dao_authority, can_transfer_pda]
```


#### Utilities

Expand Down
34 changes: 28 additions & 6 deletions libraries/tlv-account-resolution/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ impl SplDiscriminate for MyInstruction {
const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([1; ArrayDiscriminator::LENGTH]);
}

// Actually put it in the additional required account keys and signer / writable
// Prepare the additional required account keys and signer / writable
let extra_metas = [
AccountMeta::new(Pubkey::new_unique(), false).into(),
AccountMeta::new(Pubkey::new_unique(), true).into(),
AccountMeta::new_readonly(Pubkey::new_unique(), true).into(),
AccountMeta::new_readonly(Pubkey::new_unique(), false).into(),
];

// Assume that this buffer is actually account data, already allocated to `account_size`
// Allocate a new buffer with the proper `account_size`
let account_size = ExtraAccountMetaList::size_of(extra_metas.len()).unwrap();
let mut buffer = vec![0; account_size];

Expand Down Expand Up @@ -57,7 +57,7 @@ ExtraAccountMetaList::add_to_cpi_instruction::<MyInstruction>(
).unwrap();
```

For ease of use on-chain, `ExtraAccountMetaList::init_with_account_infos` is also
For ease of use on-chain, `ExtraAccountMetaList::init` is also
provided to initialize directly from a set of given accounts.

## Motivation
Expand All @@ -83,7 +83,7 @@ uses static account data.

For example, let's imagine there's a `Transferable` interface, along with a
`transfer` instruction. Some programs that implement `transfer` may need more
accounts than just the ones defined in the interface. How does a an on-chain or
accounts than just the ones defined in the interface. How does an on-chain or
off-chain client figure out the additional required accounts?

The "static" approach requires programs to write the extra required accounts to
Expand All @@ -99,13 +99,35 @@ instruction and give the correct account infos.

This approach could also be called a "state interface".

### Types of Required Accounts

This library is capable of storing two types of configurations for additional
required accounts:

- Accounts with a fixed address
- Accounts with a **dynamic program-derived address** derived from seeds that
may come from any combination of the following:
- Hard-coded values, such as string literals or integers
- A slice of the instruction data provided to the transfer-hook program
- The address of another account in the total list of accounts

When you store configurations for a dynamic Program-Derived Address within the
additional required accounts, the PDA itself is evaluated (or resolved) at the
time of instruction invocation using the instruction itself. This
occurs in the offchain and onchain helpers mentioned below, which leverage
the SPL TLV Account Resolution library to perform this resolution
automatically.

## How it works

This library uses `spl-type-length-value` to read and write required instruction
accounts from account data.

Interface instructions must have an 8-byte discriminator, so that the exposed
`ExtraAccountMetaList` type can use the instruction discriminator as a `ArrayDiscriminator`.
`ExtraAccountMetaList` type can use the instruction discriminator as an
`ArrayDiscriminator`, which allows that discriminator to serve as a unique TLV
discriminator for identifying entries that correspond to that particular
instruction.

This can be confusing. Typically, a type implements `SplDiscriminate`, so that
the type can be written into TLV data. In this case, `ExtraAccountMetaList` is
Expand Down Expand Up @@ -136,4 +158,4 @@ into return data, making it easy for on-chain and off-chain clients to consume.

See the
[relevant sRFC](https://forum.solana.com/t/srfc-00010-additional-accounts-request-transfer-spec/122)
for more information about the dynamic approach.
for more information about the dynamic approach.
111 changes: 83 additions & 28 deletions token/transfer-hook-interface/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,34 +38,25 @@ pub fn process_instruction(
return Err(ProgramError::InvalidSeeds);
}

// Get the extra account metas from the account data
// Load the extra required accounts from the validation account
let data = extra_account_metas_info.try_borrow_data()?;
let state = TlvStateBorrowed::unpack(&data).unwrap();
let extra_account_metas =
ExtraAccountMetaList::unpack_with_tlv_state::<ExecuteInstruction>(&state)?;

// If incorrect number of accounts is provided, error
let extra_account_infos = account_info_iter.as_slice();
let account_metas = extra_account_metas.data();
if extra_account_infos.len() != account_metas.len() {
return Err(ProgramError::InvalidInstructionData);
}

// Let's require that they're provided in the correct order
for (i, account_info) in extra_account_infos.iter().enumerate() {
if &account_metas[i] != account_info {
return Err(ProgramError::InvalidInstructionData);
}
}
// Check the provided accounts against the validation data
ExtraAccountMetaList::check_account_infos::<ExecuteInstruction>(
accounts,
&TransferHookInstruction::Execute { amount }.pack(),
program_id,
&data,
)?;

Ok(())
}
```

### Motivation

Token creators may need more control over transfers of their token. The most
prominent use case revolves around NFT royalties. Whenever a token is moved,
Token creators may need more control over how their token is transferred. The
most prominent use case revolves around NFT royalties. Whenever a token is moved,
the creator should be entitled to royalties, but due to the design of the current
token program, it's impossible to stop a transfer at the protocol level.

Expand All @@ -79,28 +70,92 @@ harder.

### Solution

To give more flexibility to token creators and improve the situation for everyone,
`spl-transfer-hook-interface` introduces the concept of an interface integrated
with `spl-token-2022`. A token creator must develop and deploy a program that
To improve the situation, Token-2022 introduces the concept of the transfer-hook
interface and extension. A token creator must develop and deploy a program that
implements the interface and then configure their token mint to use their program.

During transfer, token-2022 calls into the program with the accounts specified
During transfer, Token-2022 calls into the program with the accounts specified
at a well-defined program-derived address for that mint and program id. This
call happens after all other transfer logic, so the accounts reflect the *end*
state of the transfer.

A developer must implement the `Execute` instruction, and the
### How to Use

Developers must implement the `Execute` instruction, and optionally the
`InitializeExtraAccountMetaList` instruction to write the required additional account
pubkeys into the program-derived address defined by the mint and program id.

Side note: it's technically not required to implement `InitializeExtraAccountMetaList`
Note: it's technically not required to implement `InitializeExtraAccountMetaList`
at that instruction descriminator. Your program may implement multiple interfaces,
so any other instruction in your program can create the account at the program-derived
address!

This library provides offchain and onchain helpers for resolving the additional
accounts required. See
When your program stores configurations for extra required accounts in the
well-defined program-derived address, it's possible to send an instruction -
such as `Execute` (transfer) - to your program with only accounts required
for the interface instruction, and all extra required accounts are
automatically resolved!

### Account Resolution

Implementers of the transfer-hook interface are encouraged to make use of the
[spl-tlv-account-resolution](https://github.com/solana-labs/solana-program-library/tree/master/libraries/tlv-account-resolution/README.md)
library to manage the additional required accounts for their transfer hook
program.

This library is capable of storing two types of configurations for additional
required accounts:

- Accounts with a fixed address
- Accounts with a **dynamic program-derived address** derived from seeds that
may come from any combination of the following:
- Hard-coded values, such as string literals or integers
- A slice of the instruction data provided to the transfer-hook program
- The address of another account in the total list of accounts

When you store configurations for a dynamic Program-Derived Address within the
additional required accounts, the PDA itself is evaluated (or resolved) at the
time of instruction invocation using the instruction itself. This
occurs in the offchain and onchain helpers mentioned below, which leverage
the SPL TLV Account Resolution library to perform this resolution
automatically.

### An Example

You have created a DAO to govern a community. Your DAO's authority is a
multisig account, and you want to ensure that any transfer of your token is
approved by the DAO. You also want to make sure that someone who intends to
transfer your token has the proper permissions to do so.

Let's assume the DAO multisig has some **fixed address**. And let's assume that
in order to have the `can_transfer` permission, a user must have this
**dynamic program-derived address** associated with their wallet via the
following seeds: `"can_transfer" + <wallet_address>`.

Using the transfer-hook interface, you can store these configurations in the
well-defined program-derived address for your mint and program id.

When a user attempts to transfer your token, they might provide to Token-2022:

```rust
[source, mint, destination, owner/delegate]
```

Token-2022 will then call into your program,
**resolving the extra required accounts automatically** from your stored
configurations, to result in the following accounts being provided to your
program:

```rust
[source, mint, destination, owner/delegate, dao_authority, can_transfer_pda]
```

### Utilities

The `spl-transfer-hook-interface` library provides offchain and onchain helpers
for resolving the additional accounts required. See
[invoke.rs](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook-interface/src/invoke.rs)
for usage on-chain, and
[offchain.rs](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook-interface/src/offchain.rs)
for fetching the additional required account metas.
for fetching the additional required account metas with any async off-chain client
like `BanksClient` or `RpcClient`.

0 comments on commit 68ec0fa

Please sign in to comment.