Prechádzať zdrojové kódy

Add support for default shank ix discriminators in nodes-from-anchor (#590)

* `feat`: Add support for default shank ix discriminators in nodes-from-anchor

---------

Co-authored-by: Loris Leiva <loris.leiva@gmail.com>
fernandodeluret 6 mesiacov pred
rodič
commit
9936e036da

+ 5 - 0
.changeset/strong-planes-hide.md

@@ -0,0 +1,5 @@
+---
+'@codama/nodes-from-anchor': minor
+---
+
+Add support for default shank ix discriminators (using 1 byte at the start of the ix data) in nodes-from-anchor

+ 3 - 1
packages/nodes-from-anchor/README.md

@@ -16,7 +16,9 @@ pnpm install @codama/nodes-from-anchor
 ```
 
 > [!NOTE]
-> This package is **not** included in the main [`codama`](../library) package.
+>
+> - This package is **not** included in the main [`codama`](../library) package.
+> - If `metadata.origin` is not set on the IDL, it is assumed to be `"anchor"`. If you are trying to parse a Shank IDL, be sure that origin is set to `"shank"` so discriminators can be set correctly.
 
 ## Functions
 

+ 15 - 1
packages/nodes-from-anchor/src/v00/InstructionNode.ts

@@ -1,5 +1,6 @@
 import {
     bytesTypeNode,
+    bytesValueNode,
     camelCase,
     DiscriminatorNode,
     fieldDiscriminatorNode,
@@ -16,7 +17,11 @@ import { instructionAccountNodesFromAnchorV00 } from './InstructionAccountNode';
 import { instructionArgumentNodeFromAnchorV00 } from './InstructionArgumentNode';
 import { typeNodeFromAnchorV00 } from './typeNodes';
 
-export function instructionNodeFromAnchorV00(idl: IdlV00Instruction, origin?: 'anchor' | 'shank'): InstructionNode {
+export function instructionNodeFromAnchorV00(
+    idl: IdlV00Instruction,
+    ixIndex: number,
+    origin?: 'anchor' | 'shank',
+): InstructionNode {
     const idlName = idl.name ?? '';
     const name = camelCase(idlName);
     let dataArguments = (idl.args ?? []).map(instructionArgumentNodeFromAnchorV00);
@@ -41,6 +46,15 @@ export function instructionNodeFromAnchorV00(idl: IdlV00Instruction, origin?: 'a
         });
         dataArguments = [discriminatorField, ...dataArguments];
         discriminators = [fieldDiscriminatorNode('discriminator')];
+    } else if (origin === 'shank') {
+        const discriminatorField = instructionArgumentNode({
+            defaultValue: bytesValueNode('base16', ixIndex.toString(16)),
+            defaultValueStrategy: 'omitted',
+            name: 'discriminator',
+            type: fixedSizeTypeNode(bytesTypeNode(), 1),
+        });
+        dataArguments = [discriminatorField, ...dataArguments];
+        discriminators = [fieldDiscriminatorNode('discriminator')];
     }
 
     return instructionNode({

+ 3 - 1
packages/nodes-from-anchor/src/v00/ProgramNode.ts

@@ -11,7 +11,9 @@ export function programNodeFromAnchorV00(idl: IdlV00): ProgramNode {
     const origin = (idl?.metadata as { origin?: 'anchor' | 'shank' })?.origin ?? 'anchor';
     const pdas = (idl.accounts ?? []).filter(account => (account.seeds ?? []).length > 0).map(pdaNodeFromAnchorV00);
     const accounts = (idl.accounts ?? []).map(a => accountNodeFromAnchorV00(a, origin));
-    const instructions = (idl.instructions ?? []).map(i => instructionNodeFromAnchorV00(i, origin));
+    const instructions = (idl.instructions ?? []).map((instruction, index) =>
+        instructionNodeFromAnchorV00(instruction, index, origin),
+    );
     return programNode({
         accounts,
         definedTypes: (idl?.types ?? []).map(definedTypeNodeFromAnchorV00),

+ 9 - 5
packages/nodes-from-anchor/test/v00/InstructionNode.test.ts

@@ -13,11 +13,14 @@ import { expect, test } from 'vitest';
 import { instructionNodeFromAnchorV00 } from '../../src';
 
 test('it creates instruction nodes', () => {
-    const node = instructionNodeFromAnchorV00({
-        accounts: [{ isMut: true, isSigner: false, name: 'mint' }],
-        args: [{ name: 'amount', type: 'u8' }],
-        name: 'mintTokens',
-    });
+    const node = instructionNodeFromAnchorV00(
+        {
+            accounts: [{ isMut: true, isSigner: false, name: 'mint' }],
+            args: [{ name: 'amount', type: 'u8' }],
+            name: 'mintTokens',
+        },
+        0,
+    );
 
     expect(node).toEqual(
         instructionNode({
@@ -35,6 +38,7 @@ test('it creates instruction nodes with anchor discriminators', () => {
             args: [],
             name: 'myInstruction',
         },
+        0,
         'anchor',
     );
 

+ 19 - 1
packages/nodes-from-anchor/test/v00/ProgramNode.test.ts

@@ -1,8 +1,13 @@
 import {
     accountNode,
+    bytesTypeNode,
+    bytesValueNode,
     constantPdaSeedNodeFromProgramId,
     definedTypeNode,
     errorNode,
+    fieldDiscriminatorNode,
+    fixedSizeTypeNode,
+    instructionArgumentNode,
     instructionNode,
     pdaLinkNode,
     pdaNode,
@@ -36,7 +41,20 @@ test('it creates program nodes', () => {
                     name: 'myError',
                 }),
             ],
-            instructions: [instructionNode({ name: 'myInstruction' })],
+            instructions: [
+                instructionNode({
+                    arguments: [
+                        instructionArgumentNode({
+                            defaultValue: bytesValueNode('base16', (0).toString(16)),
+                            defaultValueStrategy: 'omitted',
+                            name: 'discriminator',
+                            type: fixedSizeTypeNode(bytesTypeNode(), 1),
+                        }),
+                    ],
+                    discriminators: [fieldDiscriminatorNode('discriminator')],
+                    name: 'myInstruction',
+                }),
+            ],
             name: 'myProgram',
             origin: 'shank',
             pdas: [pdaNode({ name: 'myAccount', seeds: [constantPdaSeedNodeFromProgramId()] })],