package abi import ( "encoding/json" "fmt" "io" "strings" "github.com/ethereum/go-ethereum/crypto" ) // Callable method given a `Name` and whether the method is a constant. // If the method is `Const` no transaction needs to be created for this // particular Method call. It can easily be simulated using a local VM. // For example a `Balance()` method only needs to retrieve something // from the storage and therefor requires no Tx to be send to the // network. A method such as `Transact` does require a Tx and thus will // be flagged `true`. // Input specifies the required input parameters for this gives method. type Method struct { Name string Const bool Input []Argument Return Type // not yet implemented } // Returns the methods string signature according to the ABI spec. // // Example // // function foo(uint32 a, int b) = "foo(uint32,int256)" // // Please note that "int" is substitute for its canonical representation "int256" func (m Method) String() (out string) { out += m.Name types := make([]string, len(m.Input)) i := 0 for _, input := range m.Input { types[i] = input.Type.String() i++ } out += "(" + strings.Join(types, ",") + ")" return } func (m Method) Id() []byte { return crypto.Sha3([]byte(m.String()))[:4] } // Argument holds the name of the argument and the corresponding type. // Types are used when packing and testing arguments. type Argument struct { Name string Type Type } func (a *Argument) UnmarshalJSON(data []byte) error { var extarg struct { Name string Type string } err := json.Unmarshal(data, &extarg) if err != nil { return fmt.Errorf("argument json err: %v", err) } a.Type, err = NewType(extarg.Type) if err != nil { return err } a.Name = extarg.Name return nil } // The ABI holds information about a contract's context and available // invokable methods. It will allow you to type check function calls and // packs data accordingly. type ABI struct { Methods map[string]Method } // tests, tests whether the given input would result in a successful // call. Checks argument list count and matches input to `input`. func (abi ABI) pack(name string, args ...interface{}) ([]byte, error) { method := abi.Methods[name] var ret []byte for i, a := range args { input := method.Input[i] packed, err := input.Type.pack(a) if err != nil { return nil, fmt.Errorf("`%s` %v", name, err) } ret = append(ret, packed...) } return ret, nil } // Pack the given method name to conform the ABI. Method call's data // will consist of method_id, args0, arg1, ... argN. Method id consists // of 4 bytes and arguments are all 32 bytes. // Method ids are created from the first 4 bytes of the hash of the // methods string signature. (signature = baz(uint32,string32)) func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { method, exist := abi.Methods[name] if !exist { return nil, fmt.Errorf("method '%s' not found", name) } // start with argument count match if len(args) != len(method.Input) { return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(method.Input)) } arguments, err := abi.pack(name, args...) if err != nil { return nil, err } // Set function id packed := abi.Methods[name].Id() packed = append(packed, arguments...) return packed, nil } func (abi *ABI) UnmarshalJSON(data []byte) error { var methods []Method if err := json.Unmarshal(data, &methods); err != nil { return err } abi.Methods = make(map[string]Method) for _, method := range methods { abi.Methods[method.Name] = method } return nil } func JSON(reader io.Reader) (ABI, error) { dec := json.NewDecoder(reader) var abi ABI if err := dec.Decode(&abi); err != nil { return ABI{}, err } return abi, nil }