mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-22 07:49:26 +00:00
accounts/abi, cmd/abigen: add human-readable ABI support
* to provide intuitive way to define contract interfaces compared to verbose JSON * inspired by: https://github.com/yihuang/go-abi * for more spec: https://abitype.dev/api/human
This commit is contained in:
parent
494908a852
commit
bf541d1dfd
3 changed files with 922 additions and 26 deletions
437
accounts/abi/human_readable_test.go
Normal file
437
accounts/abi/human_readable_test.go
Normal file
|
|
@ -0,0 +1,437 @@
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// normalizeArgument converts ArgumentMarshaling to a JSON-compatible map.
|
||||||
|
// Auto-generated parameter names like "param0", "param1" are converted to empty strings.
|
||||||
|
func normalizeArgument(arg ArgumentMarshaling, isEvent bool) map[string]interface{} {
|
||||||
|
name := arg.Name
|
||||||
|
if strings.HasPrefix(name, "param") && len(name) > 5 {
|
||||||
|
isParamN := true
|
||||||
|
for _, c := range name[5:] {
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
isParamN = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isParamN {
|
||||||
|
name = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := map[string]interface{}{
|
||||||
|
"name": name,
|
||||||
|
"type": arg.Type,
|
||||||
|
}
|
||||||
|
if arg.InternalType != "" {
|
||||||
|
result["internalType"] = arg.InternalType
|
||||||
|
}
|
||||||
|
if len(arg.Components) > 0 {
|
||||||
|
components := make([]map[string]interface{}, len(arg.Components))
|
||||||
|
for i, comp := range arg.Components {
|
||||||
|
components[i] = normalizeArgument(comp, isEvent)
|
||||||
|
}
|
||||||
|
result["components"] = components
|
||||||
|
}
|
||||||
|
if isEvent {
|
||||||
|
result["indexed"] = arg.Indexed
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseHumanReadableABIArray processes multiple human-readable ABI signatures
|
||||||
|
// and returns a JSON array. Comments and empty lines are skipped.
|
||||||
|
func parseHumanReadableABIArray(signatures []string) ([]byte, error) {
|
||||||
|
var results []map[string]interface{}
|
||||||
|
for _, sig := range signatures {
|
||||||
|
sig = skipWhitespace(sig)
|
||||||
|
if sig == "" || strings.HasPrefix(sig, "//") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(sig, "struct ") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result, err := ParseHumanReadableABI(sig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resultType := result["type"]
|
||||||
|
normalized := map[string]interface{}{
|
||||||
|
"type": resultType,
|
||||||
|
}
|
||||||
|
isEvent := resultType == "event"
|
||||||
|
isFunction := resultType == "function"
|
||||||
|
|
||||||
|
if name, ok := result["name"]; ok {
|
||||||
|
normalized["name"] = name
|
||||||
|
}
|
||||||
|
if inputs, ok := result["inputs"].([]ArgumentMarshaling); ok {
|
||||||
|
normInputs := make([]map[string]interface{}, len(inputs))
|
||||||
|
for i, inp := range inputs {
|
||||||
|
normInputs[i] = normalizeArgument(inp, isEvent)
|
||||||
|
}
|
||||||
|
normalized["inputs"] = normInputs
|
||||||
|
}
|
||||||
|
if outputs, ok := result["outputs"].([]ArgumentMarshaling); ok {
|
||||||
|
normOutputs := make([]map[string]interface{}, len(outputs))
|
||||||
|
for i, out := range outputs {
|
||||||
|
normOutputs[i] = normalizeArgument(out, false)
|
||||||
|
}
|
||||||
|
normalized["outputs"] = normOutputs
|
||||||
|
} else if isFunction {
|
||||||
|
normalized["outputs"] = []map[string]interface{}{}
|
||||||
|
}
|
||||||
|
if stateMutability, ok := result["stateMutability"]; ok {
|
||||||
|
normalized["stateMutability"] = stateMutability
|
||||||
|
}
|
||||||
|
if anonymous, ok := result["anonymous"]; ok {
|
||||||
|
normalized["anonymous"] = anonymous
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, normalized)
|
||||||
|
}
|
||||||
|
return json.Marshal(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseHumanReadableABI(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
expected string
|
||||||
|
hasError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple function",
|
||||||
|
input: []string{"function transfer(address to, uint256 amount)"},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "transfer",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "to", "type": "address"},
|
||||||
|
{"name": "amount", "type": "uint256"}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function with view and returns",
|
||||||
|
input: []string{"function balanceOf(address account) view returns (uint256)"},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "balanceOf",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "account", "type": "address"}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"name": "", "type": "uint256"}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function with payable",
|
||||||
|
input: []string{"function deposit() payable"},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "deposit",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "payable"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event with indexed parameters",
|
||||||
|
input: []string{"event Transfer(address indexed from, address indexed to, uint256 value)"},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"name": "Transfer",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "from", "type": "address", "indexed": true},
|
||||||
|
{"name": "to", "type": "address", "indexed": true},
|
||||||
|
{"name": "value", "type": "uint256", "indexed": false}
|
||||||
|
],
|
||||||
|
"anonymous": false
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple functions",
|
||||||
|
input: []string{
|
||||||
|
"function transfer(address to, uint256 amount)",
|
||||||
|
"function balanceOf(address account) view returns (uint256)",
|
||||||
|
},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "transfer",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "to", "type": "address"},
|
||||||
|
{"name": "amount", "type": "uint256"}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "balanceOf",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "account", "type": "address"}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"name": "", "type": "uint256"}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function with arrays",
|
||||||
|
input: []string{"function batchTransfer(address[] recipients, uint256[] amounts)"},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "batchTransfer",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "recipients", "type": "address[]"},
|
||||||
|
{"name": "amounts", "type": "uint256[]"}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function with fixed arrays",
|
||||||
|
input: []string{"function getBalances(address[10] accounts) view returns (uint256[10])"},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "getBalances",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "accounts", "type": "address[10]"}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"name": "", "type": "uint256[10]"}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function with bytes types",
|
||||||
|
input: []string{"function setData(bytes32 key, bytes value)"},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "setData",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "key", "type": "bytes32"},
|
||||||
|
{"name": "value", "type": "bytes"}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function with small integers",
|
||||||
|
input: []string{"function smallIntegers(uint8 u8, uint16 u16, uint32 u32, uint64 u64, int8 i8, int16 i16, int32 i32, int64 i64)"},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "smallIntegers",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "u8", "type": "uint8"},
|
||||||
|
{"name": "u16", "type": "uint16"},
|
||||||
|
{"name": "u32", "type": "uint32"},
|
||||||
|
{"name": "u64", "type": "uint64"},
|
||||||
|
{"name": "i8", "type": "int8"},
|
||||||
|
{"name": "i16", "type": "int16"},
|
||||||
|
{"name": "i32", "type": "int32"},
|
||||||
|
{"name": "i64", "type": "int64"}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function with non-standard small integers",
|
||||||
|
input: []string{"function nonStandardIntegers(uint24 u24, uint48 u48, uint72 u72, uint96 u96, uint120 u120, int24 i24, int36 i36, int48 i48, int72 i72, int96 i96, int120 i120)"},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "nonStandardIntegers",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "u24", "type": "uint24"},
|
||||||
|
{"name": "u48", "type": "uint48"},
|
||||||
|
{"name": "u72", "type": "uint72"},
|
||||||
|
{"name": "u96", "type": "uint96"},
|
||||||
|
{"name": "u120", "type": "uint120"},
|
||||||
|
{"name": "i24", "type": "int24"},
|
||||||
|
{"name": "i36", "type": "int36"},
|
||||||
|
{"name": "i48", "type": "int48"},
|
||||||
|
{"name": "i72", "type": "int72"},
|
||||||
|
{"name": "i96", "type": "int96"},
|
||||||
|
{"name": "i120", "type": "int120"}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "comments and empty lines",
|
||||||
|
input: []string{
|
||||||
|
"// This is a comment",
|
||||||
|
"",
|
||||||
|
"function transfer(address to, uint256 amount)",
|
||||||
|
"",
|
||||||
|
"// Another comment",
|
||||||
|
"function balanceOf(address account) view returns (uint256)",
|
||||||
|
},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "transfer",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "to", "type": "address"},
|
||||||
|
{"name": "amount", "type": "uint256"}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "balanceOf",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "account", "type": "address"}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{"name": "", "type": "uint256"}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function with nested dynamic arrays",
|
||||||
|
input: []string{
|
||||||
|
"function processNestedArrays(uint256[][] matrix, address[][2][] deepArray)",
|
||||||
|
},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "processNestedArrays",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "matrix", "type": "uint256[][]"},
|
||||||
|
{"name": "deepArray", "type": "address[][2][]"}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function with mixed fixed and dynamic arrays",
|
||||||
|
input: []string{
|
||||||
|
"function processMixedArrays(uint256[5] fixedArray, address[] dynamicArray, bytes32[3][] fixedDynamicArray)",
|
||||||
|
},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "processMixedArrays",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "fixedArray", "type": "uint256[5]"},
|
||||||
|
{"name": "dynamicArray", "type": "address[]"},
|
||||||
|
{"name": "fixedDynamicArray", "type": "bytes32[3][]"}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function with deeply nested mixed arrays",
|
||||||
|
input: []string{
|
||||||
|
"function deepNestedArrays(uint256[][] complexArray, address[][] mixedArray)",
|
||||||
|
},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "deepNestedArrays",
|
||||||
|
"inputs": [
|
||||||
|
{"name": "complexArray", "type": "uint256[][]"},
|
||||||
|
{"name": "mixedArray", "type": "address[][]"}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := parseHumanReadableABIArray(tt.input)
|
||||||
|
if tt.hasError {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var expectedJSON interface{}
|
||||||
|
err = json.Unmarshal([]byte(tt.expected), &expectedJSON)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var actualJSON interface{}
|
||||||
|
err = json.Unmarshal(result, &actualJSON)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, expectedJSON, actualJSON)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseHumanReadableABI_Errors(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "invalid function format",
|
||||||
|
input: []string{"function invalid format"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid array size",
|
||||||
|
input: []string{"function test(uint256[invalid] arr) returns (bool)"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unrecognized line",
|
||||||
|
input: []string{"invalid line format"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, err := parseHumanReadableABIArray(tt.input)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,14 +19,82 @@ package abi
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SelectorMarshaling struct {
|
type SelectorMarshaling struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Inputs []ArgumentMarshaling `json:"inputs"`
|
||||||
|
Outputs []ArgumentMarshaling `json:"outputs,omitempty"`
|
||||||
|
StateMutability string `json:"stateMutability,omitempty"`
|
||||||
|
Anonymous bool `json:"anonymous,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventMarshaling struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Inputs []ArgumentMarshaling `json:"inputs"`
|
||||||
|
Anonymous bool `json:"anonymous"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorMarshaling struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Inputs []ArgumentMarshaling `json:"inputs"`
|
Inputs []ArgumentMarshaling `json:"inputs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ABIMarshaling is a union type that can represent any ABI element
|
||||||
|
type ABIMarshaling map[string]interface{}
|
||||||
|
|
||||||
|
// ParseHumanReadableABI parses a human-readable ABI signature into a JSON-compatible map
|
||||||
|
func ParseHumanReadableABI(signature string) (ABIMarshaling, error) {
|
||||||
|
signature = skipWhitespace(signature)
|
||||||
|
|
||||||
|
if strings.HasPrefix(signature, "event ") || (strings.Contains(signature, "(") && strings.Contains(signature, "indexed")) {
|
||||||
|
event, err := ParseEvent(signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make(ABIMarshaling)
|
||||||
|
result["name"] = event.Name
|
||||||
|
result["type"] = event.Type
|
||||||
|
result["inputs"] = event.Inputs
|
||||||
|
result["anonymous"] = event.Anonymous
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(signature, "error ") {
|
||||||
|
errSig, err := ParseError(signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make(ABIMarshaling)
|
||||||
|
result["name"] = errSig.Name
|
||||||
|
result["type"] = errSig.Type
|
||||||
|
result["inputs"] = errSig.Inputs
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(signature, "struct ") {
|
||||||
|
return nil, fmt.Errorf("struct definitions not supported, use inline tuple syntax")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn, err := ParseSelector(signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make(ABIMarshaling)
|
||||||
|
result["name"] = fn.Name
|
||||||
|
result["type"] = fn.Type
|
||||||
|
result["inputs"] = fn.Inputs
|
||||||
|
if len(fn.Outputs) > 0 {
|
||||||
|
result["outputs"] = fn.Outputs
|
||||||
|
}
|
||||||
|
result["stateMutability"] = fn.StateMutability
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func isDigit(c byte) bool {
|
func isDigit(c byte) bool {
|
||||||
return c >= '0' && c <= '9'
|
return c >= '0' && c <= '9'
|
||||||
}
|
}
|
||||||
|
|
@ -62,12 +130,32 @@ func parseIdentifier(unescapedSelector string) (string, string, error) {
|
||||||
return parseToken(unescapedSelector, true)
|
return parseToken(unescapedSelector, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func skipWhitespace(s string) string {
|
||||||
|
i := 0
|
||||||
|
for i < len(s) && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return s[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseKeyword checks if the string starts with a keyword followed by whitespace or special char
|
||||||
|
func parseKeyword(s string, keyword string) (string, bool) {
|
||||||
|
s = skipWhitespace(s)
|
||||||
|
if !strings.HasPrefix(s, keyword) {
|
||||||
|
return s, false
|
||||||
|
}
|
||||||
|
rest := s[len(keyword):]
|
||||||
|
if len(rest) > 0 && (isAlpha(rest[0]) || isDigit(rest[0]) || isIdentifierSymbol(rest[0])) {
|
||||||
|
return s, false
|
||||||
|
}
|
||||||
|
return skipWhitespace(rest), true
|
||||||
|
}
|
||||||
|
|
||||||
func parseElementaryType(unescapedSelector string) (string, string, error) {
|
func parseElementaryType(unescapedSelector string) (string, string, error) {
|
||||||
parsedType, rest, err := parseToken(unescapedSelector, false)
|
parsedType, rest, err := parseToken(unescapedSelector, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("failed to parse elementary type: %v", err)
|
return "", "", fmt.Errorf("failed to parse elementary type: %v", err)
|
||||||
}
|
}
|
||||||
// handle arrays
|
|
||||||
for len(rest) > 0 && rest[0] == '[' {
|
for len(rest) > 0 && rest[0] == '[' {
|
||||||
parsedType = parsedType + string(rest[0])
|
parsedType = parsedType + string(rest[0])
|
||||||
rest = rest[1:]
|
rest = rest[1:]
|
||||||
|
|
@ -123,10 +211,15 @@ func parseType(unescapedSelector string) (interface{}, string, error) {
|
||||||
func assembleArgs(args []interface{}) ([]ArgumentMarshaling, error) {
|
func assembleArgs(args []interface{}) ([]ArgumentMarshaling, error) {
|
||||||
arguments := make([]ArgumentMarshaling, 0)
|
arguments := make([]ArgumentMarshaling, 0)
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
// generate dummy name to avoid unmarshal issues
|
|
||||||
name := fmt.Sprintf("name%d", i)
|
name := fmt.Sprintf("name%d", i)
|
||||||
if s, ok := arg.(string); ok {
|
if s, ok := arg.(string); ok {
|
||||||
arguments = append(arguments, ArgumentMarshaling{name, s, s, nil, false})
|
arguments = append(arguments, ArgumentMarshaling{
|
||||||
|
Name: name,
|
||||||
|
Type: s,
|
||||||
|
InternalType: s,
|
||||||
|
Components: nil,
|
||||||
|
Indexed: false,
|
||||||
|
})
|
||||||
} else if components, ok := arg.([]interface{}); ok {
|
} else if components, ok := arg.([]interface{}); ok {
|
||||||
subArgs, err := assembleArgs(components)
|
subArgs, err := assembleArgs(components)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -137,7 +230,13 @@ func assembleArgs(args []interface{}) ([]ArgumentMarshaling, error) {
|
||||||
subArgs = subArgs[:len(subArgs)-1]
|
subArgs = subArgs[:len(subArgs)-1]
|
||||||
tupleType = "tuple[]"
|
tupleType = "tuple[]"
|
||||||
}
|
}
|
||||||
arguments = append(arguments, ArgumentMarshaling{name, tupleType, tupleType, subArgs, false})
|
arguments = append(arguments, ArgumentMarshaling{
|
||||||
|
Name: name,
|
||||||
|
Type: tupleType,
|
||||||
|
InternalType: tupleType,
|
||||||
|
Components: subArgs,
|
||||||
|
Indexed: false,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("failed to assemble args: unexpected type %T", arg)
|
return nil, fmt.Errorf("failed to assemble args: unexpected type %T", arg)
|
||||||
}
|
}
|
||||||
|
|
@ -145,33 +244,311 @@ func assembleArgs(args []interface{}) ([]ArgumentMarshaling, error) {
|
||||||
return arguments, nil
|
return arguments, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSelector converts a method selector into a struct that can be JSON encoded
|
// parseParameterList parses a parameter list with optional indexed flags
|
||||||
// and consumed by other functions in this package.
|
func parseParameterList(params string, allowIndexed bool) ([]ArgumentMarshaling, error) {
|
||||||
// Note, although uppercase letters are not part of the ABI spec, this function
|
params = skipWhitespace(params)
|
||||||
// still accepts it as the general format is valid.
|
if params == "" {
|
||||||
|
return []ArgumentMarshaling{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var arguments []ArgumentMarshaling
|
||||||
|
paramIndex := 0
|
||||||
|
|
||||||
|
for params != "" {
|
||||||
|
params = skipWhitespace(params)
|
||||||
|
|
||||||
|
var typeStr interface{}
|
||||||
|
var rest string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if params[0] == '(' {
|
||||||
|
typeStr, rest, err = parseCompositeType(params)
|
||||||
|
} else {
|
||||||
|
typeStr, rest, err = parseElementaryType(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse parameter type: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
|
||||||
|
indexed := false
|
||||||
|
if allowIndexed {
|
||||||
|
rest, indexed = parseKeyword(rest, "indexed")
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
paramName := fmt.Sprintf("param%d", paramIndex)
|
||||||
|
if len(rest) > 0 && (isAlpha(rest[0]) || isIdentifierSymbol(rest[0])) {
|
||||||
|
var name string
|
||||||
|
name, rest, err = parseIdentifier(rest)
|
||||||
|
if err == nil {
|
||||||
|
paramName = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := typeStr.(string); ok {
|
||||||
|
arguments = append(arguments, ArgumentMarshaling{
|
||||||
|
Name: paramName,
|
||||||
|
Type: s,
|
||||||
|
Indexed: indexed,
|
||||||
|
})
|
||||||
|
} else if components, ok := typeStr.([]interface{}); ok {
|
||||||
|
subArgs, err := assembleArgs(components)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to assemble tuple components: %v", err)
|
||||||
|
}
|
||||||
|
tupleType := "tuple"
|
||||||
|
if len(subArgs) != 0 && subArgs[len(subArgs)-1].Type == "[]" {
|
||||||
|
subArgs = subArgs[:len(subArgs)-1]
|
||||||
|
tupleType = "tuple[]"
|
||||||
|
}
|
||||||
|
arguments = append(arguments, ArgumentMarshaling{
|
||||||
|
Name: paramName,
|
||||||
|
Type: tupleType,
|
||||||
|
Components: subArgs,
|
||||||
|
Indexed: indexed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
|
||||||
|
if rest == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if rest[0] == ',' {
|
||||||
|
rest = rest[1:]
|
||||||
|
params = rest
|
||||||
|
paramIndex++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return arguments, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEvent parses an event signature into EventMarshaling
|
||||||
|
func ParseEvent(unescapedSelector string) (EventMarshaling, error) {
|
||||||
|
unescapedSelector = skipWhitespace(unescapedSelector)
|
||||||
|
|
||||||
|
rest, _ := parseKeyword(unescapedSelector, "event")
|
||||||
|
|
||||||
|
name, rest, err := parseIdentifier(rest)
|
||||||
|
if err != nil {
|
||||||
|
return EventMarshaling{}, fmt.Errorf("failed to parse event name: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
|
||||||
|
if len(rest) == 0 || rest[0] != '(' {
|
||||||
|
return EventMarshaling{}, fmt.Errorf("expected '(' after event name")
|
||||||
|
}
|
||||||
|
rest = rest[1:]
|
||||||
|
|
||||||
|
parenCount := 1
|
||||||
|
paramEnd := 0
|
||||||
|
for i := 0; i < len(rest); i++ {
|
||||||
|
if rest[i] == '(' {
|
||||||
|
parenCount++
|
||||||
|
} else if rest[i] == ')' {
|
||||||
|
parenCount--
|
||||||
|
if parenCount == 0 {
|
||||||
|
paramEnd = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if parenCount != 0 {
|
||||||
|
return EventMarshaling{}, fmt.Errorf("unbalanced parentheses in event signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
paramsStr := rest[:paramEnd]
|
||||||
|
arguments, err := parseParameterList(paramsStr, true)
|
||||||
|
if err != nil {
|
||||||
|
return EventMarshaling{}, fmt.Errorf("failed to parse event parameters: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = skipWhitespace(rest[paramEnd+1:])
|
||||||
|
_, anonymous := parseKeyword(rest, "anonymous")
|
||||||
|
|
||||||
|
return EventMarshaling{
|
||||||
|
Name: name,
|
||||||
|
Type: "event",
|
||||||
|
Inputs: arguments,
|
||||||
|
Anonymous: anonymous,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseError parses an error signature into ErrorMarshaling
|
||||||
|
func ParseError(unescapedSelector string) (ErrorMarshaling, error) {
|
||||||
|
unescapedSelector = skipWhitespace(unescapedSelector)
|
||||||
|
|
||||||
|
rest, _ := parseKeyword(unescapedSelector, "error")
|
||||||
|
|
||||||
|
name, rest, err := parseIdentifier(rest)
|
||||||
|
if err != nil {
|
||||||
|
return ErrorMarshaling{}, fmt.Errorf("failed to parse error name: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
|
||||||
|
if len(rest) == 0 || rest[0] != '(' {
|
||||||
|
return ErrorMarshaling{}, fmt.Errorf("expected '(' after error name")
|
||||||
|
}
|
||||||
|
rest = rest[1:]
|
||||||
|
|
||||||
|
parenCount := 1
|
||||||
|
paramEnd := 0
|
||||||
|
for i := 0; i < len(rest); i++ {
|
||||||
|
if rest[i] == '(' {
|
||||||
|
parenCount++
|
||||||
|
} else if rest[i] == ')' {
|
||||||
|
parenCount--
|
||||||
|
if parenCount == 0 {
|
||||||
|
paramEnd = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if parenCount != 0 {
|
||||||
|
return ErrorMarshaling{}, fmt.Errorf("unbalanced parentheses in error signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
paramsStr := rest[:paramEnd]
|
||||||
|
arguments, err := parseParameterList(paramsStr, false)
|
||||||
|
if err != nil {
|
||||||
|
return ErrorMarshaling{}, fmt.Errorf("failed to parse error parameters: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrorMarshaling{
|
||||||
|
Name: name,
|
||||||
|
Type: "error",
|
||||||
|
Inputs: arguments,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ParseSelector(unescapedSelector string) (SelectorMarshaling, error) {
|
func ParseSelector(unescapedSelector string) (SelectorMarshaling, error) {
|
||||||
name, rest, err := parseIdentifier(unescapedSelector)
|
unescapedSelector = skipWhitespace(unescapedSelector)
|
||||||
|
|
||||||
|
rest, _ := parseKeyword(unescapedSelector, "function")
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
|
||||||
|
name, rest, err := parseIdentifier(rest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': %v", unescapedSelector, err)
|
return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': %v", unescapedSelector, err)
|
||||||
}
|
}
|
||||||
args := []interface{}{}
|
|
||||||
if len(rest) >= 2 && rest[0] == '(' && rest[1] == ')' {
|
rest = skipWhitespace(rest)
|
||||||
rest = rest[2:]
|
|
||||||
} else {
|
if len(rest) == 0 || rest[0] != '(' {
|
||||||
args, rest, err = parseCompositeType(rest)
|
return SelectorMarshaling{}, fmt.Errorf("expected '(' after function name")
|
||||||
if err != nil {
|
}
|
||||||
return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': %v", unescapedSelector, err)
|
rest = rest[1:]
|
||||||
|
|
||||||
|
parenCount := 1
|
||||||
|
paramEnd := 0
|
||||||
|
for i := 0; i < len(rest); i++ {
|
||||||
|
if rest[i] == '(' {
|
||||||
|
parenCount++
|
||||||
|
} else if rest[i] == ')' {
|
||||||
|
parenCount--
|
||||||
|
if parenCount == 0 {
|
||||||
|
paramEnd = i
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if parenCount != 0 {
|
||||||
|
return SelectorMarshaling{}, fmt.Errorf("unbalanced parentheses in function signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
paramsStr := rest[:paramEnd]
|
||||||
|
fakeArgs, err := parseParameterList(paramsStr, false)
|
||||||
|
if err != nil {
|
||||||
|
return SelectorMarshaling{}, fmt.Errorf("failed to parse input parameters: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = skipWhitespace(rest[paramEnd+1:])
|
||||||
|
|
||||||
|
stateMutability := "nonpayable"
|
||||||
|
if newRest, found := parseKeyword(rest, "view"); found {
|
||||||
|
stateMutability = "view"
|
||||||
|
rest = newRest
|
||||||
|
} else if newRest, found := parseKeyword(rest, "pure"); found {
|
||||||
|
stateMutability = "pure"
|
||||||
|
rest = newRest
|
||||||
|
} else if newRest, found := parseKeyword(rest, "payable"); found {
|
||||||
|
stateMutability = "payable"
|
||||||
|
rest = newRest
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
|
||||||
|
var outputs []ArgumentMarshaling
|
||||||
|
if newRest, found := parseKeyword(rest, "returns"); found {
|
||||||
|
rest = skipWhitespace(newRest)
|
||||||
|
if len(rest) == 0 || rest[0] != '(' {
|
||||||
|
return SelectorMarshaling{}, fmt.Errorf("expected '(' after returns keyword")
|
||||||
|
}
|
||||||
|
|
||||||
|
parenCount := 1
|
||||||
|
paramEnd := 1
|
||||||
|
for i := 1; i < len(rest); i++ {
|
||||||
|
if rest[i] == '(' {
|
||||||
|
parenCount++
|
||||||
|
} else if rest[i] == ')' {
|
||||||
|
parenCount--
|
||||||
|
if parenCount == 0 {
|
||||||
|
paramEnd = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if parenCount != 0 {
|
||||||
|
return SelectorMarshaling{}, fmt.Errorf("unbalanced parentheses in returns clause")
|
||||||
|
}
|
||||||
|
|
||||||
|
returnsStr := rest[1:paramEnd]
|
||||||
|
outputs, err = parseParameterList(returnsStr, false)
|
||||||
|
if err != nil {
|
||||||
|
return SelectorMarshaling{}, fmt.Errorf("failed to parse returns: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = skipWhitespace(rest[paramEnd+1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
if stateMutability == "nonpayable" {
|
||||||
|
if newRest, found := parseKeyword(rest, "view"); found {
|
||||||
|
stateMutability = "view"
|
||||||
|
rest = newRest
|
||||||
|
} else if newRest, found := parseKeyword(rest, "pure"); found {
|
||||||
|
stateMutability = "pure"
|
||||||
|
rest = newRest
|
||||||
|
} else if newRest, found := parseKeyword(rest, "payable"); found {
|
||||||
|
stateMutability = "payable"
|
||||||
|
rest = newRest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
|
||||||
if len(rest) > 0 {
|
if len(rest) > 0 {
|
||||||
return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': unexpected string '%s'", unescapedSelector, rest)
|
return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': unexpected string '%s'", unescapedSelector, rest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reassemble the fake ABI and construct the JSON
|
return SelectorMarshaling{
|
||||||
fakeArgs, err := assembleArgs(args)
|
Name: name,
|
||||||
if err != nil {
|
Type: "function",
|
||||||
return SelectorMarshaling{}, fmt.Errorf("failed to parse selector: %v", err)
|
Inputs: fakeArgs,
|
||||||
}
|
Outputs: outputs,
|
||||||
|
StateMutability: stateMutability,
|
||||||
return SelectorMarshaling{name, "function", fakeArgs}, nil
|
Anonymous: false,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -24,6 +26,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/abigen"
|
"github.com/ethereum/go-ethereum/accounts/abi/abigen"
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common/compiler"
|
"github.com/ethereum/go-ethereum/common/compiler"
|
||||||
|
|
@ -71,6 +74,10 @@ var (
|
||||||
Name: "v2",
|
Name: "v2",
|
||||||
Usage: "Generates v2 bindings",
|
Usage: "Generates v2 bindings",
|
||||||
}
|
}
|
||||||
|
humanABIFlag = &cli.StringFlag{
|
||||||
|
Name: "human-abi",
|
||||||
|
Usage: "Path to human-readable ABI file (one signature per line), - for STDIN",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var app = flags.NewApp("Ethereum ABI wrapper code generator")
|
var app = flags.NewApp("Ethereum ABI wrapper code generator")
|
||||||
|
|
@ -87,18 +94,19 @@ func init() {
|
||||||
outFlag,
|
outFlag,
|
||||||
aliasFlag,
|
aliasFlag,
|
||||||
v2Flag,
|
v2Flag,
|
||||||
|
humanABIFlag,
|
||||||
}
|
}
|
||||||
app.Action = generate
|
app.Action = generate
|
||||||
}
|
}
|
||||||
|
|
||||||
func generate(c *cli.Context) error {
|
func generate(c *cli.Context) error {
|
||||||
flags.CheckExclusive(c, abiFlag, jsonFlag) // Only one source can be selected.
|
flags.CheckExclusive(c, abiFlag, jsonFlag, humanABIFlag) // Only one source can be selected.
|
||||||
|
|
||||||
if c.String(pkgFlag.Name) == "" {
|
if c.String(pkgFlag.Name) == "" {
|
||||||
utils.Fatalf("No destination package specified (--pkg)")
|
utils.Fatalf("No destination package specified (--pkg)")
|
||||||
}
|
}
|
||||||
if c.String(abiFlag.Name) == "" && c.String(jsonFlag.Name) == "" {
|
if c.String(abiFlag.Name) == "" && c.String(jsonFlag.Name) == "" && c.String(humanABIFlag.Name) == "" {
|
||||||
utils.Fatalf("Either contract ABI source (--abi) or combined-json (--combined-json) are required")
|
utils.Fatalf("Either contract ABI source (--abi), combined-json (--combined-json), or human-readable ABI (--human-abi) are required")
|
||||||
}
|
}
|
||||||
// If the entire solidity code was specified, build and bind based on that
|
// If the entire solidity code was specified, build and bind based on that
|
||||||
var (
|
var (
|
||||||
|
|
@ -137,6 +145,45 @@ func generate(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
bins = append(bins, string(bin))
|
bins = append(bins, string(bin))
|
||||||
|
|
||||||
|
kind := c.String(typeFlag.Name)
|
||||||
|
if kind == "" {
|
||||||
|
kind = c.String(pkgFlag.Name)
|
||||||
|
}
|
||||||
|
types = append(types, kind)
|
||||||
|
} else if c.String(humanABIFlag.Name) != "" {
|
||||||
|
// Load human-readable ABI and convert to JSON
|
||||||
|
var (
|
||||||
|
humanABI []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
input := c.String(humanABIFlag.Name)
|
||||||
|
if input == "-" {
|
||||||
|
humanABI, err = io.ReadAll(os.Stdin)
|
||||||
|
} else {
|
||||||
|
humanABI, err = os.ReadFile(input)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read human-readable ABI: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonABI, err := convertHumanReadableABI(string(humanABI))
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to parse human-readable ABI: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
abis = append(abis, jsonABI)
|
||||||
|
|
||||||
|
var bin []byte
|
||||||
|
if binFile := c.String(binFlag.Name); binFile != "" {
|
||||||
|
if bin, err = os.ReadFile(binFile); err != nil {
|
||||||
|
utils.Fatalf("Failed to read input bytecode: %v", err)
|
||||||
|
}
|
||||||
|
if strings.Contains(string(bin), "//") {
|
||||||
|
utils.Fatalf("Contract has additional library references, please use other mode(e.g. --combined-json) to catch library infos")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bins = append(bins, string(bin))
|
||||||
|
|
||||||
kind := c.String(typeFlag.Name)
|
kind := c.String(typeFlag.Name)
|
||||||
if kind == "" {
|
if kind == "" {
|
||||||
kind = c.String(pkgFlag.Name)
|
kind = c.String(pkgFlag.Name)
|
||||||
|
|
@ -234,6 +281,41 @@ func generate(c *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertHumanReadableABI(humanABI string) (string, error) {
|
||||||
|
var abiElements []abi.ABIMarshaling
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(humanABI))
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
if line == "" || strings.HasPrefix(line, "//") || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
element, err := abi.ParseHumanReadableABI(line)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse signature '%s': %v", line, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if element != nil {
|
||||||
|
abiElements = append(abiElements, element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read human-readable ABI: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
encoder := json.NewEncoder(&buf)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
if err := encoder.Encode(abiElements); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to encode JSON ABI: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue