Ver Fonte

E2E tests for custom types and options

armaniferrante há 4 anos atrás
pai
commit
c2d2041759
7 ficheiros alterados com 102 adições e 38 exclusões
  1. 1 0
      Cargo.lock
  2. 1 0
      Cargo.toml
  3. 11 9
      examples/basic/src/lib.rs
  4. 8 9
      src/lib.rs
  5. 5 5
      syn/src/codegen/anchor.rs
  6. 13 12
      ts/src/coder.ts
  7. 63 3
      ts/test.js

+ 1 - 0
Cargo.lock

@@ -17,6 +17,7 @@ dependencies = [
  "anchor-attributes-program",
  "anchor-derive",
  "borsh",
+ "solana-program",
  "solana-sdk",
  "thiserror",
 ]

+ 1 - 0
Cargo.toml

@@ -11,6 +11,7 @@ default = []
 
 [dependencies]
 thiserror = "1.0.20"
+solana-program = "1.4.3"
 solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
 anchor-derive = { path = "./derive" }
 anchor-attributes-program = { path = "./attributes/program" }

+ 11 - 9
examples/basic/src/lib.rs

@@ -11,22 +11,24 @@ mod example {
     #[access_control(not_zero(authority))]
     pub fn create_root(ctx: Context<CreateRoot>, authority: Pubkey, data: u64) -> ProgramResult {
         let root = &mut ctx.accounts.root;
-        root.account.authority = authority;
-        root.account.data = data;
-        root.account.initialized = true;
+        root.authority = authority;
+        root.data = data;
+        root.initialized = true;
         Ok(())
     }
 
     pub fn update_root(ctx: Context<UpdateRoot>, data: u64) -> ProgramResult {
         let root = &mut ctx.accounts.root;
-        root.account.data = data;
+        root.data = data;
         Ok(())
     }
 
     pub fn create_leaf(ctx: Context<CreateLeaf>, data: u64, custom: MyCustomType) -> ProgramResult {
         let leaf = &mut ctx.accounts.leaf;
-        leaf.account.data = data;
-        leaf.account.custom = custom;
+        leaf.initialized = true;
+        leaf.root = *ctx.accounts.root.info.key;
+        leaf.data = data;
+        leaf.custom = custom;
         Ok(())
     }
 
@@ -36,9 +38,9 @@ mod example {
         custom: Option<MyCustomType>,
     ) -> ProgramResult {
         let leaf = &mut ctx.accounts.leaf;
-        leaf.account.data = data;
+        leaf.data = data;
         if let Some(custom) = custom {
-            leaf.account.custom = custom;
+            leaf.custom = custom;
         }
         Ok(())
     }
@@ -74,7 +76,7 @@ pub struct UpdateLeaf<'info> {
     pub authority: AccountInfo<'info>,
     #[account("root.initialized", "&root.authority == authority.key")]
     pub root: ProgramAccount<'info, Root>,
-    #[account(mut, belongs_to = root, "!leaf.initialized")]
+    #[account(mut, belongs_to = root, "leaf.initialized")]
     pub leaf: ProgramAccount<'info, Leaf>,
 }
 

+ 8 - 9
src/lib.rs

@@ -1,7 +1,7 @@
 use solana_sdk::account_info::AccountInfo;
 use solana_sdk::program_error::ProgramError;
 use solana_sdk::pubkey::Pubkey;
-use std::ops::Deref;
+use std::ops::{Deref, DerefMut};
 
 pub use anchor_attributes_access_control::access_control;
 pub use anchor_attributes_program::program;
@@ -17,10 +17,6 @@ impl<'a, T: AnchorSerialize + AnchorDeserialize> ProgramAccount<'a, T> {
     pub fn new(info: AccountInfo<'a>, account: T) -> ProgramAccount<'a, T> {
         Self { info, account }
     }
-
-    pub fn save(&self) {
-        // todo
-    }
 }
 
 impl<'a, T: AnchorSerialize + AnchorDeserialize> Deref for ProgramAccount<'a, T> {
@@ -31,12 +27,14 @@ impl<'a, T: AnchorSerialize + AnchorDeserialize> Deref for ProgramAccount<'a, T>
     }
 }
 
