From 5554ff34a66386ec5295c8d22c2a0ca88ff811ae Mon Sep 17 00:00:00 2001 From: J1ang Date: Sun, 28 May 2023 22:11:21 +0800 Subject: [PATCH] feature: support generate parse transaction input bytes codes in `abigen` (#7593) feature: support generate parse transaction input bytes codes in `abigen`. It's easy to use abigen to generate code that help you parse the event log and call the payable method with the transactor. But parsing the transaction call data is missing. You can only send a transaction with a transactor. This PR aims to generate codes that help developer parse transaction calldata. abigen can generate code to parse the transaction call data. With this PR, transaction parse codes are generated like this: ```go // CollectParams is an auto generated read-only Go binding of transcaction calldata params type CollectParams struct { Param_params INonfungiblePositionManagerCollectParams } // Parse Collect method from calldata of a transaction // // Solidity: function collect((uint256,address,uint128,uint128) params) payable returns(uint256 amount0, uint256 amount1) func ParseCollect(calldata []byte) (*CollectParams, error) { if len(calldata) <= 4 { return nil, fmt.Errorf("invalid calldata input") } _abi, err := abi.JSON(strings.NewReader(UniswapABI)) if err != nil { return nil, fmt.Errorf("failed to get abi of registry metadata: %w", err) } out, err := _abi.Methods["collect"].Inputs.Unpack(calldata[4:]) if err != nil { return nil, fmt.Errorf("failed to unpack collect params data: %w", err) } var paramsResult = new(CollectParams) value := reflect.ValueOf(paramsResult).Elem() if value.NumField() != len(out) { return nil, fmt.Errorf("failed to match calldata with param field number") } out0 := *abi.ConvertType(out[0], new(INonfungiblePositionManagerCollectParams)).(*INonfungiblePositionManagerCollectParams) return &CollectParams{ Param_params: out0, }, nil } ``` Example of using `Parse` Function above: ```go package bin import ( "context" "fmt" "testing" "github.com/ledgerwatch/erigon/common/hexutil" "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/log/v3" ) func TestOnParse(t *testing.T) { cli, err := rpc.Dial("https://rpc.ankr.com/polygon", log.New(context.Background())) if err != nil { t.Fatal(err) } var collectTx struct { Input string `json:"input"` } err = cli.CallContext(context.Background(), &collectTx, "eth_getTransactionByHash", "0x741146cce64d873cfe82ade413651de355a6db92f992e5bfc4e1c58d92f5dd5b") if err != nil { t.Fatal(err) } var increaseLiquidityTx struct { Input string `json:"input"` } err = cli.CallContext(context.Background(), &increaseLiquidityTx, "eth_getTransactionByHash", "0x645ff650d7bfb9a74f573af474b9ebee48c3fadc7dac67257a8da6b55c71f338") if err != nil { t.Fatal(err) } fmt.Println(collectTx.Input) fmt.Println(increaseLiquidityTx.Input) collectBytes, _ := hexutil.Decode(collectTx.Input) increaseLiquidityBytes, _ := hexutil.Decode(increaseLiquidityTx.Input) collectInfo, err := ParseCollect(collectBytes) if err != nil { t.Fatal(err) } increaseLiquidityInfo, err := ParseIncreaseLiquidity(increaseLiquidityBytes) if err != nil { t.Fatal(err) } fmt.Printf("%+v\n", collectInfo) fmt.Printf("%+v\n", increaseLiquidityInfo) } ``` Output of code above: &{Param_params:{TokenId:+894123 Recipient:0x48B8e4ed457da9B64c33Ee50Fd5490614833A37D Amount0Max:+340282366920938463463374607431768211455 Amount1Max:+340282366920938463463374607431768211455}} &{Param_params:{TokenId:+891904 Amount0Desired:+331092 Amount1Desired:+0 Amount0Min:+331092 Amount1Min:+0 Deadline:+1685112789}} --- accounts/abi/bind/template.go | 50 ++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index dd8a106c0..344b1974b 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -90,6 +90,8 @@ package {{.Package}} import ( "math/big" "strings" + "fmt" + "reflect" ethereum "github.com/ledgerwatch/erigon" "github.com/ledgerwatch/erigon/accounts/abi" @@ -335,7 +337,7 @@ var ( func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) { return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}}) } - {{end}} + {{end}} {{range .Transacts}} // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. @@ -360,6 +362,52 @@ var ( } {{end}} + {{$metaType := .Type}} + {{range .Transacts}} + {{if ne (len .Normalized.Inputs) 0}} + + // {{.Normalized.Name}}Params is an auto generated read-only Go binding of transcaction calldata params + type {{.Normalized.Name}}Params struct { + {{range $i, $_ := .Normalized.Inputs}} Param_{{.Name}} {{bindtype .Type $structs}} + {{end}} + } + + // Parse {{.Normalized.Name}} method from calldata of a transaction + // + // Solidity: {{.Original.String}} + func Parse{{.Normalized.Name}}(calldata []byte) (*{{.Normalized.Name}}Params, error) { + if len(calldata) <= 4 { + return nil, fmt.Errorf("invalid calldata input") + } + + _abi, err := abi.JSON(strings.NewReader({{$metaType}}ABI)) + if err != nil { + return nil, fmt.Errorf("failed to get abi of registry metadata: %w", err) + } + + out, err := _abi.Methods["{{.Original.Name}}"].Inputs.Unpack(calldata[4:]) + if err != nil { + return nil, fmt.Errorf("failed to unpack {{.Original.Name}} params data: %w", err) + } + + var paramsResult = new({{.Normalized.Name}}Params) + value := reflect.ValueOf(paramsResult).Elem() + + if value.NumField() != len(out) { + return nil, fmt.Errorf("failed to match calldata with param field number") + } + + {{range $i, $t := .Normalized.Inputs}} + out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + + return &{{.Normalized.Name}}Params{ + {{range $i, $_ := .Normalized.Inputs}} Param_{{.Name}} : out{{$i}},{{end}} + }, nil + } + + {{end}} + {{end}} + {{if .Fallback}} // Fallback is a paid mutator transaction binding the contract fallback function. //