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 {
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]

View file

@ -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())

View file

@ -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