Browse Source

Create Solana's System Instruction library and 'solana-library' folder (#996)

* Move spl-token to solana-library

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>

* Create SystemInstruction libary

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>

* Update documentation

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>

* Add license headers and disclaimer

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>
Lucas Steuernagel 3 years ago
parent
commit
6a055a8851

+ 33 - 9
docs/targets/solana.rst

@@ -217,18 +217,42 @@ the provided seeds, along with a seed bump. See the Solana documentation on
         }
         }
     }
     }
 
 
-Using spl-token
-_______________
 
 
-`spl-token <https://spl.solana.com/token>`_ is the solana native way of creating tokens, minting, burning and
-transfering token. This is the Solana equivalent of
+
+Solana Library
+______________
+
+In Solang's Github repository, there is a directory called ``solana-library``. It contains libraries for Solidity contracts
+to interact with Solana specific instructions. Currently, there are two libraries there: one for SPL tokens and another
+for Solana's system instructions. In order to use those functionalities, copy the correspondent library
+file to your project and import it.
+
+SPL-token
++++++++++
+
+`spl-token <https://spl.solana.com/token>`_ is the Solana native way of creating tokens, minting, burning and
+transferring token. This is the Solana equivalent of
 `ERC-20 <https://ethereum.org/en/developers/docs/standards/tokens/erc-20/>`_ and
 `ERC-20 <https://ethereum.org/en/developers/docs/standards/tokens/erc-20/>`_ and
-`ERC-721 <https://ethereum.org/en/developers/docs/standards/tokens/erc-721/>`_. We have created a library ``SplToken`` to use
-spl-token from Solidity. The file
-`spl_token.sol <https://github.com/hyperledger/solang/blob/main/examples/spl_token.sol>`_  should be copied into
+`ERC-721 <https://ethereum.org/en/developers/docs/standards/tokens/erc-721/>`_. Solang's repository contains
+a library ``SplToken`` to use spl-token from Solidity. The file
+`spl_token.sol <https://github.com/hyperledger/solang/blob/main/solana-library/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
 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.
 comments explaining how it should be used.
 
 
-There is an example in our integration tests of how this should be used, see
+There is an example in our integration tests of how this should be used. See
 `token.sol <https://github.com/hyperledger/solang/blob/main/integration/solana/token.sol>`_ and
 `token.sol <https://github.com/hyperledger/solang/blob/main/integration/solana/token.sol>`_ and
-`token.spec.ts <https://github.com/hyperledger/solang/blob/main/integration/solana/token.spec.ts>`_.
+`token.spec.ts <https://github.com/hyperledger/solang/blob/main/integration/solana/token.spec.ts>`_.
+
+System Instructions
++++++++++++++++++++
+
+Solana's system instructions enables developers to interact with Solana's System Program. There are functions to
+create new accounts, allocate account data, assign accounts to owning programs, transfer lamports from System Program
+owned accounts and pay transaction fees. More information about the functions offered can be found both on
+`Solana documentation <https://docs.rs/solana-program/1.11.10/solana_program/system_instruction/enum.SystemInstruction.html>`_
+and on Solang's `system_instruction.sol <https://github.com/hyperledger/solang/blob/main/solana-library/system_instruction.sol>`_ file.
+
+The usage of system instructions needs the correct setting of writable and signer accounts when interacting with Solidity
+contracts on chain. Examples are available on Solang's integration tests.
+See `system_instruction_example.sol <https://github.com/hyperledger/solang/blob/main/integration/solana/system_instruction_example.sol>`_
+and `system_instruction.spec.ts <https://github.com/hyperledger/solang/blob/main/integration/solana/system_instruction.spec.ts>`_

+ 6 - 6
integration/solana/setup.ts

@@ -44,11 +44,7 @@ async function newAccountWithLamports(connection: Connection): Promise<Keypair>
     const account = Keypair.generate();
     const account = Keypair.generate();
 
 
     console.log('Airdropping SOL to a new wallet ...');
     console.log('Airdropping SOL to a new wallet ...');
