Skip to content

Commit

Permalink
Add example of how to use spl-token from Solidity
Browse files Browse the repository at this point in the history
Signed-off-by: Sean Young <[email protected]>
  • Loading branch information
seanyoung committed May 28, 2022
1 parent ba4204f commit 1be12c1
Show file tree
Hide file tree
Showing 9 changed files with 402 additions and 82 deletions.
76 changes: 0 additions & 76 deletions docs/language/imports.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,79 +72,3 @@ This also has a slightly more baroque syntax, which does exactly the same.
.. code-block:: solidity
import * as defs from "defines.sol";
Builtin Imports
_______________

Some builtin functionality is only available after importing. The following structs
can be imported via the special import file ``solana``.

.. code-block:: solidity
import {AccountMeta, AccountInfo} from 'solana';
Note that ``{AccountMeta, AccountInfo}`` can be omitted, renamed or imported via
import object.

.. code-block:: solidity
// Now AccountMeta will be known as AM
import {AccountMeta as AM} from 'solana';
// Now AccountMeta will be available as solana.AccountMeta
import 'solana' as solana;
.. note::

The import file ``solana`` is only available when compiling for the Solana
target.

.. _account_info:

Builtin AccountInfo
+++++++++++++++++++

The account info of all the accounts passed into the transaction. ``AccountInfo`` is a builtin
structure with the following fields:

address ``key``
The address (or public key) of the account

uint64 ``lamports``
The lamports of the accounts. This field can be modified, however the lamports need to be
balanced for all accounts by the end of the transaction.

bytes ``data```
The account data. This field can be modified, but use with caution.

address ``owner``
The program that owns this account

uint64 ``rent_epoch``
The next epoch when rent is due.

bool ``is_signer``
Did this account sign the transaction

bool ``is_writable``
Is this account writable in this transaction

bool ``executable``
Is this account a program

.. _account_meta:

Builtin AccountMeta
+++++++++++++++++++

When doing an external call (aka CPI), ``AccountMeta`` specifies which accounts
should be passed to the callee.

address ``pubkey``
The address (or public key) of the account

bool ``is_writable``
Can the callee write to this account

bool ``is_signer``
Can the callee assume this account signed the transaction
4 changes: 2 additions & 2 deletions docs/targets/burrow.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Hyperledger Burrow (ewasm)
__________________________
==========================

The ewasm specification is not finalized yet. There is no `create2` or `chainid` call, and the keccak256 precompile
contract has not been finalized yet.
Expand All @@ -15,4 +15,4 @@ Please use the latest master version of burrow, as ewasm support is still maturi

Some language features have not been fully implemented yet on ewasm:

- Contract storage variables types ``string``, ``bytes`` and function types are not implemented
- Contract storage variables types ``string``, ``bytes`` and function types are not implemented
92 changes: 91 additions & 1 deletion docs/targets/solana.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Solana
______
======

The Solana target requires `Solana <https://www.solana.com/>`_ v1.8.1.

Expand Down Expand Up @@ -72,3 +72,93 @@ The contract can be used via the `@solana/solidity <https://www.npmjs.com/packag
package has `documentation <https://solana-labs.github.io/solana-solidity.js/>`_ and there
are `some examples <https://solana-labs.github.io/solana-solidity.js/>`_. There is also
`solang's integration tests <https://github.com/hyperledger-labs/solang/tree/main/integration/solana>`_.


Builtin Imports
________________

Some builtin functionality is only available after importing. The following structs
can be imported via the special import file ``solana``.

.. code-block:: solidity
import {AccountMeta, AccountInfo} from 'solana';
Note that ``{AccountMeta, AccountInfo}`` can be omitted, renamed or imported via
import object.

.. code-block:: solidity
// Now AccountMeta will be known as AM
import {AccountMeta as AM} from 'solana';
// Now AccountMeta will be available as solana.AccountMeta
import 'solana' as solana;
.. note::