-pub trait Accounts<'info>: Sized {
-    fn try_anchor(program_id: &Pubkey, from: &[AccountInfo<'info>]) -> Result<Self, ProgramError>;
+impl<'a, T: AnchorSerialize + AnchorDeserialize> DerefMut for ProgramAccount<'a, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.account
+    }
 }
 
-pub trait AccountsSave: Sized {
-    fn try_save(&self) -> Result<Self, ProgramError>;
+pub trait Accounts<'info>: Sized {
+    fn try_anchor(program_id: &Pubkey, from: &[AccountInfo<'info>]) -> Result<Self, ProgramError>;
 }
 
 pub struct Context<'a, 'b, T> {
@@ -50,6 +48,7 @@ pub mod prelude {
         ProgramAccount,
     };
 
+    pub use solana_program::msg;
     pub use solana_sdk::account_info::next_account_info;
     pub use solana_sdk::account_info::AccountInfo;
     pub use solana_sdk::entrypoint::ProgramResult;

+ 5 - 5
syn/src/codegen/anchor.rs

@@ -49,11 +49,11 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
             match f.is_mut {
                 false => quote! {},
                 true => quote! {
-                        let mut data = self.#info.try_borrow_mut_data()?;
-                        let dst: &mut [u8] = &mut data;
-                        let mut cursor = std::io::Cursor::new(dst);
-                        self.#ident.account.serialize(&mut cursor)
-                                .map_err(|_| ProgramError::InvalidAccountData)?;
+                    let mut data = self.#info.try_borrow_mut_data()?;
+                    let dst: &mut [u8] = &mut data;
+                    let mut cursor = std::io::Cursor::new(dst);
+                    self.#ident.account.serialize(&mut cursor)
+                        .map_err(|_| ProgramError::InvalidAccountData)?;
                 },
             }
         })

+ 13 - 12
ts/src/coder.ts

@@ -96,30 +96,32 @@ class AccountsCoder {
 
 class IdlCoder {
   public static fieldLayout(field: IdlField, types?: IdlTypeDef[]): Layout {
+    const fieldName =
+      field.name !== undefined ? camelCase(field.name) : undefined;
     switch (field.type) {
       case "bool": {
-        return borsh.bool(field.name);
+        return borsh.bool(fieldName);
       }
       case "u8": {
-        return borsh.u8(field.name);
+        return borsh.u8(fieldName);
       }
       case "u32": {
-        return borsh.u32(field.name);
+        return borsh.u32(fieldName);
       }
       case "u64": {
-        return borsh.u64(field.name);
+        return borsh.u64(fieldName);
       }
       case "i64": {
-        return borsh.i64(field.name);
+        return borsh.i64(fieldName);
       }
       case "bytes": {
-        return borsh.vecU8(field.name);
+        return borsh.vecU8(fieldName);
       }
       case "string": {
-        return borsh.str(field.name);
+        return borsh.str(fieldName);
       }
       case "publicKey": {
-        return borsh.publicKey(field.name);
+        return borsh.publicKey(fieldName);
       }
       // TODO: all the other types that need to be exported by the borsh package.
       default: {
@@ -134,7 +136,7 @@ class IdlCoder {
               },
               types
             ),
-            field.name
+            fieldName
           );
           // @ts-ignore
         } else if (field.type.defined) {
@@ -143,12 +145,11 @@ class IdlCoder {
             throw new IdlError("User defined types not provided");
           }
           // @ts-ignore
-          const name = field.type.defined;
-          const filtered = types.filter((t) => t.name === name);
+          const filtered = types.filter((t) => t.name === field.type.defined);
           if (filtered.length !== 1) {
             throw new IdlError("Type not found");
           }
-          return IdlCoder.typeDefLayout(filtered[0], types, name);
+          return IdlCoder.typeDefLayout(filtered[0], types, fieldName);
         } else {
           throw new Error(`Not yet implemented: ${field}`);
         }

+ 63 - 3
ts/test.js

@@ -4,11 +4,13 @@ const anchor = require('.');
 // Global workspace settings.
 const WORKSPACE = {
     idl: JSON.parse(require('fs').readFileSync('../examples/basic/idl.json', 'utf8')),
-    programId: new anchor.web3.PublicKey('3bSz7zXCXFdEBw8AKEWJAa53YswM5aCoNNt5xSR42JDp'),
+    programId: new anchor.web3.PublicKey('CrQZpSbUnkXxwf1FnepmefoZ7VsbYE6HXmG1TjChH6y'),
     provider: anchor.Provider.local(),
 };
 
 async function test() {
+    console.log('Starting test.');
+
     // Configure the local cluster.
     anchor.setProvider(WORKSPACE.provider);
 
@@ -42,8 +44,7 @@ async function test() {
                 programId: WORKSPACE.programId,
             }),
         ],
-    }
-                                );
+    });
 
     // Read the newly created account data.
     let account = await program.account.root(root.publicKey);
