You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This document proposes a Solana ABI to make it possible to create different
implementations of the same interface.
Having an ABI makes it possible to create mintable tokens, non-mintable
tokens, interest bearing tokens, rebase tokens, etc. without overloading one
single token implementation.
Furthermore, it would be possible for DeFi products to support different types of
tokens as long as they conform to the same standard interface. Right now, the
logic of SPL Token is hardwired into projects like spl-swap, spl-lend, and
serum.
The important ideas to enable ABI are:
An interface defines messages passed to a target.
A target is a tuple of (program_id, accounts).
The program id specifies the implementation
The accounts specify the state of the target
In a message, the referenced accounts are encoded as indexes
Processor dispatches on messages using selectors, instead of hard coded enum
The selector is sha256 of the message signature.
This makes it possible to create a program that "mix and match" messages
from different crates.
Handle program accounts and normal accounts transparently in the ABI encoding.
This is important if a program need to invoke another program, yet we want
to keep the ABI the same.
First a high-level overview of the ABI "from the outside", then a binary
encoding for instructions is given at the end.
A Mintable Token
Let's define an interface with a single mint message:
An example of using an ABI client to mint some tokens:
// the programID can be any implementationconsttoken=newInterface(TokenABI,programID)token.send("mint",{// not signed, pass in PubKeytoken: tokenAddress,// PubKey// signed, pass in an Accountauth: tokenOwnerAccount,// Account// this should be deduplicateddest: tokenOwnerAccount.pubkey,// PubKeyamount: 100,})
The ABI client should generate the transaction by doing two things:
Convert the accounts referenced by the ABI to indexes, doing deduplication.
Collect the referenced accounts into an account_info array.
Suppose that we want to build a token faucet that has minting authority, and
supports arbitrary token programs as long as the mint ABI interface is
satisfied.
In Solidity it might look like this:
functiongive(IERC20token,uint64amount,addressreceiver){require(token.owner==address(this),"faucet is not owner of token");token.mint(receiver,amount);}
The Solana ABI needs to have a way to encode two things:
The target that should receive the mint message.
Support using a program account as the minting authority.
The ABI client might look like:
consttokenOwnerAccount=newProgramAccount(programID,"program token owner")faucet.send("give",{// this specifies the target that will receive a `mint` message using invoke_signedtoken: [tokenProgramID,tokenAddress],// signed program accountauth: tokenOwnerAccount,// Accountdest: receiverAddress,// PubKeyamount: 100,})
In this case, the generated transaction needs to also provide the program seed:
Convert the accounts referenced by the ABI to indexes, doing deduplication.
If the referenced account is a program account, it should also encode the
index of the seed used.
Collect the referenced accounts into an account_info array.
Collect the referenced program seeds into an array, doing deduplication.
The transaction would be like:
instruction_data: newGiveInstruction({token: (0,1),auth: (2,0),dest: 3,amount: 100,}).encode()
seeds: [// the seed is the user given seed plus the nonce["program owner",1]]
account_infos: [tokenProgramID,tokenAddress,// writetokenOwnerAccount,// signedreceiverAddress,// signed]
auth is now a tuple that refers to both program account info and
program seed, so that invoke_signed may be called to mint the token.
token is now a tuple, the first is the program id, and the second is the
token address for that program.
The faucet program then use this information to send a message to the target token with invoke_signed.
instruction_data: newMintMessage({token: 1,auth: (2,0),dest: 3,amount: 100,}).encode()
seeds: [// the seed is the user given seed plus the nonce["program owner",1]]
target_program: accounts[0]
Faucet With Counter
By encoding account indexes in the message, the programs do not need to agree on
the order of the accounts in a transaction.
Suppose that we want to extend the faucet with a counter to keep track of how
much supply the faucet had given out. We add a counter address to store that
data in the give message:
instruction_data: newGiveMessage({counter: 0,token: (1,2),auth: (3,0),dest: 4,amount: 100,}).encode()
seeds: [// the seed is the user given seed plus the nonce["program owner",1]]
account_infos: [counterAddress// writetokenProgramID,tokenAddress,// writetokenOwnerAccount,// signedreceiverAddress,// write]
Although the indexes are shifted by one, the logic that deals with invoke_signed does not have to change at all. Nor does the token program rely
on accounts being passed in a fixed order.
SPL Token Compatibility
With an ABI token defined, it should be possible to create a wrapper for
SPL tokens to handle them in the same way as ABI tokens.
TODO Reader Interface
There need to be a way to define standard reader APIs, for both programs and ABI
clients to consume.
A typical example is the balanceOf interface. The states of different program
implementation may be different, and without a reader to compute value from
state is needed.
Readers would be like serverless lambda functions... would be awesome if it's possible to
subscribe to these lambda functions if the underlying states changed.
Encoding of a Message Into an Instruction
A message needs to specify:
The binary encoded message (e.g. with borsh).
Program seeds.
The mint token ABI would be calculated similar to Solidity's selector, using
the sha256 hash of its signature:
The account indexes are encoded as a struct of 2 bytes:
structAccountIndex{account: u8,}// if this is a program account that needs to invoke_signedstructSignedProgramAccountIndex{account: u8,seed: u8,}structTarget{program_id: u8,account: u8,}
The mint message with account indexes:
struct Mint {
token: AccountIndex,
from: AccountIndex,
from_owner: AccountIndex,
to: AccountIndex,
amount: u64,
}
This is a very well thought-out proposal and answers a lot of questions that have come up in our ABI discussions, thanks for the great work!
A lot of this sounds very workable. We've been wondering about parameter differences in ABIs for our particular usecase of interest-bearing tokens. In order to mint tokens, imagine that we have to accrue interest, which means that we have to either send an additional instruction beforehand to accrue interest, or send the clock sysvar along with the normal parameters defined in the mint ABI.
How would you modify your model to make that work? It seems like it might be independent of your solution, requiring an additional hook implemented by the program library. Or the ABI solution includes an on-chain registry that gives more info about requirements of specific programs.
Solana Program ABI
This document proposes a Solana ABI to make it possible to create different
implementations of the same interface.
Having an ABI makes it possible to create mintable tokens, non-mintable
tokens, interest bearing tokens, rebase tokens, etc. without overloading one
single token implementation.
Furthermore, it would be possible for DeFi products to support different types of
tokens as long as they conform to the same standard interface. Right now, the
logic of SPL Token is hardwired into projects like spl-swap, spl-lend, and
serum.
The important ideas to enable ABI are:
(program_id, accounts)
.from different crates.
to keep the ABI the same.
First a high-level overview of the ABI "from the outside", then a binary
encoding for instructions is given at the end.
A Mintable Token
Let's define an interface with a single
mint
message:An example of using an ABI client to mint some tokens:
The ABI client should generate the transaction by doing two things:
A Faucet Program
Suppose that we want to build a token faucet that has minting authority, and
supports arbitrary token programs as long as the
mint
ABI interface issatisfied.
In Solidity it might look like this:
The Solana ABI needs to have a way to encode two things:
target
that should receive themint
message.The ABI client might look like:
In this case, the generated transaction needs to also provide the program seed:
index of the seed used.
The transaction would be like:
auth
is now a tuple that refers to both program account info andprogram seed, so that
invoke_signed
may be called to mint the token.token
is now a tuple, the first is the program id, and the second is thetoken address for that program.
The faucet program then use this information to send a message to the target token with
invoke_signed
.Faucet With Counter
By encoding account indexes in the message, the programs do not need to agree on
the order of the accounts in a transaction.
Suppose that we want to extend the faucet with a counter to keep track of how
much supply the faucet had given out. We add a
counter
address to store thatdata in the
give
message:Although the indexes are shifted by one, the logic that deals with
invoke_signed
does not have to change at all. Nor does the token program relyon accounts being passed in a fixed order.
SPL Token Compatibility
With an ABI token defined, it should be possible to create a wrapper for
SPL tokens to handle them in the same way as ABI tokens.
TODO Reader Interface
There need to be a way to define standard reader APIs, for both programs and ABI
clients to consume.
A typical example is the
balanceOf
interface. The states of different programimplementation may be different, and without a reader to compute value from
state is needed.
Readers would be like serverless lambda functions... would be awesome if it's possible to
subscribe to these lambda functions if the underlying states changed.
Encoding of a Message Into an Instruction
A message needs to specify:
The
mint
token ABI would be calculated similar to Solidity's selector, usingthe sha256 hash of its signature:
It differs from Solidity selector in the following ways:
The transaction data's layout:
A program should use the selector to lookup the corresponding message type, and
ecode the message using borsh.
The
accounts
are provided to the message processor, to associate the encodedaccount indexes with account info.
Furthermore,
seeds
(if any) intransaction_data
are provided to the messageprocessor, if it needs to call
invoke_signed
message encoding size
Assuming all the structs are encoded using borsch.
The representation for dynamic Vector has 4 bytes of overhead for length:
We could use varuint to encode the
top-level instruction:
size <= 240
, it takes one byte.size <= 2031
, it takes two bytes.The account indexes are encoded as a struct of 2 bytes:
The
mint
message with account indexes:The text was updated successfully, but these errors were encountered: