From ad60f0cec08829efaedf13b5600f8c1877d1c978 Mon Sep 17 00:00:00 2001 From: MariusVanDerWijden Date: Thu, 13 Feb 2020 13:48:09 +0100 Subject: [PATCH] accounts/abi: prevent recalculation of ID, Sig and String --- accounts/abi/abi.go | 65 +++++----------------------------------- accounts/abi/abi_test.go | 20 +++++-------- accounts/abi/method.go | 58 ++++++++++++++++++++++++++++++----- 3 files changed, 65 insertions(+), 78 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 4b88a52cef..965c4f7f27 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -139,59 +139,24 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { for _, field := range fields { switch field.Type { case "constructor": - abi.Constructor = Method{ - Inputs: field.Inputs, - - // Note for constructor the `StateMutability` can only - // be payable or nonpayable according to the output of - // compiler. So constant is always false. - StateMutability: field.StateMutability, - - // Legacy fields, keep them for backward compatibility - Constant: field.Constant, - Payable: field.Payable, - } - case "function": + abi.Constructor = NewMethod("", "", field.StateMutability, field.Constant, field.Payable, false, false, field.Inputs, nil) + // empty defaults to function according to the abi spec + case "function", "": name := field.Name _, ok := abi.Methods[name] for idx := 0; ok; idx++ { name = fmt.Sprintf("%s%d", field.Name, idx) _, ok = abi.Methods[name] } - abi.Methods[name] = Method{ - Name: name, - RawName: field.Name, - StateMutability: field.StateMutability, - Inputs: field.Inputs, - Outputs: field.Outputs, - - // Legacy fields, keep them for backward compatibility - Constant: field.Constant, - Payable: field.Payable, - } + isConst := field.Constant || field.StateMutability == "pure" || field.StateMutability == "view" + abi.Methods[name] = NewMethod(name, field.Name, field.StateMutability, isConst, field.Payable, false, false, field.Inputs, field.Outputs) case "fallback": // New introduced function type in v0.6.0, check more detail // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function if abi.HasFallback() { return errors.New("only single fallback is allowed") } - abi.Fallback = Method{ - Name: "", - RawName: "", - - // The `StateMutability` can only be payable or nonpayable, - // so the constant is always false. - StateMutability: field.StateMutability, - IsFallback: true, - - // Fallback doesn't have any input or output - Inputs: nil, - Outputs: nil, - - // Legacy fields, keep them for backward compatibility - Constant: field.Constant, - Payable: field.Payable, - } + abi.Fallback = NewMethod("", "", field.StateMutability, field.Constant, field.Payable, true, false, nil, nil) case "receive": // New introduced function type in v0.6.0, check more detail // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function @@ -201,23 +166,7 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { if field.StateMutability != "payable" { return errors.New("the statemutability of receive can only be payable") } - abi.Receive = Method{ - Name: "", - RawName: "", - - // The `StateMutability` can only be payable, so constant - // is always true while payable is always false. - StateMutability: field.StateMutability, - IsReceive: true, - - // Receive doesn't have any input or output - Inputs: nil, - Outputs: nil, - - // Legacy fields, keep them for backward compatibility - Constant: field.Constant, - Payable: field.Payable, - } + abi.Receive = NewMethod("", "", "payable", field.Constant, field.Payable, false, true, nil, nil) case "event": name := field.Name _, ok := abi.Events[name] diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 352006cf5f..28bc1b739a 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -58,20 +58,14 @@ const jsondata2 = ` func TestReader(t *testing.T) { Uint256, _ := NewType("uint256", "", nil) - exp := ABI{ + abi := ABI{ Methods: map[string]Method{ - "balance": { - "balance", "balance", "view", false, false, false, false, nil, nil, - }, - "send": { - "send", "send", "", false, false, false, false, []Argument{ - {"amount", Uint256, false}, - }, nil, - }, + "balance": NewMethod("balance", "balance", "view", true, false, false, false, nil, nil), + "send": NewMethod("send", "send", "", false, false, false, false, []Argument{{"amount", Uint256, false}}, nil), }, } - abi, err := JSON(strings.NewReader(jsondata)) + exp, err := JSON(strings.NewReader(jsondata)) if err != nil { t.Error(err) } @@ -173,7 +167,7 @@ func TestTestSlice(t *testing.T) { func TestMethodSignature(t *testing.T) { String, _ := NewType("string", "", nil) - m := Method{"foo", "foo", "", false, false, false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil} + m := NewMethod("foo", "foo", "", false, false, false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil) exp := "foo(string,string)" if m.Sig() != exp { t.Error("signature mismatch", exp, "!=", m.Sig()) @@ -185,7 +179,7 @@ func TestMethodSignature(t *testing.T) { } uintt, _ := NewType("uint256", "", nil) - m = Method{"foo", "foo", "", false, false, false, false, []Argument{{"bar", uintt, false}}, nil} + m = NewMethod("foo", "foo", "", false, false, false, false, []Argument{{"bar", uintt, false}}, nil) exp = "foo(uint256)" if m.Sig() != exp { t.Error("signature mismatch", exp, "!=", m.Sig()) @@ -204,7 +198,7 @@ func TestMethodSignature(t *testing.T) { {Name: "y", Type: "int256"}, }}, }) - m = Method{"foo", "foo", "", false, false, false, false, []Argument{{"s", s, false}, {"bar", String, false}}, nil} + m = NewMethod("foo", "foo", "", false, false, false, false, []Argument{{"s", s, false}, {"bar", String, false}}, nil) exp = "foo((int256,int256[],(int256,int256)[],(int256,int256)[2]),string)" if m.Sig() != exp { t.Error("signature mismatch", exp, "!=", m.Sig()) diff --git a/accounts/abi/method.go b/accounts/abi/method.go index 217c3d2e68..1b629c758c 100644 --- a/accounts/abi/method.go +++ b/accounts/abi/method.go @@ -60,6 +60,41 @@ type Method struct { Inputs Arguments Outputs Arguments + // internal fields to prevent recalculation + sig string + id []byte + str string +} + +// NewMethod creates a new Method. +// It also precomputes the sig representation and the string representation +// of the method. +// A method should always be created using NewMethod. +func NewMethod(name string, rawName string, mutability string, isConst, isPayable, isFallback, isReceive bool, inputs Arguments, outputs Arguments) Method { + method := Method{ + Name: name, + RawName: rawName, + StateMutability: mutability, + Constant: isConst, + Payable: isPayable, + IsFallback: isFallback, + IsReceive: isReceive, + Inputs: inputs, + Outputs: outputs, + } + method.initFields() + return method +} + +// initFields should only be used in the unit tests +// to create valid Method objects from json. +func (method *Method) initFields() { + // Calculate and set Signature + method.sig = method.calcSig() + // Calculate the method id as the first 4 bytes of the hash of sig. + method.id = crypto.Keccak256([]byte(method.sig))[:4] + // Calculate and set the String representation + method.str = method.calcString() } // Sig returns the methods string signature according to the ABI spec. @@ -75,6 +110,21 @@ func (method Method) Sig() string { if method.IsFallback || method.IsReceive { return "" } + return method.sig +} + +func (method Method) String() string { + return method.str +} + +// ID returns the canonical representation of the method's signature used by the +// abi definition to identify method names and types. +func (method Method) ID() []byte { + return method.id +} + +// calcSig calculates the method string signature. +func (method Method) calcSig() string { types := make([]string, len(method.Inputs)) for i, input := range method.Inputs { types[i] = input.Type.String() @@ -82,7 +132,7 @@ func (method Method) Sig() string { return fmt.Sprintf("%v(%v)", method.RawName, strings.Join(types, ",")) } -func (method Method) String() string { +func (method Method) calcString() string { inputs := make([]string, len(method.Inputs)) for i, input := range method.Inputs { inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name) @@ -112,12 +162,6 @@ func (method Method) String() string { return fmt.Sprintf("%v(%v) %sreturns(%v)", identity, strings.Join(inputs, ", "), state, strings.Join(outputs, ", ")) } -// ID returns the canonical representation of the method's signature used by the -// abi definition to identify method names and types. -func (method Method) ID() []byte { - return crypto.Keccak256([]byte(method.Sig()))[:4] -} - // IsConstant returns the indicator whether the method is read-only. func (method Method) IsConstant() bool { return method.StateMutability == "view" || method.StateMutability == "pure" || method.Constant