@@ -62,6 +63,65 @@ async function test() {
     // Check the update actually persisted.
     account = await program.account.root(root.publicKey);
     assert.ok(account.data.eq(new anchor.BN(999)));
+
+    // Create and initialize a leaf account.
+    const leaf = new anchor.web3.Account();
+    let customType = { myData: new anchor.BN(4), key: WORKSPACE.programId };
+    await program.rpc.createLeaf(new anchor.BN(2), customType, {
+        accounts: {
+            root: root.publicKey,
+            leaf: leaf.publicKey,
+        },
+        signers: [leaf],
+        instructions: [
+            anchor.web3.SystemProgram.createAccount({
+                fromPubkey: WORKSPACE.provider.wallet.publicKey,
+                newAccountPubkey: leaf.publicKey,
+                space: 100,
+                lamports: await WORKSPACE.provider.connection.getMinimumBalanceForRentExemption(100),
+                programId: WORKSPACE.programId,
+            }),
+        ],
+    });
+
+    // Check the account was initialized.
+    account = await program.account.leaf(leaf.publicKey);
+    assert.ok(account.initialized);
+    assert.ok(account.root.equals(root.publicKey));
+    assert.ok(account.data.eq(new anchor.BN(2)));
+    assert.ok(account.custom.myData.eq(new anchor.BN(4)));
+    assert.ok(account.custom.key.equals(WORKSPACE.programId));
+
+    // Update the account.
+    await program.rpc.updateLeaf(new anchor.BN(5), null, {
+        accounts: {
+            authority: WORKSPACE.provider.wallet.publicKey,
+            root: root.publicKey,
+            leaf: leaf.publicKey,
+        },
+    });
+
+    // Check it was updated.
+    account = await program.account.leaf(leaf.publicKey);
+    assert.ok(account.data.eq(new anchor.BN(5)));
+
+    // Now update with the option.
+    customType = { myData: new anchor.BN(7), key: WORKSPACE.programId };
+    await program.rpc.updateLeaf(new anchor.BN(6), customType, {
+        accounts: {
+            authority: WORKSPACE.provider.wallet.publicKey,
+            root: root.publicKey,
+            leaf: leaf.publicKey,
+        },
+    });
+
+    // Check it was updated.
+    account = await program.account.leaf(leaf.publicKey);
+    assert.ok(account.data.eq(new anchor.BN(6)));
+    assert.ok(account.custom.myData.eq(new anchor.BN(7)));
+    assert.ok(account.custom.key.equals(WORKSPACE.programId));
+
+    console.log('Test complete.');
 }
 
 test();