Explorar o código

lang: Add `SystemAccount<'info>` Account Type (#954)

Matthew Callens %!s(int64=3) %!d(string=hai) anos
pai
achega
20726d06bd

+ 1 - 0
.travis.yml

@@ -78,6 +78,7 @@ jobs:
       script:
         - pushd tests/escrow && yarn && anchor test && popd
         - pushd tests/pyth && yarn && anchor test && popd
+        - pushd tests/system-accounts && yarn && anchor test && popd
         - pushd examples/tutorial/basic-0 && anchor test && popd
         - pushd examples/tutorial/basic-1 && anchor test && popd
         - pushd examples/tutorial/basic-2 && anchor test && popd

+ 4 - 0
CHANGELOG.md

@@ -11,6 +11,10 @@ incremented for features.
 
 ## [Unreleased]
 
+### Features
+
+* lang: Add `SystemAccount<'info>` account type for generic wallet addresses or accounts owned by the system program ([#954](https://github.com/project-serum/anchor/pull/954))
+
 ### Fixes
 
 * cli: fix dns in NODE_OPTIONS ([#928](https://github.com/project-serum/anchor/pull/928)).

+ 2 - 0
lang/src/error.rs

@@ -72,6 +72,8 @@ pub enum ErrorCode {
     InvalidProgramExecutable,
     #[msg("The given account did not sign")]
     AccountNotSigner,
+    #[msg("The given account is not owned by the system program")]
+    AccountNotSystemOwned,
 
     // State.
     #[msg("The given state account does not have the correct address")]

+ 4 - 2
lang/src/lib.rs

@@ -49,6 +49,7 @@ mod program;
 mod program_account;
 mod signer;
 pub mod state;
+mod system_account;
 mod system_program;
 mod sysvar;
 mod unchecked_account;
@@ -75,6 +76,7 @@ pub use crate::signer::Signer;
 #[doc(hidden)]
 #[allow(deprecated)]
 pub use crate::state::ProgramState;
+pub use crate::system_account::SystemAccount;
 pub use crate::system_program::System;
 pub use crate::sysvar::Sysvar;
 pub use crate::unchecked_account::UncheckedAccount;
@@ -250,8 +252,8 @@ pub mod prelude {
         access_control, account, declare_id, emit, error, event, interface, program, require,
         state, zero_copy, Account, AccountDeserialize, AccountLoader, AccountSerialize, Accounts,
         AccountsExit, AnchorDeserialize, AnchorSerialize, Context, CpiContext, Id, Key, Loader,
-        Owner, Program, ProgramAccount, Signer, System, Sysvar, ToAccountInfo, ToAccountInfos,
-        ToAccountMetas, UncheckedAccount,
+        Owner, Program, ProgramAccount, Signer, System, SystemAccount, Sysvar, ToAccountInfo,
+        ToAccountInfos, ToAccountMetas, UncheckedAccount,
     };
 
     #[allow(deprecated)]

+ 94 - 0
lang/src/system_account.rs

@@ -0,0 +1,94 @@
+use crate::error::ErrorCode;
+use crate::*;
+use solana_program::account_info::AccountInfo;
+use solana_program::entrypoint::ProgramResult;
+use solana_program::instruction::AccountMeta;
+use solana_program::program_error::ProgramError;
+use solana_program::pubkey::Pubkey;
+use solana_program::system_program;
+use std::ops::Deref;
+
+#[derive(Clone)]
+pub struct SystemAccount<'info> {
+    info: AccountInfo<'info>,
+}
+
+impl<'info> SystemAccount<'info> {
+    fn new(info: AccountInfo<'info>) -> SystemAccount<'info> {
+        Self { info }
+    }
+
+    #[inline(never)]
+    pub fn try_from(info: &AccountInfo<'info>) -> Result<SystemAccount<'info>, ProgramError> {
+        if *info.owner != system_program::ID {
+            return Err(ErrorCode::AccountNotSystemOwned.into());
+        }
+        Ok(SystemAccount::new(info.clone()))
+    }
+}
+
+impl<'info> Accounts<'info> for SystemAccount<'info> {
+    #[inline(never)]
+    fn try_accounts(
+        _program_id: &Pubkey,
+        accounts: &mut &[AccountInfo<'info>],
+        _ix_data: &[u8],
+    ) -> Result<Self, ProgramError> {
+        if accounts.is_empty() {
+            return Err(ErrorCode::AccountNotEnoughKeys.into());
+        }
+        let account = &accounts[0];
+        *accounts = &accounts[1..];
+        SystemAccount::try_from(account)
+    }
+}
+
+impl<'info> AccountsExit<'info> for SystemAccount<'info> {
+    fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
+        // No-op.
+        Ok(())
+    }
+}
+
+impl<'info> ToAccountMetas for SystemAccount<'info> {
+    fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
+        let is_signer = is_signer.unwrap_or(self.info.is_signer);
+        let meta = match self.info.is_writable {
+            false => AccountMeta::new_readonly(*self.info.key, is_signer),
+            true => AccountMeta::new(*self.info.key, is_signer),
+        };
+        vec![meta]
+    }
+}
+
+impl<'info> ToAccountInfos<'info> for SystemAccount<'info> {
+    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
+        vec![self.info.clone()]
+    }
+}
+
+impl<'info> ToAccountInfo<'info> for SystemAccount<'info> {
+    fn to_account_info(&self) -> AccountInfo<'info> {
+        self.info.clone()
+    }
+}
+
+impl<'info> AsRef<AccountInfo<'info>> for SystemAccount<'info> {
+    fn as_ref(&self) -> &AccountInfo<'info> {
+        &self.info
+    }
+}
+
+impl<'info> Deref for SystemAccount<'info> {
+    type Target = AccountInfo<'info>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.info
+    }
+}
+
+impl<'info> Key for SystemAccount<'info> {
+    fn key(&self) -> Pubkey {
+        *self.info.key
+    }
+}