The import file ``solana`` is only available when compiling for the Solana
target.

.. _account_info:

Builtin AccountInfo
+++++++++++++++++++

The account info of all the accounts passed into the transaction. ``AccountInfo`` is a builtin
structure with the following fields:

address ``key``
The address (or public key) of the account

uint64 ``lamports``
The lamports of the accounts. This field can be modified, however the lamports need to be
balanced for all accounts by the end of the transaction.

bytes ``data```
The account data. This field can be modified, but use with caution.

address ``owner``
The program that owns this account

uint64 ``rent_epoch``
The next epoch when rent is due.

bool ``is_signer``
Did this account sign the transaction

bool ``is_writable``
Is this account writable in this transaction

bool ``executable``
Is this account a program

.. _account_meta:

Builtin AccountMeta
+++++++++++++++++++

When doing an external call (aka CPI), ``AccountMeta`` specifies which accounts
should be passed to the callee.

address ``pubkey``
The address (or public key) of the account

bool ``is_writable``
Can the callee write to this account

bool ``is_signer``
Can the callee assume this account signed the transaction

Using spl-token
_______________

`spl-token <https://spl.solana.com/token>`_ is the solana native way of creating tokens, minting, burning and
transfering tokens. We have created a library ``SplToken`` to use spl-token from Solidity. The file
`spl_token.sol <https://github.com/hyperledger-labs/solang/blob/main/examples/spl_token.sol>`_ should be copied into
your source tree, and then imported in your solidity files where it is required. The ``SplToken`` library has doc
comments explaining how it should be used.

There is an example in our integration tests of how this should be used, see
`token.sol <https://github.com/hyperledger-labs/solang/blob/main/integration/solana/token.sol>`_ and
`token.spec.ts <https://github.com/hyperledger-labs/solang/blob/main/integration/solana/token.spec.ts>`_.
2 changes: 1 addition & 1 deletion docs/targets/substrate.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Parity Substrate
________________
================

Solang works with Parity Substrate 2.0 or later.

Expand Down
174 changes: 174 additions & 0 deletions examples/spl_token.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import 'solana';