-    let signature = await connection.requestAirdrop(account.publicKey, LAMPORTS_PER_SOL);
-    await connection.confirmTransaction(signature, 'confirmed');
-    signature = await connection.requestAirdrop(account.publicKey, LAMPORTS_PER_SOL);
-    await connection.confirmTransaction(signature, 'confirmed');
-    signature = await connection.requestAirdrop(account.publicKey, LAMPORTS_PER_SOL);
+    let signature = await connection.requestAirdrop(account.publicKey, 16*LAMPORTS_PER_SOL);
     await connection.confirmTransaction(signature, 'confirmed');
     await connection.confirmTransaction(signature, 'confirmed');
 
 
     return account;
     return account;
@@ -56,7 +52,11 @@ async function newAccountWithLamports(connection: Connection): Promise<Keypair>
 
 
 
 
 async function setup() {
 async function setup() {
-    const connection = new Connection(endpoint, 'confirmed');
+
+    const connection = new Connection(endpoint, {
+        commitment: "confirmed",
+        confirmTransactionInitialTimeout: 100000,
+    });
     const payer = await newAccountWithLamports(connection);
     const payer = await newAccountWithLamports(connection);
 
 
     const program = Keypair.generate();
     const program = Keypair.generate();

+ 237 - 0
integration/solana/system_instruction.spec.ts

@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: Apache-2.0
+
+import {loadContract} from "./setup";
+import {Keypair, LAMPORTS_PER_SOL, PublicKey} from "@solana/web3.js";
+import {publicKeyToHex} from "@solana/solidity";
+import {TOKEN_PROGRAM_ID} from "@solana/spl-token";
+
+
+describe('Test system instructions', function() {
+    this.timeout(500000);
+    const system_account = new PublicKey('11111111111111111111111111111111');
+    const recent_block_hashes = new PublicKey('SysvarRecentB1ockHashes11111111111111111111');
+    const rentAddress = new PublicKey('SysvarRent111111111111111111111111111111111');
+    const seed = 'my_seed_is_tea';
+
+    it('create account', async function create_account() {
+        const { contract, connection, payer, program } = await loadContract('TestingInstruction', 'TestingInstruction.abi');
+        const to_key_pair = Keypair.generate();
+
+        await contract.functions.create_account(
+            publicKeyToHex(payer.publicKey),
+            publicKeyToHex(to_key_pair.publicKey),
+            100000000,
+            5,
+            publicKeyToHex(TOKEN_PROGRAM_ID),
+            {
+                accounts: [system_account, TOKEN_PROGRAM_ID],
+                writableAccounts: [payer.publicKey, to_key_pair.publicKey],
+                signers: [payer, to_key_pair],
+            }
+        );
+    });
+
+    it('create account with seed', async function create_account_with_seed() {
+        const { contract, connection, payer, program } = await loadContract('TestingInstruction', 'TestingInstruction.abi');
+        const base_keypair = Keypair.generate();
+        const to_key_pair = await PublicKey.createWithSeed(base_keypair.publicKey, seed, TOKEN_PROGRAM_ID);
+
+        await contract.functions.create_account_with_seed(
+            publicKeyToHex(payer.publicKey),
+            publicKeyToHex(to_key_pair),
+            publicKeyToHex(base_keypair.publicKey),
+            seed,
+            100000000,
+            5,
+            publicKeyToHex(TOKEN_PROGRAM_ID),
+            {
+                accounts: [system_account, TOKEN_PROGRAM_ID],
+                writableAccounts: [payer.publicKey, to_key_pair],
+                signers: [payer, base_keypair]
+            }
+        );
+    });
+
+    it('assign', async function assign() {
+        const { contract, connection, payer, program } = await loadContract('TestingInstruction', 'TestingInstruction.abi');
+        const to_key_pair = Keypair.generate();
+
+        const assign_account = new PublicKey('AddressLookupTab1e1111111111111111111111111');
+        await contract.functions.assign(
+            publicKeyToHex(to_key_pair.publicKey),
+            publicKeyToHex(assign_account),
+            {
+                accounts: [system_account, payer.publicKey],
+                writable_accounts: [to_key_pair.publicKey],
+                signers: [to_key_pair],
+            }
+        )
+    });
+
+    it('assign with seed', async function assign_with_with_seed() {
+        const { contract, connection, payer, program } = await loadContract('TestingInstruction', 'TestingInstruction.abi');
+
+        const assign_account = new PublicKey('AddressLookupTab1e1111111111111111111111111');
+        const to_key_pair = await PublicKey.createWithSeed(payer.publicKey, seed, assign_account);
+
+        await contract.functions.assign_with_seed(
+            publicKeyToHex(to_key_pair),
+            publicKeyToHex(payer.publicKey),
+            seed,
+            publicKeyToHex(assign_account),
+            {
+                accounts: [system_account, assign_account],
+                writableAccounts: [to_key_pair],
+                signers: [payer]
+            }
+        );
+    });
+
+    it('transfer', async function transfer() {
+        const { contract, connection, payer, program } = await loadContract('TestingInstruction', 'TestingInstruction.abi');
+        const dest = new Keypair();
+
+        await contract.functions.transfer(
+            publicKeyToHex(payer.publicKey),
+            publicKeyToHex(dest.publicKey),
+            100000000,
+            {
+                accounts: [system_account],
+                writableAccounts: [payer.publicKey, dest.publicKey],
+                signers: [payer]
+            }
+        );
+    });
+
+    it('transfer with seed', async function transfer_with_seed() {
+        const { contract, connection, payer, program } = await loadContract('TestingInstruction', 'TestingInstruction.abi');
+        const dest = new Keypair();
+        const assign_account = new PublicKey('AddressLookupTab1e1111111111111111111111111');
+        const derived_payer = await PublicKey.createWithSeed(payer.publicKey, seed, assign_account);
+
+        let signature = await connection.requestAirdrop(derived_payer, LAMPORTS_PER_SOL);
+        await connection.confirmTransaction(signature, 'confirmed');
+
+        await contract.functions.transfer_with_seed(
+            publicKeyToHex(derived_payer),
+            publicKeyToHex(payer.publicKey),
+            seed,
+            publicKeyToHex(assign_account),
+            publicKeyToHex(dest.publicKey),
+            100000000,
+            {
+                accounts: [system_account, assign_account],
+                writableAccounts: [derived_payer, dest.publicKey],
+                signers: [payer]
+            }
+        );
+    });
+
+    it('allocate', async function allocate() {
+        const { contract, connection, payer, program } = await loadContract('TestingInstruction', 'TestingInstruction.abi');
+        const account = Keypair.generate();
+
+        await contract.functions.allocate(
+            publicKeyToHex(account.publicKey),
+            2,
+            {
+                accounts: [system_account],
+                writableAccounts: [account.publicKey],
+                signers: [account]
+            }
+        );
+    });
+
+    it('allocate with seed', async function allocate_with_seed() {
+        const { contract, connection, payer, program } = await loadContract('TestingInstruction', 'TestingInstruction.abi');
+        const account = Keypair.generate();
+        const owner = new PublicKey('Stake11111111111111111111111111111111111111');
+        const derived_key = await PublicKey.createWithSeed(account.publicKey, seed, owner);
+
+        await contract.functions.allocate_with_seed(
+            publicKeyToHex(derived_key),
+            publicKeyToHex(account.publicKey),
+            seed,
+            200,
+            publicKeyToHex(owner),
+            {
+                accounts: [system_account, owner],
+                writableAccounts: [derived_key],
+                signers: [account],
+            }
+        );
+    });
+
+    it('create nonce account with seed', async function create_nonce_account_with_seed() {
+        const { contract, connection, payer, program } = await loadContract('TestingInstruction', 'TestingInstruction.abi');
+        const base_address = Keypair.generate();
+        const derived_account = await PublicKey.createWithSeed(base_address.publicKey, seed, system_account);
+        const authority = Keypair.generate();
+
+        await contract.functions.create_nonce_account_with_seed(
+            publicKeyToHex(payer.publicKey),
+            publicKeyToHex(derived_account),
+            publicKeyToHex(base_address.publicKey),
+            seed,
+            publicKeyToHex(authority.publicKey),
+            100000000,
+            {
+                accounts: [system_account, recent_block_hashes, rentAddress],
+                writableAccounts: [payer.publicKey, derived_account],
+                signers: [payer, base_address]
+            }
+        );
+    });
+
+    it('nonce accounts', async function nonce_accounts() {
+        const { contract, connection, payer, program } = await loadContract('TestingInstruction', 'TestingInstruction.abi');
+        const nonce = Keypair.generate();
+        const authority = Keypair.generate();
+
+        await contract.functions.create_nonce_account(
+            publicKeyToHex(payer.publicKey),
+            publicKeyToHex(nonce.publicKey),
+            publicKeyToHex(authority.publicKey),
+            100000000,
+            {
+                accounts: [system_account, recent_block_hashes, rentAddress],
+                writableAccounts: [payer.publicKey, nonce.publicKey],
+                signers: [payer, nonce],
+            }
+        );
+
+        await contract.functions.advance_nonce_account(
+            publicKeyToHex(nonce.publicKey),
+            publicKeyToHex(authority.publicKey),
+            {
+                accounts: [system_account, recent_block_hashes],
+                writableAccounts: [nonce.publicKey],
+                signers: [authority],
+            }
+        );
+
+        await contract.functions.withdraw_nonce_account(
+            publicKeyToHex(nonce.publicKey),
+            publicKeyToHex(authority.publicKey),
+            publicKeyToHex(payer.publicKey),
+            1000,
+            {
+                accounts: [system_account, recent_block_hashes, rentAddress],
+                writableAccounts: [nonce.publicKey, payer.publicKey],
+                signers: [authority],
+            }
+        );
+
+        const new_authority = Keypair.generate();
+        await contract.functions.authorize_nonce_account(
+            publicKeyToHex(nonce.publicKey),
+            publicKeyToHex(authority.publicKey),
+            publicKeyToHex(new_authority.publicKey),
+            {
+                accounts: [system_account],
+                writableAccounts: [nonce.publicKey],
+                signers: [authority],
+            }
+        );
+    });
+});

+ 62 - 0
integration/solana/system_instruction_example.sol

@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: Apache-2.0
+
+import '../../solana-library/system_instruction.sol';
+
+contract TestingInstruction {
+    function create_account(address from, address to, uint64 lamports, uint64 space, address owner) public {
+        SystemInstruction.create_account(from, to, lamports, space, owner);
+    }
+
+    function create_account_with_seed(address from, address to, address base, string seed, uint64 lamports, uint64 space, address owner) public {
+        SystemInstruction.create_account_with_seed(from, to, base, seed, lamports, space, owner);
+    }
+
+    function assign(address account, address owner) public {
+        SystemInstruction.assign(account, owner);
+    }
+
+    function assign_with_seed(address account, address base, string seed, address owner) public {
+        SystemInstruction.assign_with_seed(account, base, seed, owner);
+    }
+
+    function transfer(address from, address to, uint64 lamports) public {
+        SystemInstruction.transfer(from, to, lamports);
+    }
+
+    function transfer_with_seed(address from_pubkey, address from_base, string seed, address from_owner, address to_pubkey, uint64 lamports) public {
+        SystemInstruction.transfer_with_seed(from_pubkey, from_base, seed, from_owner, to_pubkey, lamports);
+    }
+
+    function allocate(address pub_key, uint64 space) public {
+        SystemInstruction.allocate(pub_key, space);
+    }
+
+    function allocate_with_seed(address addr, address base, string seed, uint64 space, address owner) public {
+        SystemInstruction.allocate_with_seed(addr, base, seed, space, owner);
+    }
+
+    function create_nonce_account_with_seed(address from, address nonce, address base, string seed, address authority, uint64 lamports) public {
+        SystemInstruction.create_nonce_account_with_seed(from, nonce, base, seed, authority, lamports);
+    }
+
+    function create_nonce_account(address from, address nonce, address authority, uint64 lamports) public {
+        SystemInstruction.create_nonce_account(from, nonce, authority, lamports);
+    }
+
+    function advance_nonce_account(address nonce, address authorized) public {
+        SystemInstruction.advance_nonce_account(nonce, authorized);
+    }
+
+    function withdraw_nonce_account(address nonce, address authority, address to, uint64 lamports) public {
+        SystemInstruction.withdraw_nonce_account(nonce, authority, to, lamports);
+    }
+
+    function authorize_nonce_account(address nonce, address authority, address new_authority) public {
+        SystemInstruction.authorize_nonce_account(nonce, authority, new_authority);
+    }
+
+    // This is not available on Solana v1.9.15
+    // function upgrade_nonce_account(address nonce) public {
+    //     SystemInstruction.upgrade_nonce_account(nonce);
+    // }
+}

+ 1 - 1
integration/solana/token.sol

@@ -1,4 +1,4 @@
-import '../../examples/spl_token.sol';
+import '../../solana-library/spl_token.sol';
 
 
 contract Token {
 contract Token {
     address mint;
     address mint;

+ 5 - 0
examples/spl_token.sol → solana-library/spl_token.sol

@@ -1,3 +1,8 @@
+// SPDX-License-Identifier: Apache-2.0
+
+// Disclaimer: This library provides a way for Solidity to interact with Solana's SPL-Token. Although it is production ready,
+// it has not been audited for security, so use it at your own risk.
+
 import 'solana';
 import 'solana';
 
 
 library SplToken {
 library SplToken {

+ 300 - 0
solana-library/system_instruction.sol

@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: Apache-2.0
+
+// Disclaimer: This library provides a bridge for Solidity to interact with Solana's system instructions. Although it is production ready,
+// it has not been audited for security, so use it at your own risk.
+
+import 'solana';
+
+library SystemInstruction {
+    address constant systemAddress = address"11111111111111111111111111111111";
+    address constant recentBlockHashes = address"SysvarRecentB1ockHashes11111111111111111111";
+    address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
+    uint64 constant state_size = 80;
+
+    enum Instruction {
+        CreateAccount,
+        Assign,
+        Transfer,
+        CreateAccountWithSeed,
+        AdvanceNounceAccount,
+        WithdrawNonceAccount,
+        InitializeNonceAccount,
+        AuthorizeNonceAccount,
+        Allocate,
+        AllocateWithSeed,
+        AssignWithSeed,
+        TransferWithSeed,
+        UpgradeNonceAccount // This is not available on Solana v1.9.15
+    }
+
+    /// Create a new account on Solana
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param to public key for the account to be created
+    /// @param lamports amount of lamports to be transfered to the new account
+    /// @param space the size in bytes that is going to be made available for the account 
+    /// @param owner public key for the program that will own the account being created
+    function create_account(address from, address to, uint64 lamports, uint64 space, address owner) internal {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: true, is_writable: true})
+        ];
+        
+        bytes bincode = abi.encode(uint32(Instruction.CreateAccount), lamports, space, owner);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new account on Solana using a public key derived from a seed
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param to the public key for the account to be created. The public key must match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'to' address using the seed
+    /// @param seed the string utilized to created the 'to' public key
+    /// @param lamports amount of lamports to be transfered to the new account
+    /// @param space the size in bytes that is going to be made available for the account 
+    /// @param owner public key for the program that will own the account being created
+    function create_account_with_seed(address from, address to, address base, string seed, uint64 lamports, uint64 space, address owner) internal {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+        uint32 buffer_size = 92 + seed.length;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.CreateAccountWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(uint64(seed.length), 36);
+        bincode.writeString(seed, 44);
+        uint32 offset = seed.length + 44;
+        bincode.writeUint64LE(lamports, offset);
+        offset += 8;
+        bincode.writeUint64LE(space, offset);
+        offset += 8;
+        bincode.writeAddress(owner, offset);
+    
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Assign account to a program (owner)
+    ///
+    /// @param pubkey the public key for the account whose owner is going to be reassigned
+    /// @param owner the public key for the new account owner
+    function assign(address pubkey, address owner) internal {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: pubkey, is_signer: true, is_writable: true})
+        ];
+        bytes bincode = abi.encode(uint32(Instruction.Assign), owner);
+
+        systemAddress.call{accounts: meta}(bincode);
+    }
+
+    /// Assign account to a program (owner) based on a seed
+    ///
+    /// @param addr the public key for the account whose owner is going to be reassigned. The public key must match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'addr' key using the seed
+    /// @param seed the string utilized to created the 'addr' public key
+    /// @param owner the public key for the new program owner
+    function assign_with_seed(address addr, address base, string seed, address owner) internal {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: addr, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+        
+        uint32 buffer_size = 76 + seed.length;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.AssignWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(uint64(seed.length), 36);
+        bincode.writeString(seed, 44);
+        bincode.writeAddress(owner, 44 + seed.length);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Transfer lamports between accounts
+    ///
+    /// @param from public key for the funding account
+    /// @param to public key for the recipient account
+    /// @param lamports amount of lamports to transfer
+    function transfer(address from, address to, uint64 lamports) internal {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: false, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.Transfer), lamports);
+        
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Transfer lamports from a derived address
+    ///
+    /// @param from_pubkey The funding account public key. It should match create_with_seed(from_base, seed, from_owner)
+    /// @param from_base the base address that derived the 'from_pubkey' key using the seed
+    /// @param seed the string utilized to create the 'from_pubkey' public key
+    /// @param from_owner owner to use to derive the funding account address
+    /// @param to_pubkey the public key for the recipient account
+    /// @param lamports amount of lamports to transfer
+    function transfer_with_seed(address from_pubkey, address from_base, string seed, address from_owner, address to_pubkey, uint64 lamports) internal {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: from_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: from_base, is_signer: true, is_writable: false}),
+            AccountMeta({pubkey: to_pubkey, is_signer: false, is_writable: true})
+        ];
+
+        uint32 buffer_size = seed.length + 52;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.TransferWithSeed), 0);
+        bincode.writeUint64LE(lamports, 4);
+        bincode.writeUint64LE(seed.length, 12);
+        bincode.writeString(seed, 20);
+        bincode.writeAddress(from_owner, 20 + seed.length);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Allocate psace in a (possibly new) account without funding
+    ///
+    /// @param pub_key account for which to allocate space
+    /// @param space number of bytes of memory to allocate
+    function allocate(address pub_key, uint64 space) internal {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: pub_key, is_signer: true, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.Allocate), space);
+
+        systemAddress.call{accounts: meta}(bincode);
+    }
+
+    /// Allocate space for an assign an account at an address derived from a base public key and a seed
+    ///
+    /// @param addr account for which to allocate space. It should match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'addr' key using the seed
+    /// @param seed the string utilized to create the 'addr' public key
+    /// @param space number of bytes of memory to allocate
+    /// @param owner owner to use to derive the 'addr' account address
+    function allocate_with_seed(address addr, address base, string seed, uint64 space, address owner) internal {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: addr, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = new bytes(seed.length + 84);
+        bincode.writeUint32LE(uint32(Instruction.AllocateWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(seed.length, 36);
+        bincode.writeString(seed, 44);
+        uint32 offset = 44 + seed.length;
+        bincode.writeUint64LE(space, offset);
+        offset += 8;
+        bincode.writeAddress(owner, offset);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new nonce account on Solana using a public key derived from a seed
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param nonce the public key for the account to be created. The public key must match create_with_seed(base, seed, systemAddress)
+    /// @param base the base address that derived the 'nonce' key using the seed
+    /// @param seed the string utilized to create the 'addr' public key
+    /// @param authority The entity authorized to execute nonce instructions on the account
+    /// @param lamports amount of lamports to be transfered to the new account
+    function create_nonce_account_with_seed(address from, address nonce, address base, string seed, address authority, uint64 lamports) internal {
+        create_account_with_seed(from, nonce, base, seed, lamports, state_size, systemAddress);
+
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.InitializeNonceAccount), authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new account on Solana
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param nonce the public key for the nonce account to be created
+    /// @param authority The entity authorized to execute nonce instructions on the account
+    /// @param lamports amount of lamports to be transfered to the new account
+    function create_nonce_account(address from, address nonce, address authority, uint64 lamports) internal {
+        create_account(from, nonce, lamports, state_size, systemAddress);
+
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.InitializeNonceAccount), authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Consumes a stored nonce, replacing it with a successor
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the publick key for the entity authorized to execute instructins on the account
+    function advance_nonce_account(address nonce_pubkey, address authorized_pubkey) internal {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.AdvanceNounceAccount));
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Withdraw funds from a nonce account
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the public key for the entity authorized to execute instructins on the account
+    /// @param to_pubkey the recipient account
+    /// @param lamports the number of lamports to withdraw
+    function withdraw_nonce_account(address nonce_pubkey, address authorized_pubkey, address to_pubkey, uint64 lamports) internal {
+        AccountMeta[5] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: to_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.WithdrawNonceAccount), lamports);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Change the entity authorized to execute nonce instructions on the account
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the public key for the entity authorized to execute instructins on the account
+    /// @param new_authority 
+    function authorize_nonce_account(address nonce_pubkey, address authorized_pubkey, address new_authority) internal {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.AuthorizeNonceAccount), new_authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// One-time idempotent upgrade of legacy nonce version in order to bump them out of chain domain.
+    ///
+    /// @param nonce the public key for the nonce account
+    // This is not available on Solana v1.9.15
+    function upgrade_nonce_account(address nonce) internal {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.UpgradeNonceAccount));
+        systemAddress.call{accounts: meta}(bincode);
+    }
+}