accounts/abi: prevent recalculation of ID, Sig and String

This commit is contained in:
MariusVanDerWijden 2020-02-13 13:48:09 +01:00 committed by Marius van der Wijden
parent 00064ddcfb
commit ad60f0cec0
3 changed files with 65 additions and 78 deletions

View file

@ -139,59 +139,24 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
for _, field := range fields { for _, field := range fields {
switch field.Type { switch field.Type {
case "constructor": case "constructor":
abi.Constructor = Method{ abi.Constructor = NewMethod("", "", field.StateMutability, field.Constant, field.Payable, false, false, field.Inputs, nil)
Inputs: field.Inputs, // empty defaults to function according to the abi spec
case "function", "":
// 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":
name := field.Name name := field.Name
_, ok := abi.Methods[name] _, ok := abi.Methods[name]
for idx := 0; ok; idx++ { for idx := 0; ok; idx++ {
name = fmt.Sprintf("%s%d", field.Name, idx) name = fmt.Sprintf("%s%d", field.Name, idx)
_, ok = abi.Methods[name] _, ok = abi.Methods[name]
} }
abi.Methods[name] = Method{ isConst := field.Constant || field.StateMutability == "pure" || field.StateMutability == "view"
Name: name, abi.Methods[name] = NewMethod(name, field.Name, field.StateMutability, isConst, field.Payable, false, false, field.Inputs, field.Outputs)
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,
}
case "fallback": case "fallback":
// New introduced function type in v0.6.0, check more detail // New introduced function type in v0.6.0, check more detail
// here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function
if abi.HasFallback() { if abi.HasFallback() {
return errors.New("only single fallback is allowed") return errors.New("only single fallback is allowed")
} }
abi.Fallback = Method{ abi.Fallback = NewMethod("", "", field.StateMutability, field.Constant, field.Payable, true, false, nil, nil)
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,
}
case "receive": case "receive":
// New introduced function type in v0.6.0, check more detail // New introduced function type in v0.6.0, check more detail
// here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function // 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" { if field.StateMutability != "payable" {
return errors.New("the statemutability of receive can only be payable") return errors.New("the statemutability of receive can only be payable")
} }
abi.Receive = Method{ abi.Receive = NewMethod("", "", "payable", field.Constant, field.Payable, false, true, nil, nil)
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,
}
case "event": case "event":
name := field.Name name := field.Name
_, ok := abi.Events[name] _, ok := abi.Events[name]

View file

@ -58,20 +58,14 @@ const jsondata2 = `
func TestReader(t *testing.T) { func TestReader(t *testing.T) {
Uint256, _ := NewType("uint256", "", nil) Uint256, _ := NewType("uint256", "", nil)
exp := ABI{ abi := ABI{
Methods: map[string]Method{ Methods: map[string]Method{
"balance": { "balance": NewMethod("balance", "balance", "view", true, false, false, false, nil, nil),
"balance", "balance", "view", false, false, false, false, nil, nil, "send": NewMethod("send", "send", "", false, false, false, false, []Argument{{"amount", Uint256, false}}, nil),
},
"send": {
"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 { if err != nil {
t.Error(err) t.Error(err)
} }
@ -173,7 +167,7 @@ func TestTestSlice(t *testing.T) {
func TestMethodSignature(t *testing.T) { func TestMethodSignature(t *testing.T) {
String, _ := NewType("string", "", nil) 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)" exp := "foo(string,string)"
if m.Sig() != exp { if m.Sig() != exp {
t.Error("signature mismatch", exp, "!=", m.Sig()) t.Error("signature mismatch", exp, "!=", m.Sig())
@ -185,7 +179,7 @@ func TestMethodSignature(t *testing.T) {
} }
uintt, _ := NewType("uint256", "", nil) 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)" exp = "foo(uint256)"
if m.Sig() != exp { if m.Sig() != exp {
t.Error("signature mismatch", exp, "!=", m.Sig()) t.Error("signature mismatch", exp, "!=", m.Sig())
@ -204,7 +198,7 @@ func TestMethodSignature(t *testing.T) {
{Name: "y", Type: "int256"}, {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)" exp = "foo((int256,int256[],(int256,int256)[],(int256,int256)[2]),string)"
if m.Sig() != exp { if m.Sig() != exp {
t.Error("signature mismatch", exp, "!=", m.Sig()) t.Error("signature mismatch", exp, "!=", m.Sig())

View file

@ -60,6 +60,41 @@ type Method struct {
Inputs Arguments Inputs Arguments
Outputs 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. // 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 { if method.IsFallback || method.IsReceive {
return "" 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)) types := make([]string, len(method.Inputs))
for i, input := range method.Inputs { for i, input := range method.Inputs {
types[i] = input.Type.String() types[i] = input.Type.String()
@ -82,7 +132,7 @@ func (method Method) Sig() string {
return fmt.Sprintf("%v(%v)", method.RawName, strings.Join(types, ",")) 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)) inputs := make([]string, len(method.Inputs))
for i, input := range method.Inputs { for i, input := range method.Inputs {
inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name) 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, ", ")) 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. // IsConstant returns the indicator whether the method is read-only.
func (method Method) IsConstant() bool { func (method Method) IsConstant() bool {
return method.StateMutability == "view" || method.StateMutability == "pure" || method.Constant return method.StateMutability == "view" || method.StateMutability == "pure" || method.Constant