Skip to content

Commit 6e3a3be

Browse files
authored
Merge pull request #76 from gmajor-encrypt/master
Add extrinsic encode support
2 parents 66bc58a + fb80fa8 commit 6e3a3be

10 files changed

+177
-46
lines changed

Dockerfile

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM golang:1.18
2+
3+
WORKDIR app
4+
5+
COPY . .
6+
7+
RUN go mod download
8+
9+
CMD go test -v ./...

README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ go get -u github.com/itering/scale.go
2121
## Test
2222

2323
```bash
24-
go test ./... --cover -v
24+
go test ./... --cover -v
25+
```
26+
27+
Docker
28+
```bash
29+
docker build -t scale_go .
30+
docker run -it scale_go
2531
```
2632

2733
## Resources

contract/ABI_test.go

-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"testing"
66

77
"github.com/itering/scale.go/types"
8-
"github.com/itering/scale.go/utiles"
98
"github.com/stretchr/testify/assert"
109
)
1110

@@ -16,12 +15,10 @@ func Test_AbiParse(t *testing.T) {
1615
abi, err := InitAbi(c)
1716
assert.NoError(t, err)
1817
assert.Greater(t, len(abi.Types), 1)
19-
utiles.Debug(abi)
2018

2119
sc := types.ScaleDecoder{DuplicateName: make(map[string]int), RegisteredSiType: make(map[int]string)}
2220
abi.Register(&sc, "pre")
2321

24-
utiles.Debug(abi.RegisteredSiType)
2522
assert.Equal(t, len(abi.RegisteredSiType), 8)
2623

2724
}

extrinsic.go

+109-26
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package scalecodec
22

33
import (
4+
"encoding/json"
5+
"errors"
46
"fmt"
57

68
scaleType "github.com/itering/scale.go/types"
79
"github.com/itering/scale.go/types/scaleBytes"
810
"github.com/itering/scale.go/utiles"
11+
"github.com/shopspring/decimal"
912
"golang.org/x/crypto/blake2b"
1013
)
1114

@@ -73,6 +76,25 @@ func (e *ExtrinsicDecoder) generateHash() string {
7376
return utiles.BytesToHex(h)
7477
}
7578