+ 8 - 0
lang/syn/src/lib.rs

@@ -184,6 +184,9 @@ impl Field {
             Ty::Signer => quote! {
                 Signer
             },
+            Ty::SystemAccount => quote! {
+                SystemAccount
+            },
             Ty::Account(AccountTy { boxed, .. }) => {
                 if *boxed {
                     quote! {
@@ -294,6 +297,7 @@ impl Field {
             Ty::AccountInfo => quote! {},
             Ty::UncheckedAccount => quote! {},
             Ty::Signer => quote! {},
+            Ty::SystemAccount => quote! {},
         }
     }
 
@@ -309,6 +313,9 @@ impl Field {
             Ty::Signer => quote! {
                 Signer
             },
+            Ty::SystemAccount => quote! {
+                SystemAccount
+            },
             Ty::ProgramAccount(ty) => {
                 let ident = &ty.account_type_path;
                 quote! {
@@ -397,6 +404,7 @@ pub enum Ty {
     Account(AccountTy),
     Program(ProgramTy),
     Signer,
+    SystemAccount,
 }
 
 #[derive(Debug, PartialEq)]

+ 2 - 0
lang/syn/src/parser/accounts/mod.rs

@@ -78,6 +78,7 @@ fn is_field_primitive(f: &syn::Field) -> ParseResult<bool> {
             | "Account"
             | "Program"
             | "Signer"
+            | "SystemAccount"
     );
     Ok(r)
 }
@@ -100,6 +101,7 @@ fn parse_ty(f: &syn::Field) -> ParseResult<Ty> {
         "Account" => Ty::Account(parse_account_ty(&path)?),
         "Program" => Ty::Program(parse_program_ty(&path)?),
         "Signer" => Ty::Signer,
+        "SystemAccount" => Ty::SystemAccount,
         _ => return Err(ParseError::new(f.ty.span(), "invalid account type given")),
     };
 

+ 9 - 0
tests/system-accounts/Anchor.toml

@@ -0,0 +1,9 @@
+[provider]
+cluster = "localnet"
+wallet = "~/.config/solana/id.json"
+
+[programs.localnet]
+system_accounts = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+
+[scripts]
+test = "mocha -t 1000000 tests/"

+ 4 - 0
tests/system-accounts/Cargo.toml

@@ -0,0 +1,4 @@
+[workspace]
+members = [
+    "programs/*"
+]

+ 16 - 0
tests/system-accounts/programs/system-accounts/Cargo.toml

@@ -0,0 +1,16 @@
+[package]
+name = "system-accounts"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "system_accounts"
+
+[features]
+no-entrypoint = []
+cpi = ["no-entrypoint"]
+
+[dependencies]
+anchor-lang = { path = "../../../../lang" }

+ 2 - 0
tests/system-accounts/programs/system-accounts/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 18 - 0
tests/system-accounts/programs/system-accounts/src/lib.rs

@@ -0,0 +1,18 @@
+use anchor_lang::prelude::*;
+
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
+#[program]
+mod system_accounts {
+    use super::*;
+
+    pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct Initialize<'info> {
+    pub authority: Signer<'info>,
+    pub wallet: SystemAccount<'info>,
+}

+ 60 - 0
tests/system-accounts/tests/system-accounts.js

@@ -0,0 +1,60 @@
+const anchor = require('@project-serum/anchor');
+const splToken = require('@solana/spl-token');
+const assert = require('assert');
+
+describe('system_accounts', () => {
+  anchor.setProvider(anchor.Provider.local());
+  const program = anchor.workspace.SystemAccounts;
+  const authority = program.provider.wallet.payer;
+  const wallet = anchor.web3.Keypair.generate();
+
+  it('Is initialized!', async () => {
+    const tx = await program.rpc.initialize({
+      accounts: {
+        authority: authority.publicKey,
+        wallet: wallet.publicKey
+      },
+      signers: [authority]
+    });
+
+    console.log("Your transaction signature", tx);
+  });
+
+  it('Emits an AccountNotSystemOwned error', async () => {
+    const mint = await splToken.Token.createMint(
+      program.provider.connection,
+      authority,
+      authority.publicKey,
+      null,
+      9,
+      splToken.TOKEN_PROGRAM_ID,
+    );
+
+    const tokenAccount = await mint.createAssociatedTokenAccount(
+      wallet.publicKey
+    );
+
+    await mint.mintTo(
+      tokenAccount,
+      authority.publicKey,
+      [],
+      1 * anchor.web3.LAMPORTS_PER_SOL,
+    );
+
+    try {
+      await program.rpc.initialize({
+        accounts: {
+          authority: authority.publicKey,
+          wallet: tokenAccount
+        },
+        signers: [authority]
+      })
+      assert.ok(false);
+    } catch (err) {
+      const errMsg = 'The given account is not owned by the system program';
+      assert.equal(err.toString(), errMsg);
+      assert.equal(err.msg, errMsg);
+      assert.equal(err.code, 171);
+    }
+  });
+});

+ 7 - 3
ts/src/error.ts

@@ -86,7 +86,9 @@ const LangErrorCode = {
   AccountNotMutable: 166,
   AccountNotProgramOwned: 167,
   InvalidProgramId: 168,
-  InvalidProgramIdExecutable: 169,
+  InvalidProgramExecutable: 169,
+  AccountNotSigner: 170,
+  AccountNotSystemOwned: 171,
 
   // State.
   StateInvalidAddress: 180,
@@ -167,9 +169,11 @@ const LangErrorMessage = new Map([
     "The given account is not owned by the executing program",
   ],
   [LangErrorCode.InvalidProgramId, "Program ID was not as expected"],
+  [LangErrorCode.InvalidProgramExecutable, "Program account is not executable"],
+  [LangErrorCode.AccountNotSigner, "The given account did not sign"],
   [
-    LangErrorCode.InvalidProgramIdExecutable,
-    "Program account is not executable",
+    LangErrorCode.AccountNotSystemOwned,
+    "The given account is not owned by the system program",
   ],
 
   // State.