library SplToken {
address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
enum TokenInstruction {
InitializeMint, // 0
InitializeAccount, // 1
InitializeMultisig, // 2
Transfer, // 3
Approve, // 4
Revoke, // 5
SetAuthority, // 6
MintTo, // 7
Burn, // 8
CloseAccount, // 9
FreezeAccount, // 10
ThawAccount, // 11
TransferChecked, // 12
ApproveChecked, // 13
MintToChecked, // 14
BurnChecked, // 15
InitializeAccount2, // 16
SyncNative, // 17
InitializeAccount3, // 18
InitializeMultisig2, // 19
InitializeMint2, // 20
GetAccountDataSize, // 21
InitializeImmutableOwner, // 22
AmountToUiAmount, // 23
UiAmountToAmount, // 24
InitializeMintCloseAuthority, // 25
TransferFeeExtension, // 26
ConfidentialTransferExtension, // 27
DefaultAccountStateExtension, // 28
Reallocate, // 29
MemoTransferExtension, // 30
CreateNativeMint // 31
}

/// Mint new tokens. The transaction should be signed by the mint authority keypair
///
/// @param mint the account of the mint
/// @param account the token account where the minted tokens should go
/// @param authority the public key of the mint authority
/// @param amount the amount of tokens to mint
function mint_to(address mint, address account, address authority, uint64 amount) internal {
bytes instr = new bytes(9);

instr[0] = uint8(TokenInstruction.MintTo);
instr.writeUint64LE(amount, 1);

AccountMeta[3] metas = [
AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
AccountMeta({pubkey: authority, is_writable: true, is_signer: true})
];

tokenProgramId.call{accounts: metas}(instr);
}

/// Transfer @amount token from @from to @to. The transaction should be signed by the owner
/// keypair of the from account.
///
/// @param from the account to transfer tokens from
/// @param to the account to transfer tokens to
/// @param owner the publickey of the from account owner keypair
/// @param amount the amount to transfer
function transfer(address from, address to, address owner, uint64 amount) internal {
bytes instr = new bytes(9);

instr[0] = uint8(TokenInstruction.Transfer);
instr.writeUint64LE(amount, 1);

AccountMeta[3] metas = [
AccountMeta({pubkey: from, is_writable: true, is_signer: false}),
AccountMeta({pubkey: to, is_writable: true, is_signer: false}),
AccountMeta({pubkey: owner, is_writable: true, is_signer: true})
];

tokenProgramId.call{accounts: metas}(instr);
}

/// Burn @amount tokens in account. This transaction should be signed by the owner.
///
/// @param account the acount for which tokens should be burned
/// @param mint the mint for this token
/// @param owner the publickey of the account owner keypair
/// @param amount the amount to transfer
function burn(address account, address mint, address owner, uint64 amount) internal {
bytes instr = new bytes(9);

instr[0] = uint8(TokenInstruction.Burn);
instr.writeUint64LE(amount, 1);

AccountMeta[3] metas = [
AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
AccountMeta({pubkey: owner, is_writable: true, is_signer: true})
];

tokenProgramId.call{accounts: metas}(instr);
}

/// Approve an amount to a delegate. This transaction should be signed by the owner
///
/// @param account the account for which a delegate should be approved
/// @param delegate the delegate publickey
/// @param owner the publickey of the account owner keypair
/// @param amount the amount to approve
function approve(address account, address delegate, address owner, uint64 amount) internal {
bytes instr = new bytes(9);

instr[0] = uint8(TokenInstruction.Approve);
instr.writeUint64LE(amount, 1);

AccountMeta[3] metas = [
AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
AccountMeta({pubkey: delegate, is_writable: false, is_signer: false}),
AccountMeta({pubkey: owner, is_writable: false, is_signer: true})
];

tokenProgramId.call{accounts: metas}(instr);
}

/// Revoke a previously approved delegate. This transaction should be signed by the owner. After
/// this transaction, no delgate is approved for any amount.
///
/// @param account the account for which a delegate should be approved
/// @param owner the publickey of the account owner keypair
function revoke(address account, address owner) internal {
bytes instr = new bytes(1);

instr[0] = uint8(TokenInstruction.Revoke);

AccountMeta[2] metas = [
AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
AccountMeta({pubkey: owner, is_writable: false, is_signer: true})
];

tokenProgramId.call{accounts: metas}(instr);
}

/// Get the total supply for the mint, i.e. the total amount in circulation
/// @param mint the mint for this token
function total_supply(address mint) internal view returns (uint64) {
AccountInfo account = get_account_info(mint);

return account.data.readUint64LE(36);
}

/// Get the balance for an account.
///
/// @param account the account for which we want to know a balance
function get_balance(address account) internal view returns (uint64) {
AccountInfo ai = get_account_info(account);

return ai.data.readUint64LE(64);
}

/// Get the account info for an account. This walks the transaction account infos
/// and find the account info, or the transaction fails.
///
/// @param account the account for which we want to have the acount info.
function get_account_info(address account) internal view returns (AccountInfo) {
for (uint64 i = 0; i < tx.accounts.length; i++) {
AccountInfo ai = tx.accounts[i];
if (ai.key == account) {
return ai;
}
}

revert("account missing");
}
}
3 changes: 2 additions & 1 deletion integration/solana/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
"typescript": "^4.1.2"
},
"dependencies": {
"@solana/solidity": "0.0.18",
"@solana/solidity": "0.0.19",
"@solana/web3.js": "^1.30.2 <1.40.0",
"@solana/spl-token": "0.2.0",
"ethers": "^5.2.0",
"web3-eth-abi": "^1.3.0",
"web3-utils": "^1.3.0",
Expand Down
Loading

0 comments on commit 1be12c1

Please sign in to comment.