79+
type GenericExtrinsic struct {
80+
VersionInfo string `json:"version_info"`
81+
ExtrinsicLength int `json:"extrinsic_length"`
82+
AddressType string `json:"address_type"`
83+
Tip decimal.Decimal `json:"tip"`
84+
SignedExtensions map[string]interface{} `json:"signed_extensions"`
85+
AccountId interface{} `json:"accountId"`
86+
Signer interface{} `json:"signer"` // map[string]interface or string
87+
Signature string `json:"signature"`
88+
SignatureRaw interface{} `json:"signature_raw"` // map[string]interface or string
89+
Nonce int `json:"nonce"`
90+
Era string `json:"era"`
91+
ExtrinsicHash string `json:"extrinsic_hash"`
92+
CallModuleFunction string `json:"call_module_function"`
93+
CallCode string `json:"call_code"`
94+
CallModule string `json:"call_module"`
95+
Params []ExtrinsicParam `json:"params"`
96+
}
97+
7698
func (e *ExtrinsicDecoder) Process() {
7799
e.ExtrinsicLength = e.ProcessAndUpdateData("Compact<u32>").(int)
78100
if e.ExtrinsicLength != e.Data.GetRemainingLength() {
@@ -84,27 +106,28 @@ func (e *ExtrinsicDecoder) Process() {
84106

85107
e.ContainsTransaction = utiles.U256(e.VersionInfo).Int64() >= 80
86108

87-
result := map[string]interface{}{
88-
"extrinsic_length": e.ExtrinsicLength,
89-
"version_info": e.VersionInfo,
109+
result := GenericExtrinsic{
110+
ExtrinsicLength: e.ExtrinsicLength,
111+
VersionInfo: e.VersionInfo,
90112
}
113+
91114
if e.VersionInfo == "04" || e.VersionInfo == "84" {
92115
if e.ContainsTransaction {
93116
// Address
94-
address := e.ProcessAndUpdateData(utiles.TrueOrElse(e.Metadata.MetadataVersion >= 14 && scaleType.HasReg("ExtrinsicSigner"), "ExtrinsicSigner", "Address"))
95-
switch v := address.(type) {
117+
result.Signer = e.ProcessAndUpdateData(utiles.TrueOrElse(e.Metadata.MetadataVersion >= 14 && scaleType.HasReg("ExtrinsicSigner"), "ExtrinsicSigner", "Address"))
118+
switch v := result.Signer.(type) {
96119
case string:
97120
e.Address = v
98-
result["address_type"] = "AccountId"
121+
result.AddressType = "AccountId"
99122
case map[string]interface{}:
100123
for name, value := range v {
101-
result["address_type"] = name
124+
result.AddressType = name
102125
e.Address = value
103126
}
104127
}
105128
// ExtrinsicSignature
106-
signature := e.ProcessAndUpdateData("ExtrinsicSignature")
107-
switch v := signature.(type) {
129+
result.SignatureRaw = e.ProcessAndUpdateData("ExtrinsicSignature")
130+
switch v := result.SignatureRaw.(type) {
108131
case string:
109132
e.Signature = v
110133
case map[string]interface{}:
@@ -116,25 +139,25 @@ func (e *ExtrinsicDecoder) Process() {
116139
e.Nonce = int(e.ProcessAndUpdateData("Compact<U64>").(uint64))
117140
if e.Metadata.Extrinsic != nil {
118141
if utiles.SliceIndex("ChargeTransactionPayment", e.Metadata.Extrinsic.SignedIdentifier) != -1 {
119-
result["tip"] = e.ProcessAndUpdateData("Compact<Balance>")
142+
result.Tip = e.ProcessAndUpdateData("Compact<Balance>").(decimal.Decimal)
120143
}
121144
} else {
122-
result["tip"] = e.ProcessAndUpdateData("Compact<Balance>")
145+
result.Tip = e.ProcessAndUpdateData("Compact<Balance>").(decimal.Decimal)
123146
}
124147
// spec SignedExtensions
125148
if len(e.SignedExtensions) > 0 {
126149
for _, extension := range e.SignedExtensions {
127150
if utiles.SliceIndex(extension.Name, e.Metadata.Extrinsic.SignedIdentifier) != -1 {
128151
for _, v := range extension.AdditionalSigned {
129-
result[v.Name] = e.ProcessAndUpdateData(v.Type)
152+
result.SignedExtensions[v.Name] = e.ProcessAndUpdateData(v.Type)
130153
}
131154
}
132155
}
133156
} else {
134157
if e.Metadata.MetadataVersion >= 14 {
135158
for _, ext := range e.Metadata.Extrinsic.SignedExtensions {
136159
if enable, ok := signedExts[ext.Identifier]; ok && enable {
137-
result[ext.Identifier] = e.ProcessAndUpdateData(ext.TypeString)
160+
result.SignedExtensions[ext.Identifier] = e.ProcessAndUpdateData(ext.TypeString)
138161
}
139162
}
140163
}
@@ -160,21 +183,81 @@ func (e *ExtrinsicDecoder) Process() {
160183
}
161184

162185
if e.ContainsTransaction {
163-
result["account_id"] = e.Address
164-
result["signature"] = e.Signature
165-
result["nonce"] = e.Nonce
166-
result["era"] = e.Era
167-
result["extrinsic_hash"] = e.ExtrinsicHash
186+
result.AccountId = e.Address
187+
result.Signature = e.Signature
188+
result.Nonce = e.Nonce
189+
result.Era = e.Era
190+
result.ExtrinsicHash = e.ExtrinsicHash
168191
}
192+
result.CallCode = e.CallIndex
193+
result.CallModuleFunction = call.Call.Name
194+
result.CallModule = call.Module.Name
195+
result.Params = e.Params
196+
e.Value = &result
197+
}
198+
199+
/*
200+
Encode extrinsic with option
201+
opt.Metadata is required
202+
return hex string, if error, return empty string
169203
170-
if e.CallIndex != "" {
171-
result["call_code"] = e.CallIndex
172-
result["call_module_function"] = call.Call.Name
173-
result["call_module"] = call.Module.Name
204+
Example:
205+
m := scalecodec.MetadataDecoder{}
206+
m.Init(utiles.HexToBytes(Kusama9370))
207+
_ = m.Process()
208+
option := types.ScaleDecoderOption{Metadata: &m.Metadata}
209+
genericExtrinsic := scalecodec.GenericExtrinsic{
210+
VersionInfo: "84",
211+
CallCode: "0400",
212+
Nonce: 0,
213+
Era: "00",
214+
Signer: map[string]interface{}{"Id": "0xe673cb35ffaaf7ab98c4e9268bfa9b4a74e49d41c8225121c346db7a7dd06d88"},
215+
SignatureRaw: map[string]interface{}{"Ed25519": "0xfce9453b1442bba86c2781e755a29c8a215ccf4b65ce81eeaa5b5a04dcdb79a54525cc86969f910c71c05f84aeab9c205022ecd4aa2abb4a3c3667f09dd16e0b"},
216+
Params: []scalecodec.ExtrinsicParam{
217+
{Value: map[string]interface{}{"Id": "0x0770e0831a275b534f7507c8ebd9f5f982a55053c9dc672da886ef41a6b5c628"}}, {Value: "1094000000000"},
218+
},
219+
}
220+
fmt.Println(genericExtrinsic.Encode(&option))
221+
*/
222+
223+
func (g *GenericExtrinsic) Encode(opt *scaleType.ScaleDecoderOption) (string, error) {
224+
if opt.Metadata == nil {
225+
return "", errors.New("invalid metadata")
226+
}
227+
data := g.VersionInfo
228+
if g.VersionInfo == "84" {
229+
data = data + scaleType.Encode(utiles.TrueOrElse(opt.Metadata.MetadataVersion >= 14 && scaleType.HasReg("ExtrinsicSigner"), "ExtrinsicSigner", "AccountId"), g.Signer) // accountId
230+
data = data + scaleType.Encode("ExtrinsicSignature", g.SignatureRaw) // signature
231+
data = data + scaleType.Encode("EraExtrinsic", g.Era) // era
232+
data = data + scaleType.Encode("Compact<U64>", g.Nonce) // nonce
233+
data = data + scaleType.Encode("Compact<Balance>", g.Tip) // tip
174234
}
175235

176-
result["nonce"] = e.Nonce
177-
result["era"] = e.Era
178-
result["params"] = e.Params
179-
e.Value = result
236+
data = data + g.CallCode
237+
call, ok := opt.Metadata.CallIndex[g.CallCode]
238+
239+
if !ok {
240+
return "", fmt.Errorf("not find Extrinsic Lookup %s, please check metadata info", g.CallCode)
241+
}
242+
243+
if len(g.Params) != len(call.Call.Args) {
244+
return "", fmt.Errorf("extrinsic params length not match, expect %d, got %d", len(call.Call.Args), len(g.Params))
245+
}
246+
247+
for index, arg := range call.Call.Args {
248+
data = data + utiles.TrimHex(scaleType.EncodeWithOpt(arg.Type, g.Params[index].Value, opt))
249+
}
250+
251+
return scaleType.Encode("Compact<u32>", len(utiles.HexToBytes(data))) + data, nil
252+
}
253+
254+
// ToMap GenericExtrinsic convert to map[string]interface
255+
func (g *GenericExtrinsic) ToMap() map[string]interface{} {
256+
var r map[string]interface{}
257+
b, err := json.Marshal(g)
258+
if err != nil {
259+
return nil
260+
}
261+
_ = json.Unmarshal(b, &r)
262+
return r
180263
}

extrinsic_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,32 @@ func TestCallEncode(t *testing.T) {
5050

5151
assert.Equal(t, "0400009094c424429709a324e65c64f151630e6c3700192bba8abd3c8e2218b61c0a7a13000064a7b3b6e00d", types.EncodeWithOpt("Call", call, &types.ScaleDecoderOption{Metadata: &m.Metadata}), "")
5252
}
53+
54+
func TestExtrinsicEncode(t *testing.T) {
55+
m := scalecodec.MetadataDecoder{}
56+
m.Init(utiles.HexToBytes(Kusama9370))
57+
_ = m.Process()
58+
option := types.ScaleDecoderOption{Metadata: &m.Metadata}
59+
60+
extrinsicRaw := []string{
61+
"280402000b91bdddd38601",
62+
"3d028400e673cb35ffaaf7ab98c4e9268bfa9b4a74e49d41c8225121c346db7a7dd06d8800fce9453b1442bba86c2781e755a29c8a215ccf4b65ce81eeaa5b5a04dcdb79a54525cc86969f910c71c05f84aeab9c205022ecd4aa2abb4a3c3667f09dd16e0b0000000400000770e0831a275b534f7507c8ebd9f5f982a55053c9dc672da886ef41a6b5c62807003c7bb7fe",
63+
"4d0384000ed3eb9c7be61e0f8e13abb04857d82be899c1d7c3203998e02538720052ad34019edcf93212fc47fd9934311d10363a2cdbaa6efb52660bfa335325a2eb78c52d1052ff7b3d4c926ec2ed02e9ce1ddacf34211a97d467b8e1fe9e8b5f63fa9f8d550005040000009d01524d524b3a3a45515549503a3a322e302e303a3a31333235343234332d3865646566396635326238316339306132612d434c4f544845532d53555350454e444552532d30303030303030333a3a626173652d31333235323835322d444742502e434c4f54484553",
64+
"bd1384001641233add39a0bfb8941c27f3091e320fab4032db6153c4597408dc2733e52e01ae293222b8a9e7356522429a0bfa448bd5aeedeb8fafd214f5e36507c8e2b8221b19fff42d179072667ecfa8eade81d9be9ae80e7890f991c7ceb6fbfe4d638f350065070018023000007901524d524b3a3a4c4953543a3a322e302e303a3a31353837383636302d3334326631323130366561623664393034632d594f55444c4543484553542d594f55444c4543484553542d30303031313231343a3a3836353030303030303030303000006901524d524b3a3a4c4953543a3a322e302e303a3a31353838303530312d3334326631323130366561623664393034632d594f55444c454143432d594f55444c454143432d30303032333638353a3a3433323530303030303030303000007101524d524b3a3a4c4953543a3a322e302e303a3a31353838303237352d3334326631323130366561623664393034632d594f55444c45484149522d594f55444c45484149522d30303032323339353a3a3137333030303030303030303000007101524d524b3a3a4c4953543a3a322e302e303a3a31353837393232312d3334326631323130366561623664393034632d594f55444c45464143452d594f55444c45464143452d30303031353432383a3a3334363030303030303030303000007101524d524b3a3a4c4953543a3a322e302e303a3a31353837373734332d3334326631323130366561623664393034632d594f55444c45534b494e2d594f55444c45534b494e2d30303030363934363a3a3137333030303030303030303000007101524d524b3a3a4c4953543a3a322e302e303a3a31353837393732372d3334326631323130366561623664393034632d594f55444c45455945532d594f55444c45455945532d30303031383537383a3a3639323030303030303030303000007101524d524b3a3a4c4953543a3a322e302e303a3a31353837383839372d3334326631323130366561623664393034632d594f55444c45454152532d594f55444c45454152532d30303031333131303a3a3235393530303030303030303000007101524d524b3a3a4c4953543a3a322e302e303a3a31353837393835382d3334326631323130366561623664393034632d594f55444c45484149522d594f55444c45484149522d30303031393636373a3a3433323530303030303030303000006d01524d524b3a3a4c4953543a3a322e302e303a3a31353838303835302d3334326631323130366561623664393034632d594f55444c454143432d594f55444c454143432d30303032353530383a3a32313632353030303030303030300000a101524d524b3a3a4c4953543a3a322e302e303a3a31353837363832362d3334326631323130366561623664393034632d594f55444c454241434b47524f554e442d594f55444c454241434b47524f554e442d30303030313231393a3a3433323530303030303030303000007101524d524b3a3a4c4953543a3a322e302e303a3a31353838363037372d3334326631323130366561623664393034632d594f55444c4541524d2d594f55444c4541524d532d30303030303639393a3a313833333530303030303030303000005501524d524b3a3a4c4953543a3a322e302e303a3a31353837353238362d3334326631323130366561623664393034632d594f55444c452d594f55444c452d30303030303737303a3a3839303935303030303030303030",
65+
"ad0284004a7c11337fdf53dd1ac3cdbfa86f90a059684f0c01a00827ab00259d946d5f5b01aed0bd7afbf2ad7a926bbab1157d3de28aa60f9e268e0400ba1165b1af4f5b56daa7faf62cd8219a1644268fc6c52946c749d840779b0677d2fd220019f71d8fc500d902001e06004e6709193edd9ad1773ef48e549fe565ea7d6e75ee1dfc1f631dbca7106ee26fc17cbec65603a1e6d434f8e3651db8020f453b8fad25d98b7822b9ef55f77665",
66+
}
67+
68+
for _, raw := range extrinsicRaw {
69+
e := scalecodec.ExtrinsicDecoder{}
70+
e.Init(scaleBytes.ScaleBytes{Data: utiles.HexToBytes(raw)}, &option)
71+
e.Process()
72+
encode, err := e.Value.(*scalecodec.GenericExtrinsic).Encode(&option)
73+
assert.NoError(t, err)
74+
assert.Equal(t, raw, encode)
75+
}
76+
77+
// will raise extrinsic params length not match error
78+
genericExtrinsic := scalecodec.GenericExtrinsic{VersionInfo: "04", CallCode: "0200", Params: nil}
79+
_, err := genericExtrinsic.Encode(&option)
80+
assert.Error(t, err)
81+
}

metadata_test.go

+2
Large diffs are not rendered by default.

types/Bytes.go

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package types
22

33
import (
4+
"strings"
5+
46
"github.com/itering/scale.go/utiles"
57
)
68

@@ -16,11 +18,16 @@ func (b *Bytes) Process() {
1618
}
1719

1820
func (b *Bytes) Encode(value string) string {
19-
value = utiles.TrimHex(value)
20-
if len(value)%2 == 1 {
21-
value += "0"
21+
var bytes []byte
22+
if strings.HasPrefix(value, "0x") {
23+
value = utiles.TrimHex(value)
24+
if len(value)%2 == 1 {
25+
value += "0"
26+
}
27+
} else {
28+
value = utiles.BytesToHex([]byte(value))
2229
}
23-
bytes := utiles.HexToBytes(value)
30+
bytes = utiles.HexToBytes(value)
2431
return Encode("Compact<u32>", len(bytes)) + value
2532
}
2633

types/Vectors.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77

88
"github.com/itering/scale.go/types/scaleBytes"
9+
"github.com/itering/scale.go/utiles"
910
)
1011

1112
type Vec struct {
@@ -42,7 +43,7 @@ func (v *Vec) Encode(value interface{}) string {
4243
s := reflect.ValueOf(value)
4344
raw += Encode("Compact<u32>", s.Len())
4445
for i := 0; i < s.Len(); i++ {
45-
raw += EncodeWithOpt(v.SubType, s.Index(i).Interface(), &ScaleDecoderOption{Spec: v.Spec, Metadata: v.Metadata})
46+
raw += utiles.TrimHex(EncodeWithOpt(v.SubType, s.Index(i).Interface(), &ScaleDecoderOption{Spec: v.Spec, Metadata: v.Metadata}))
4647
}
4748
return raw
4849
default:

types/types.go

+4
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ func (e *Era) Process() {
9898
}
9999
}
100100

101+
func (e *Era) Encode(era string) string {
102+
return era
103+
}
104+
101105
type EraExtrinsic struct{ Era }
102106

103107
type CompactMoment struct {

0 commit comments

Comments
 (0)