-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Solana Anchor Decoding #8
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
cfd8c1a
First version
enoldev ccffe70
Fixes
enoldev 228f007
recursively get defined types
enoldev 05d9587
Add support for vec and option
enoldev 4e6f61d
fix
enoldev 6a147c2
Only include types if they are used
enoldev 889eec4
add support for 'alternate' defined type marshalling, as an object wi…
sduchesneau 3498aa9
support address inside Metadata and publicKey aliased to 'pubkey'
sduchesneau b3f7db9
sol-anchor: add user-defined network name, unexport unmarshalled IDL …
sduchesneau 1654cf1
bump substreams-rs and prost dependencies on sol-anchor
sduchesneau 0964b69
add blockFilter on sol-anchor
sduchesneau fd0689f
Add tests
enoldev 77b7ec2
Merge changes
enoldev 5bdcd7c
add initialblock
enoldev a310829
fix
enoldev 28b9c1a
Merge branch 'develop' into enol/solana-anchor
enoldev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
package solanchor | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"strconv" | ||
|
||
"github.com/mr-tron/base58" | ||
codegen "github.com/streamingfast/substreams-codegen" | ||
"github.com/streamingfast/substreams-codegen/loop" | ||
"github.com/tidwall/sjson" | ||
) | ||
|
||
type Convo struct { | ||
*codegen.Conversation[*Project] | ||
} | ||
|
||
func New() codegen.Converser { | ||
return &Convo{&codegen.Conversation[*Project]{ | ||
State: &Project{}, | ||
}} | ||
} | ||
|
||
func init() { | ||
codegen.RegisterConversation( | ||
"sol-anchor-beta", | ||
"Given an Anchor JSON IDL, create a Substreams that decodes instructions and events", | ||
"Allows you to decode data based on an Anchor JSON IDL", | ||
codegen.ConversationFactory(New), | ||
2000, | ||
) | ||
} | ||
|
||
var cmd = codegen.Cmd | ||
|
||
func (c *Convo) NextStep() loop.Cmd { | ||
p := c.State | ||
if p.Name == "" { | ||
return cmd(codegen.AskProjectName{}) | ||
} | ||
|
||
if p.idl == nil { | ||
return cmd(AskIdl{}) | ||
} | ||
|
||
if p.ChainName == "" { | ||
return cmd(codegen.AskChainName{}) | ||
} | ||
|
||
if p.idl.ProgramID() == "" { | ||
return cmd(AskProgramID{}) | ||
} | ||
|
||
if !p.InitialBlockSet { | ||
return cmd(codegen.AskInitialStartBlockType{}) | ||
} | ||
|
||
return cmd(codegen.RunGenerate{}) | ||
} | ||
|
||
func (c *Convo) Update(msg loop.Msg) loop.Cmd { | ||
switch msg := msg.(type) { | ||
case codegen.MsgStart: | ||
var msgCmd loop.Cmd | ||
if msg.Hydrate != nil { | ||
if err := json.Unmarshal([]byte(msg.Hydrate.SavedState), &c.State); err != nil { | ||
return loop.Quit(fmt.Errorf(`something went wrong, here's an error message to share with our devs (%s); we've notified them already`, err)) | ||
} | ||
|
||
msgCmd = c.Msg().Message("Ok, I reloaded your state.").Cmd() | ||
} else { | ||
msgCmd = c.Msg().Message("Ok, let's start a new package.").Cmd() | ||
} | ||
return loop.Seq(msgCmd, c.NextStep()) | ||
|
||
case codegen.AskProjectName: | ||
return c.CmdAskProjectName() | ||
|
||
case codegen.InputProjectName: | ||
c.State.Name = msg.Value | ||
return c.NextStep() | ||
|
||
case codegen.AskChainName: | ||
labels := []string{"Solana Mainnet", "Solana Devnet"} | ||
values := []string{"solana-mainnet", "solana-devnet"} | ||
return c.Action(codegen.InputChainName{}).ListSelect("Please select the chain"). | ||
Labels(labels...). | ||
Values(values...). | ||
Cmd() | ||
|
||
case codegen.InputChainName: | ||
c.State.ChainName = msg.Value | ||
return c.NextStep() | ||
|
||
case codegen.AskInitialStartBlockType: | ||
return c.Action(codegen.InputAskInitialStartBlockType{}). | ||
TextInput(codegen.InputAskInitialStartBlockTypeTextInput(), "Submit"). | ||
DefaultValue("0"). | ||
Validation(codegen.InputAskInitialStartBlockTypeRegex(), codegen.InputAskInitialStartBlockTypeValidation()). | ||
Cmd() | ||
|
||
case codegen.InputAskInitialStartBlockType: | ||
initialBlock, err := strconv.ParseUint(msg.Value, 10, 64) | ||
if err != nil { | ||
return loop.Quit(fmt.Errorf("invalid start block input value %q, expected a number", msg.Value)) | ||
} | ||
|
||
c.State.InitialBlock = initialBlock | ||
c.State.InitialBlockSet = true | ||
return c.NextStep() | ||
|
||
case AskIdl: | ||
return c.Action(InputIdl{}). | ||
TextInput("Input the Anchor IDL in JSON format\n", "Submit"). | ||
Cmd() | ||
|
||
case InputIdl: | ||
idl := &IDL{} | ||
err := json.Unmarshal([]byte(msg.Value), &idl) | ||
if err != nil { | ||
fmt.Println("Error unmarshaling JSON:", err) | ||
return loop.Quit(fmt.Errorf("could not decode IDL")) | ||
} | ||
if idl.Metadata.Name == "" { | ||
idl.Metadata.Name = c.State.Name // we need a name so anchor can compile | ||
} | ||
c.State.idl = idl | ||
c.State.IdlString = msg.Value | ||
return c.NextStep() | ||
|
||
case AskProgramID: | ||
return c.Action(InputProgramID{}). | ||
TextInput("Cannot get the ProgramID from the IDL. Please input the Program ID to match.\n", "Submit"). | ||
Cmd() | ||
|
||
case InputProgramID: | ||
newIDLString, err := sjson.Set(c.State.IdlString, "metadata.address", msg.Value) | ||
if err != nil { | ||
return loop.Quit(fmt.Errorf("could not set ProgramID in IDL: %w", err)) | ||
} | ||
c.State.IdlString = newIDLString | ||
|
||
c.State.idl.Metadata.Address = msg.Value | ||
b, err := base58.Decode(msg.Value) | ||
if err != nil || len(b) != 32 { | ||
return loop.Seq( | ||
c.Msg().Message("This address is not a valid base58-encoded solana address").Cmd(), | ||
c.NextStep(), | ||
) | ||
} | ||
return c.NextStep() | ||
|
||
case codegen.RunGenerate: | ||
str := "" | ||
for _, event := range c.State.idl.Events { | ||
for _, field := range event.Fields { | ||
fmt.Println("-----------------------------") | ||
fmt.Println(field.Name) | ||
|
||
str += field.Type.Simple | ||
} | ||
} | ||
return c.CmdGenerate(c.State.Generate) | ||
|
||
case codegen.ReturnGenerate: | ||
return c.CmdDownloadFiles(msg) | ||
} | ||
|
||
return loop.Quit(fmt.Errorf("invalid loop message: %T", msg)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package solanchor | ||
|
||
import ( | ||
"testing" | ||
|
||
codegen "github.com/streamingfast/substreams-codegen" | ||
"github.com/streamingfast/substreams-codegen/loop" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestConvoNextStep(t *testing.T) { | ||
convo := New() | ||
next := func() loop.Msg { | ||
return convo.NextStep()() | ||
} | ||
p := convo.(*Convo).State | ||
|
||
assert.Equal(t, codegen.AskProjectName{}, next()) | ||
p.Name = "my-proj" | ||
|
||
res := p.Generate() | ||
assert.NoError(t, res.Err) | ||
assert.NotEmpty(t, res.ProjectFiles) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package solanchor | ||
|
||
import ( | ||
"embed" | ||
|
||
codegen "github.com/streamingfast/substreams-codegen" | ||
) | ||
|
||
//go:embed templates/* | ||
var templatesFS embed.FS | ||
|
||
// use the output type form the Project to render the templates | ||
func (p *Project) Generate() codegen.ReturnGenerate { | ||
return codegen.GenerateTemplateTree(p, templatesFS, map[string]string{ | ||
"proto/program.proto.gotmpl": "proto/program.proto", | ||
"idls/program.json.gotmpl": "idls/program.json", | ||
"src/lib.rs.gotmpl": "src/lib.rs", | ||
"src/idl/mod.rs.gotmpl": "src/idl/mod.rs", | ||
".gitignore.gotmpl": ".gitignore", | ||
"buf.gen.yaml.gotmpl": "buf.gen.yaml", | ||
"Cargo.lock.gotmpl": "Cargo.lock", | ||
"Cargo.toml.gotmpl": "Cargo.toml", | ||
"substreams.yaml.gotmpl": "substreams.yaml", | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package solanchor | ||
|
||
import ( | ||
"unicode" | ||
) | ||
|
||
type IDL struct { | ||
Address string `json:"address"` // seen in the 'secret' program | ||
Events []Event `json:"events"` | ||
Instructions []Instruction `json:"instructions"` | ||
Metadata Metadata `json:"metadata"` | ||
Types []Type `json:"types"` | ||
} | ||
|
||
func (i *IDL) ProgramID() string { | ||
if i.Metadata.Address != "" { | ||
return i.Metadata.Address | ||
} | ||
return i.Address | ||
} | ||
|
||
func (i *IDL) IsTypeUsed(typeName string) bool { | ||
for _, instruction := range i.Instructions { | ||
for _, arg := range instruction.Args { | ||
if arg.Type.IsTypeUsed(typeName) == true { | ||
return true | ||
} | ||
} | ||
} | ||
|
||
for _, event := range i.Events { | ||
for _, field := range event.Fields { | ||
if field.Type.IsTypeUsed(typeName) == true { | ||
return true | ||
} | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
type Metadata struct { | ||
Address string `json:"address"` | ||
Name string `json:"name"` // seen in the 'secret' program | ||
} | ||
|
||
// --- UTILS | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as before, put this in a |
||
|
||
func ToProtobufType(rustType string) string { | ||
switch rustType { | ||
case "u8": | ||
return "uint64" | ||
case "u16": | ||
return "uint64" | ||
case "u128": | ||
return "uint64" | ||
case "i128": | ||
return "uint64" | ||
case "u64": | ||
return "uint64" | ||
case "i64": | ||
return "int64" | ||
case "f64": | ||
return "double" | ||
case "f32": | ||
return "float" | ||
case "i32": | ||
return "int32" | ||
case "u32": | ||
return "uint32" | ||
case "PubKey", "pubkey", "publicKey": | ||
return "string" | ||
} | ||
|
||
return rustType | ||
} | ||
|
||
func CastInRustIfNeeded(rustType string) string { | ||
switch rustType { | ||
case "u8": | ||
return "u64" | ||
case "u16": | ||
return "u64" | ||
case "u128": | ||
return "u64" | ||
case "i128": | ||
return "u64" | ||
} | ||
|
||
return "" | ||
} | ||
|
||
func toSnakeCase(str string, initialUnderscore bool) string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some library implements the |
||
var result []rune | ||
|
||
for i, r := range str { | ||
if !initialUnderscore && r == '_' && i == 0 { | ||
continue | ||
} | ||
// Check if the character is uppercase | ||
if unicode.IsUpper(r) { | ||
// Add an underscore before the uppercase letter if it's not the first character | ||
if i > 0 { | ||
result = append(result, '_') | ||
} | ||
// Convert the uppercase letter to lowercase | ||
result = append(result, unicode.ToLower(r)) | ||
} else { | ||
// Just add the character as is | ||
result = append(result, r) | ||
} | ||
} | ||
|
||
return string(result) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package solanchor | ||
|
||
// --- EVENTS | ||
|
||
type Event struct { | ||
Name string `json:"name"` | ||
Fields []Field `json:"fields"` | ||
} | ||
|
||
func (e *Event) SnakeCaseName() string { | ||
return toSnakeCase(e.Name, true) | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why you created this
UnmarshalJSON
func, the code here is difficult to understand. Why you didn't map each json field to a struct, just like what you did withEvents
,Instructions
,Types
...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "fields" of an Event or an Instruction could of different types: simple, defined, vec, array or optional.
For example:
Simple (e.g. string, u64...)
Defined (user-defined structs)
So because the that JSON part could be of different types, we have to try which one matches (not sure if there is a better way of doing it)