mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-16 18:00:46 +00:00
Merge pull request #798 from gzliudan/upgrade_accounts
accounts: upgrade to 2024-12-17
This commit is contained in:
commit
08e37a48ba
214 changed files with 21229 additions and 9502 deletions
|
|
@ -36,7 +36,7 @@ func RunContract(chain consensus.ChainContext, statedb *state.StateDB, contractA
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var unpackResult interface{}
|
var unpackResult interface{}
|
||||||
err = abi.Unpack(&unpackResult, method, result)
|
err = abi.UnpackIntoInterface(&unpackResult, method, result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ func (t *TradingStateDB) Error() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exist reports whether the given orderId address exists in the state.
|
// Exist reports whether the given orderId address exists in the state.
|
||||||
// Notably this also returns true for suicided exchanges.
|
// Notably this also returns true for self-destructed exchanges.
|
||||||
func (t *TradingStateDB) Exist(addr common.Hash) bool {
|
func (t *TradingStateDB) Exist(addr common.Hash) bool {
|
||||||
return t.getStateExchangeObject(addr) != nil
|
return t.getStateExchangeObject(addr) != nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ func (ls *LendingStateDB) Error() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exist reports whether the given tradeId address exists in the state.
|
// Exist reports whether the given tradeId address exists in the state.
|
||||||
// Notably this also returns true for suicided lenddinges.
|
// Notably this also returns true for self-destructed lenddinges.
|
||||||
func (ls *LendingStateDB) Exist(addr common.Hash) bool {
|
func (ls *LendingStateDB) Exist(addr common.Hash) bool {
|
||||||
return ls.getLendingExchange(addr) != nil
|
return ls.getLendingExchange(addr) != nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,17 +22,26 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The ABI holds information about a contract's context and available
|
// The ABI holds information about a contract's context and available
|
||||||
// invokable methods. It will allow you to type check function calls and
|
// invocable methods. It will allow you to type check function calls and
|
||||||
// packs data accordingly.
|
// packs data accordingly.
|
||||||
type ABI struct {
|
type ABI struct {
|
||||||
Constructor Method
|
Constructor Method
|
||||||
Methods map[string]Method
|
Methods map[string]Method
|
||||||
Events map[string]Event
|
Events map[string]Event
|
||||||
|
Errors map[string]Error
|
||||||
|
|
||||||
|
// Additional "special" functions introduced in solidity v0.6.0.
|
||||||
|
// It's separated from the original default fallback. Each contract
|
||||||
|
// can only define one fallback and receive function.
|
||||||
|
Fallback Method // Note it's also used to represent legacy fallback before v0.6.0
|
||||||
|
Receive Method
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON returns a parsed ABI interface and error if it failed.
|
// JSON returns a parsed ABI interface and error if it failed.
|
||||||
|
|
@ -43,7 +52,6 @@ func JSON(reader io.Reader) (ABI, error) {
|
||||||
if err := dec.Decode(&abi); err != nil {
|
if err := dec.Decode(&abi); err != nil {
|
||||||
return ABI{}, err
|
return ABI{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return abi, nil
|
return abi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,111 +69,246 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return arguments, nil
|
return arguments, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
method, exist := abi.Methods[name]
|
method, exist := abi.Methods[name]
|
||||||
if !exist {
|
if !exist {
|
||||||
return nil, fmt.Errorf("method '%s' not found", name)
|
return nil, fmt.Errorf("method '%s' not found", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
arguments, err := method.Inputs.Pack(args...)
|
arguments, err := method.Inputs.Pack(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Pack up the method ID too if not a constructor and return
|
// Pack up the method ID too if not a constructor and return
|
||||||
return append(method.Id(), arguments...), nil
|
return append(method.ID, arguments...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unpack output in v according to the abi specification
|
func (abi ABI) getArguments(name string, data []byte) (Arguments, error) {
|
||||||
func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) {
|
|
||||||
if len(output) == 0 {
|
|
||||||
return errors.New("abi: unmarshalling empty output")
|
|
||||||
}
|
|
||||||
// since there can't be naming collisions with contracts and events,
|
// since there can't be naming collisions with contracts and events,
|
||||||
// we need to decide whether we're calling a method or an event
|
// we need to decide whether we're calling a method, event or an error
|
||||||
|
var args Arguments
|
||||||
if method, ok := abi.Methods[name]; ok {
|
if method, ok := abi.Methods[name]; ok {
|
||||||
if len(output)%32 != 0 {
|
if len(data)%32 != 0 {
|
||||||
return errors.New("abi: improperly formatted output")
|
return nil, fmt.Errorf("abi: improperly formatted output: %q - Bytes: %+v", data, data)
|
||||||
}
|
}
|
||||||
return method.Outputs.Unpack(v, output)
|
args = method.Outputs
|
||||||
} else if event, ok := abi.Events[name]; ok {
|
|
||||||
return event.Inputs.Unpack(v, output)
|
|
||||||
}
|
}
|
||||||
return errors.New("abi: could not locate named method or event")
|
if event, ok := abi.Events[name]; ok {
|
||||||
|
args = event.Inputs
|
||||||
|
}
|
||||||
|
if err, ok := abi.Errors[name]; ok {
|
||||||
|
args = err.Inputs
|
||||||
|
}
|
||||||
|
if args == nil {
|
||||||
|
return nil, fmt.Errorf("abi: could not locate named method, event or error: %s", name)
|
||||||
|
}
|
||||||
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler interface
|
// Unpack unpacks the output according to the abi specification.
|
||||||
|
func (abi ABI) Unpack(name string, data []byte) ([]interface{}, error) {
|
||||||
|
args, err := abi.getArguments(name, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return args.Unpack(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpackIntoInterface unpacks the output in v according to the abi specification.
|
||||||
|
// It performs an additional copy. Please only use, if you want to unpack into a
|
||||||
|
// structure that does not strictly conform to the abi structure (e.g. has additional arguments)
|
||||||
|
func (abi ABI) UnpackIntoInterface(v interface{}, name string, data []byte) error {
|
||||||
|
args, err := abi.getArguments(name, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
unpacked, err := args.Unpack(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return args.Copy(v, unpacked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpackIntoMap unpacks a log into the provided map[string]interface{}.
|
||||||
|
func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) {
|
||||||
|
args, err := abi.getArguments(name, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return args.UnpackIntoMap(v, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler interface.
|
||||||
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||||
var fields []struct {
|
var fields []struct {
|
||||||
Type string
|
Type string
|
||||||
Name string
|
Name string
|
||||||
Constant bool
|
Inputs []Argument
|
||||||
Anonymous bool
|
Outputs []Argument
|
||||||
Inputs []Argument
|
|
||||||
Outputs []Argument
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Status indicator which can be: "pure", "view",
|
||||||
|
// "nonpayable" or "payable".
|
||||||
|
StateMutability string
|
||||||
|
|
||||||
|
// Deprecated Status indicators, but removed in v0.6.0.
|
||||||
|
Constant bool // True if function is either pure or view
|
||||||
|
Payable bool // True if function is payable
|
||||||
|
|
||||||
|
// Event relevant indicator represents the event is
|
||||||
|
// declared as anonymous.
|
||||||
|
Anonymous bool
|
||||||
|
}
|
||||||
if err := json.Unmarshal(data, &fields); err != nil {
|
if err := json.Unmarshal(data, &fields); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
abi.Methods = make(map[string]Method)
|
abi.Methods = make(map[string]Method)
|
||||||
abi.Events = make(map[string]Event)
|
abi.Events = make(map[string]Event)
|
||||||
|
abi.Errors = make(map[string]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("", "", Constructor, field.StateMutability, field.Constant, field.Payable, field.Inputs, nil)
|
||||||
Inputs: field.Inputs,
|
case "function":
|
||||||
|
name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Methods[s]; return ok })
|
||||||
|
abi.Methods[name] = NewMethod(name, field.Name, Function, field.StateMutability, field.Constant, field.Payable, 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")
|
||||||
}
|
}
|
||||||
// empty defaults to function according to the abi spec
|
abi.Fallback = NewMethod("", "", Fallback, field.StateMutability, field.Constant, field.Payable, nil, nil)
|
||||||
case "function", "":
|
case "receive":
|
||||||
abi.Methods[field.Name] = Method{
|
// New introduced function type in v0.6.0, check more detail
|
||||||
Name: field.Name,
|
// here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function
|
||||||
Const: field.Constant,
|
if abi.HasReceive() {
|
||||||
Inputs: field.Inputs,
|
return errors.New("only single receive is allowed")
|
||||||
Outputs: field.Outputs,
|
|
||||||
}
|
}
|
||||||
|
if field.StateMutability != "payable" {
|
||||||
|
return errors.New("the statemutability of receive can only be payable")
|
||||||
|
}
|
||||||
|
abi.Receive = NewMethod("", "", Receive, field.StateMutability, field.Constant, field.Payable, nil, nil)
|
||||||
case "event":
|
case "event":
|
||||||
abi.Events[field.Name] = Event{
|
name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Events[s]; return ok })
|
||||||
Name: field.Name,
|
abi.Events[name] = NewEvent(name, field.Name, field.Anonymous, field.Inputs)
|
||||||
Anonymous: field.Anonymous,
|
case "error":
|
||||||
Inputs: field.Inputs,
|
// Errors cannot be overloaded or overridden but are inherited,
|
||||||
}
|
// no need to resolve the name conflict here.
|
||||||
|
abi.Errors[field.Name] = NewError(field.Name, field.Inputs)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("abi: could not recognize type %v of field %v", field.Type, field.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MethodById looks up a method by the 4-byte id
|
// MethodById looks up a method by the 4-byte id,
|
||||||
// returns nil if none found
|
// returns nil if none found.
|
||||||
func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
|
func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
|
||||||
|
if len(sigdata) < 4 {
|
||||||
|
return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata))
|
||||||
|
}
|
||||||
for _, method := range abi.Methods {
|
for _, method := range abi.Methods {
|
||||||
if bytes.Equal(method.Id(), sigdata[:4]) {
|
if bytes.Equal(method.ID, sigdata[:4]) {
|
||||||
return &method, nil
|
return &method, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no method with id: %#x", sigdata[:4])
|
return nil, fmt.Errorf("no method with id: %#x", sigdata[:4])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EventByID looks an event up by its topic hash in the
|
||||||
|
// ABI and returns nil if none found.
|
||||||
|
func (abi *ABI) EventByID(topic common.Hash) (*Event, error) {
|
||||||
|
for _, event := range abi.Events {
|
||||||
|
if bytes.Equal(event.ID.Bytes(), topic.Bytes()) {
|
||||||
|
return &event, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no event with id: %#x", topic.Hex())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorByID looks up an error by the 4-byte id,
|
||||||
|
// returns nil if none found.
|
||||||
|
func (abi *ABI) ErrorByID(sigdata [4]byte) (*Error, error) {
|
||||||
|
for _, errABI := range abi.Errors {
|
||||||
|
if bytes.Equal(errABI.ID[:4], sigdata[:]) {
|
||||||
|
return &errABI, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no error with id: %#x", sigdata[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasFallback returns an indicator whether a fallback function is included.
|
||||||
|
func (abi *ABI) HasFallback() bool {
|
||||||
|
return abi.Fallback.Type == Fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasReceive returns an indicator whether a receive function is included.
|
||||||
|
func (abi *ABI) HasReceive() bool {
|
||||||
|
return abi.Receive.Type == Receive
|
||||||
|
}
|
||||||
|
|
||||||
// revertSelector is a special function selector for revert reason unpacking.
|
// revertSelector is a special function selector for revert reason unpacking.
|
||||||
var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
|
var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
|
||||||
|
|
||||||
|
// panicSelector is a special function selector for panic reason unpacking.
|
||||||
|
var panicSelector = crypto.Keccak256([]byte("Panic(uint256)"))[:4]
|
||||||
|
|
||||||
|
// panicReasons map is for readable panic codes
|
||||||
|
// see this linkage for the details
|
||||||
|
// https://docs.soliditylang.org/en/v0.8.21/control-structures.html#panic-via-assert-and-error-via-require
|
||||||
|
// the reason string list is copied from ether.js
|
||||||
|
// https://github.com/ethers-io/ethers.js/blob/fa3a883ff7c88611ce766f58bdd4b8ac90814470/src.ts/abi/interface.ts#L207-L218
|
||||||
|
var panicReasons = map[uint64]string{
|
||||||
|
0x00: "generic panic",
|
||||||
|
0x01: "assert(false)",
|
||||||
|
0x11: "arithmetic underflow or overflow",
|
||||||
|
0x12: "division or modulo by zero",
|
||||||
|
0x21: "enum overflow",
|
||||||
|
0x22: "invalid encoded storage byte array accessed",
|
||||||
|
0x31: "out-of-bounds array access; popping on an empty array",
|
||||||
|
0x32: "out-of-bounds access of an array or bytesN",
|
||||||
|
0x41: "out of memory",
|
||||||
|
0x51: "uninitialized function",
|
||||||
|
}
|
||||||
|
|
||||||
// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
|
// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
|
||||||
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
|
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
|
||||||
// the provided revert reason is abi-encoded as if it were a call to a function
|
// the provided revert reason is abi-encoded as if it were a call to function
|
||||||
// `Error(string)`. So it's a special tool for it.
|
// `Error(string)` or `Panic(uint256)`. So it's a special tool for it.
|
||||||
func UnpackRevert(data []byte) (string, error) {
|
func UnpackRevert(data []byte) (string, error) {
|
||||||
if len(data) < 4 {
|
if len(data) < 4 {
|
||||||
return "", errors.New("invalid data for unpacking")
|
return "", errors.New("invalid data for unpacking")
|
||||||
}
|
}
|
||||||
if !bytes.Equal(data[:4], revertSelector) {
|
switch {
|
||||||
|
case bytes.Equal(data[:4], revertSelector):
|
||||||
|
typ, err := NewType("string", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return unpacked[0].(string), nil
|
||||||
|
case bytes.Equal(data[:4], panicSelector):
|
||||||
|
typ, err := NewType("uint256", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pCode := unpacked[0].(*big.Int)
|
||||||
|
// uint64 safety check for future
|
||||||
|
// but the code is not bigger than MAX(uint64) now
|
||||||
|
if pCode.IsUint64() {
|
||||||
|
if reason, ok := panicReasons[pCode.Uint64()]; ok {
|
||||||
|
return reason, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("unknown panic code: %#x", pCode), nil
|
||||||
|
default:
|
||||||
return "", errors.New("invalid data for unpacking")
|
return "", errors.New("invalid data for unpacking")
|
||||||
}
|
}
|
||||||
typ, _ := NewType("string")
|
|
||||||
unpacked, err := (Arguments{{Type: typ}}).Unpack2(data[4:])
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return unpacked[0].(string), nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,63 +21,116 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common/math"
|
"github.com/XinFinOrg/XDPoSChain/common/math"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/internal/testrand"
|
||||||
)
|
)
|
||||||
|
|
||||||
const jsondata = `
|
const jsondata = `
|
||||||
[
|
[
|
||||||
{ "type" : "function", "name" : "balance", "constant" : true },
|
{ "type" : "function", "name" : ""},
|
||||||
{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }
|
{ "type" : "function", "name" : "balance", "stateMutability" : "view" },
|
||||||
|
{ "type" : "function", "name" : "send", "inputs" : [ { "name" : "amount", "type" : "uint256" } ] },
|
||||||
|
{ "type" : "function", "name" : "test", "inputs" : [ { "name" : "number", "type" : "uint32" } ] },
|
||||||
|
{ "type" : "function", "name" : "string", "inputs" : [ { "name" : "inputs", "type" : "string" } ] },
|
||||||
|
{ "type" : "function", "name" : "bool", "inputs" : [ { "name" : "inputs", "type" : "bool" } ] },
|
||||||
|
{ "type" : "function", "name" : "address", "inputs" : [ { "name" : "inputs", "type" : "address" } ] },
|
||||||
|
{ "type" : "function", "name" : "uint64[2]", "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] },
|
||||||
|
{ "type" : "function", "name" : "uint64[]", "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] },
|
||||||
|
{ "type" : "function", "name" : "int8", "inputs" : [ { "name" : "inputs", "type" : "int8" } ] },
|
||||||
|
{ "type" : "function", "name" : "bytes32", "inputs" : [ { "name" : "inputs", "type" : "bytes32" } ] },
|
||||||
|
{ "type" : "function", "name" : "foo", "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] },
|
||||||
|
{ "type" : "function", "name" : "bar", "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] },
|
||||||
|
{ "type" : "function", "name" : "slice", "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] },
|
||||||
|
{ "type" : "function", "name" : "slice256", "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] },
|
||||||
|
{ "type" : "function", "name" : "sliceAddress", "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] },
|
||||||
|
{ "type" : "function", "name" : "sliceMultiAddress", "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] },
|
||||||
|
{ "type" : "function", "name" : "nestedArray", "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] },
|
||||||
|
{ "type" : "function", "name" : "nestedArray2", "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] },
|
||||||
|
{ "type" : "function", "name" : "nestedSlice", "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] },
|
||||||
|
{ "type" : "function", "name" : "receive", "inputs" : [ { "name" : "memo", "type" : "bytes" }], "outputs" : [], "payable" : true, "stateMutability" : "payable" },
|
||||||
|
{ "type" : "function", "name" : "fixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
|
||||||
|
{ "type" : "function", "name" : "fixedArrBytes", "stateMutability" : "view", "inputs" : [ { "name" : "bytes", "type" : "bytes" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
|
||||||
|
{ "type" : "function", "name" : "mixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" } ] },
|
||||||
|
{ "type" : "function", "name" : "doubleFixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type" : "uint256[2]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] },
|
||||||
|
{ "type" : "function", "name" : "multipleMixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type" : "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] },
|
||||||
|
{ "type" : "function", "name" : "overloadedNames", "stateMutability" : "view", "inputs": [ { "components": [ { "internalType": "uint256", "name": "_f", "type": "uint256" }, { "internalType": "uint256", "name": "__f", "type": "uint256"}, { "internalType": "uint256", "name": "f", "type": "uint256"}],"internalType": "struct Overloader.F", "name": "f","type": "tuple"}]}
|
||||||
]`
|
]`
|
||||||
|
|
||||||
const jsondata2 = `
|
var (
|
||||||
[
|
Uint256, _ = NewType("uint256", "", nil)
|
||||||
{ "type" : "function", "name" : "balance", "constant" : true },
|
Uint32, _ = NewType("uint32", "", nil)
|
||||||
{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] },
|
Uint16, _ = NewType("uint16", "", nil)
|
||||||
{ "type" : "function", "name" : "test", "constant" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] },
|
String, _ = NewType("string", "", nil)
|
||||||
{ "type" : "function", "name" : "string", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] },
|
Bool, _ = NewType("bool", "", nil)
|
||||||
{ "type" : "function", "name" : "bool", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] },
|
Bytes, _ = NewType("bytes", "", nil)
|
||||||
{ "type" : "function", "name" : "address", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] },
|
Bytes32, _ = NewType("bytes32", "", nil)
|
||||||
{ "type" : "function", "name" : "uint64[2]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] },
|
Address, _ = NewType("address", "", nil)
|
||||||
{ "type" : "function", "name" : "uint64[]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] },
|
Uint64Arr, _ = NewType("uint64[]", "", nil)
|
||||||
{ "type" : "function", "name" : "foo", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] },
|
AddressArr, _ = NewType("address[]", "", nil)
|
||||||
{ "type" : "function", "name" : "bar", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] },
|
Int8, _ = NewType("int8", "", nil)
|
||||||
{ "type" : "function", "name" : "slice", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] },
|
// Special types for testing
|
||||||
{ "type" : "function", "name" : "slice256", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] },
|
Uint32Arr2, _ = NewType("uint32[2]", "", nil)
|
||||||
{ "type" : "function", "name" : "sliceAddress", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] },
|
Uint64Arr2, _ = NewType("uint64[2]", "", nil)
|
||||||
{ "type" : "function", "name" : "sliceMultiAddress", "constant" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] }
|
Uint256Arr, _ = NewType("uint256[]", "", nil)
|
||||||
]`
|
Uint256Arr2, _ = NewType("uint256[2]", "", nil)
|
||||||
|
Uint256Arr3, _ = NewType("uint256[3]", "", nil)
|
||||||
|
Uint256ArrNested, _ = NewType("uint256[2][2]", "", nil)
|
||||||
|
Uint8ArrNested, _ = NewType("uint8[][2]", "", nil)
|
||||||
|
Uint8SliceNested, _ = NewType("uint8[][]", "", nil)
|
||||||
|
TupleF, _ = NewType("tuple", "struct Overloader.F", []ArgumentMarshaling{
|
||||||
|
{Name: "_f", Type: "uint256"},
|
||||||
|
{Name: "__f", Type: "uint256"},
|
||||||
|
{Name: "f", Type: "uint256"}})
|
||||||
|
)
|
||||||
|
|
||||||
|
var methods = map[string]Method{
|
||||||
|
"": NewMethod("", "", Function, "", false, false, nil, nil),
|
||||||
|
"balance": NewMethod("balance", "balance", Function, "view", false, false, nil, nil),
|
||||||
|
"send": NewMethod("send", "send", Function, "", false, false, []Argument{{"amount", Uint256, false}}, nil),
|
||||||
|
"test": NewMethod("test", "test", Function, "", false, false, []Argument{{"number", Uint32, false}}, nil),
|
||||||
|
"string": NewMethod("string", "string", Function, "", false, false, []Argument{{"inputs", String, false}}, nil),
|
||||||
|
"bool": NewMethod("bool", "bool", Function, "", false, false, []Argument{{"inputs", Bool, false}}, nil),
|
||||||
|
"address": NewMethod("address", "address", Function, "", false, false, []Argument{{"inputs", Address, false}}, nil),
|
||||||
|
"uint64[]": NewMethod("uint64[]", "uint64[]", Function, "", false, false, []Argument{{"inputs", Uint64Arr, false}}, nil),
|
||||||
|
"uint64[2]": NewMethod("uint64[2]", "uint64[2]", Function, "", false, false, []Argument{{"inputs", Uint64Arr2, false}}, nil),
|
||||||
|
"int8": NewMethod("int8", "int8", Function, "", false, false, []Argument{{"inputs", Int8, false}}, nil),
|
||||||
|
"bytes32": NewMethod("bytes32", "bytes32", Function, "", false, false, []Argument{{"inputs", Bytes32, false}}, nil),
|
||||||
|
"foo": NewMethod("foo", "foo", Function, "", false, false, []Argument{{"inputs", Uint32, false}}, nil),
|
||||||
|
"bar": NewMethod("bar", "bar", Function, "", false, false, []Argument{{"inputs", Uint32, false}, {"string", Uint16, false}}, nil),
|
||||||
|
"slice": NewMethod("slice", "slice", Function, "", false, false, []Argument{{"inputs", Uint32Arr2, false}}, nil),
|
||||||
|
"slice256": NewMethod("slice256", "slice256", Function, "", false, false, []Argument{{"inputs", Uint256Arr2, false}}, nil),
|
||||||
|
"sliceAddress": NewMethod("sliceAddress", "sliceAddress", Function, "", false, false, []Argument{{"inputs", AddressArr, false}}, nil),
|
||||||
|
"sliceMultiAddress": NewMethod("sliceMultiAddress", "sliceMultiAddress", Function, "", false, false, []Argument{{"a", AddressArr, false}, {"b", AddressArr, false}}, nil),
|
||||||
|
"nestedArray": NewMethod("nestedArray", "nestedArray", Function, "", false, false, []Argument{{"a", Uint256ArrNested, false}, {"b", AddressArr, false}}, nil),
|
||||||
|
"nestedArray2": NewMethod("nestedArray2", "nestedArray2", Function, "", false, false, []Argument{{"a", Uint8ArrNested, false}}, nil),
|
||||||
|
"nestedSlice": NewMethod("nestedSlice", "nestedSlice", Function, "", false, false, []Argument{{"a", Uint8SliceNested, false}}, nil),
|
||||||
|
"receive": NewMethod("receive", "receive", Function, "payable", false, true, []Argument{{"memo", Bytes, false}}, []Argument{}),
|
||||||
|
"fixedArrStr": NewMethod("fixedArrStr", "fixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr", Uint256Arr2, false}}, nil),
|
||||||
|
"fixedArrBytes": NewMethod("fixedArrBytes", "fixedArrBytes", Function, "view", false, false, []Argument{{"bytes", Bytes, false}, {"fixedArr", Uint256Arr2, false}}, nil),
|
||||||
|
"mixedArrStr": NewMethod("mixedArrStr", "mixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr", Uint256Arr2, false}, {"dynArr", Uint256Arr, false}}, nil),
|
||||||
|
"doubleFixedArrStr": NewMethod("doubleFixedArrStr", "doubleFixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr1", Uint256Arr2, false}, {"fixedArr2", Uint256Arr3, false}}, nil),
|
||||||
|
"multipleMixedArrStr": NewMethod("multipleMixedArrStr", "multipleMixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr1", Uint256Arr2, false}, {"dynArr", Uint256Arr, false}, {"fixedArr2", Uint256Arr3, false}}, nil),
|
||||||
|
"overloadedNames": NewMethod("overloadedNames", "overloadedNames", Function, "view", false, false, []Argument{{"f", TupleF, false}}, nil),
|
||||||
|
}
|
||||||
|
|
||||||
func TestReader(t *testing.T) {
|
func TestReader(t *testing.T) {
|
||||||
Uint256, _ := NewType("uint256")
|
t.Parallel()
|
||||||
exp := ABI{
|
abi := ABI{
|
||||||
Methods: map[string]Method{
|
Methods: methods,
|
||||||
"balance": {
|
|
||||||
"balance", true, nil, nil,
|
|
||||||
},
|
|
||||||
"send": {
|
|
||||||
"send", 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.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// deep equal fails for some reason
|
|
||||||
for name, expM := range exp.Methods {
|
for name, expM := range exp.Methods {
|
||||||
gotM, exist := abi.Methods[name]
|
gotM, exist := abi.Methods[name]
|
||||||
if !exist {
|
if !exist {
|
||||||
|
|
@ -99,11 +152,61 @@ func TestReader(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTestNumbers(t *testing.T) {
|
func TestInvalidABI(t *testing.T) {
|
||||||
abi, err := JSON(strings.NewReader(jsondata2))
|
t.Parallel()
|
||||||
|
json := `[{ "type" : "function", "name" : "", "constant" : fals }]`
|
||||||
|
_, err := JSON(strings.NewReader(json))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("invalid json should produce error")
|
||||||
|
}
|
||||||
|
json2 := `[{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "typ" : "uint256" } ] }]`
|
||||||
|
_, err = JSON(strings.NewReader(json2))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("invalid json should produce error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConstructor tests a constructor function.
|
||||||
|
// The test is based on the following contract:
|
||||||
|
//
|
||||||
|
// contract TestConstructor {
|
||||||
|
// constructor(uint256 a, uint256 b) public{}
|
||||||
|
// }
|
||||||
|
func TestConstructor(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
json := `[{ "inputs": [{"internalType": "uint256","name": "a","type": "uint256" },{ "internalType": "uint256","name": "b","type": "uint256"}],"stateMutability": "nonpayable","type": "constructor"}]`
|
||||||
|
method := NewMethod("", "", Constructor, "nonpayable", false, false, []Argument{{"a", Uint256, false}, {"b", Uint256, false}}, nil)
|
||||||
|
// Test from JSON
|
||||||
|
abi, err := JSON(strings.NewReader(json))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(abi.Constructor, method) {
|
||||||
|
t.Error("Missing expected constructor")
|
||||||
|
}
|
||||||
|
// Test pack/unpack
|
||||||
|
packed, err := abi.Pack("", big.NewInt(1), big.NewInt(2))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
t.FailNow()
|
}
|
||||||
|
unpacked, err := abi.Constructor.Inputs.Unpack(packed)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(unpacked[0], big.NewInt(1)) {
|
||||||
|
t.Error("Unable to pack/unpack from constructor")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(unpacked[1], big.NewInt(2)) {
|
||||||
|
t.Error("Unable to pack/unpack from constructor")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTestNumbers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := abi.Pack("balance"); err != nil {
|
if _, err := abi.Pack("balance"); err != nil {
|
||||||
|
|
@ -137,73 +240,121 @@ func TestTestNumbers(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTestString(t *testing.T) {
|
|
||||||
abi, err := JSON(strings.NewReader(jsondata2))
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := abi.Pack("string", "hello world"); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTestBool(t *testing.T) {
|
|
||||||
abi, err := JSON(strings.NewReader(jsondata2))
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := abi.Pack("bool", true); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTestSlice(t *testing.T) {
|
|
||||||
abi, err := JSON(strings.NewReader(jsondata2))
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
slice := make([]uint64, 2)
|
|
||||||
if _, err := abi.Pack("uint64[2]", slice); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := abi.Pack("uint64[]", slice); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMethodSignature(t *testing.T) {
|
func TestMethodSignature(t *testing.T) {
|
||||||
String, _ := NewType("string")
|
t.Parallel()
|
||||||
m := Method{"foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
|
m := NewMethod("foo", "foo", Function, "", 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
idexp := crypto.Keccak256([]byte(exp))[:4]
|
idexp := crypto.Keccak256([]byte(exp))[:4]
|
||||||
if !bytes.Equal(m.Id(), idexp) {
|
if !bytes.Equal(m.ID, idexp) {
|
||||||
t.Errorf("expected ids to match %x != %x", m.Id(), idexp)
|
t.Errorf("expected ids to match %x != %x", m.ID, idexp)
|
||||||
}
|
}
|
||||||
|
|
||||||
uintt, _ := NewType("uint256")
|
m = NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", Uint256, false}}, nil)
|
||||||
m = Method{"foo", 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method with tuple arguments
|
||||||
|
s, _ := NewType("tuple", "", []ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "int256"},
|
||||||
|
{Name: "b", Type: "int256[]"},
|
||||||
|
{Name: "c", Type: "tuple[]", Components: []ArgumentMarshaling{
|
||||||
|
{Name: "x", Type: "int256"},
|
||||||
|
{Name: "y", Type: "int256"},
|
||||||
|
}},
|
||||||
|
{Name: "d", Type: "tuple[2]", Components: []ArgumentMarshaling{
|
||||||
|
{Name: "x", Type: "int256"},
|
||||||
|
{Name: "y", Type: "int256"},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
m = NewMethod("foo", "foo", Function, "", 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverloadedMethodSignature(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
json := `[{"constant":true,"inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"i","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"}],"name":"bar","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"},{"indexed":false,"name":"j","type":"uint256"}],"name":"bar","type":"event"}]`
|
||||||
|
abi, err := JSON(strings.NewReader(json))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
check := func(name string, expect string, method bool) {
|
||||||
|
if method {
|
||||||
|
if abi.Methods[name].Sig != expect {
|
||||||
|
t.Fatalf("The signature of overloaded method mismatch, want %s, have %s", expect, abi.Methods[name].Sig)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if abi.Events[name].Sig != expect {
|
||||||
|
t.Fatalf("The signature of overloaded event mismatch, want %s, have %s", expect, abi.Events[name].Sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check("foo", "foo(uint256,uint256)", true)
|
||||||
|
check("foo0", "foo(uint256)", true)
|
||||||
|
check("bar", "bar(uint256)", false)
|
||||||
|
check("bar0", "bar(uint256,uint256)", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
json := `[{ "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ],"name": "MyError", "type": "error"} ]`
|
||||||
|
abi, err := JSON(strings.NewReader(json))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
check := func(name string, expect string) {
|
||||||
|
if abi.Errors[name].Sig != expect {
|
||||||
|
t.Fatalf("The signature of overloaded method mismatch, want %s, have %s", expect, abi.Methods[name].Sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check("MyError", "MyError(uint256)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomErrorUnpackIntoInterface(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
errorName := "MyError"
|
||||||
|
json := fmt.Sprintf(`[{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"}],"name":"%s","type":"error"}]`, errorName)
|
||||||
|
abi, err := JSON(strings.NewReader(json))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
type MyError struct {
|
||||||
|
Sender common.Address
|
||||||
|
Balance *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
sender := testrand.Address()
|
||||||
|
balance := new(big.Int).SetBytes(testrand.Bytes(8))
|
||||||
|
encoded, err := abi.Errors[errorName].Inputs.Pack(sender, balance)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
result := MyError{}
|
||||||
|
err = abi.UnpackIntoInterface(&result, errorName, encoded)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if result.Sender != sender {
|
||||||
|
t.Errorf("expected %x got %x", sender, result.Sender)
|
||||||
|
}
|
||||||
|
if result.Balance.Cmp(balance) != 0 {
|
||||||
|
t.Errorf("expected %v got %v", balance, result.Balance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiPack(t *testing.T) {
|
func TestMultiPack(t *testing.T) {
|
||||||
abi, err := JSON(strings.NewReader(jsondata2))
|
t.Parallel()
|
||||||
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Fatal(err)
|
||||||
t.FailNow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sig := crypto.Keccak256([]byte("bar(uint32,uint16)"))[:4]
|
sig := crypto.Keccak256([]byte("bar(uint32,uint16)"))[:4]
|
||||||
|
|
@ -213,8 +364,7 @@ func TestMultiPack(t *testing.T) {
|
||||||
|
|
||||||
packed, err := abi.Pack("bar", uint32(10), uint16(11))
|
packed, err := abi.Pack("bar", uint32(10), uint16(11))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Fatal(err)
|
||||||
t.FailNow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(packed, sig) {
|
if !bytes.Equal(packed, sig) {
|
||||||
|
|
@ -227,11 +377,11 @@ func ExampleJSON() {
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
out, err := abi.Pack("isBar", common.HexToAddress("01"))
|
out, err := abi.Pack("isBar", common.HexToAddress("01"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%x\n", out)
|
fmt.Printf("%x\n", out)
|
||||||
|
|
@ -240,6 +390,7 @@ func ExampleJSON() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInputVariableInputLength(t *testing.T) {
|
func TestInputVariableInputLength(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[
|
const definition = `[
|
||||||
{ "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] },
|
{ "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] },
|
||||||
{ "type" : "function", "name" : "bytesOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] },
|
{ "type" : "function", "name" : "bytesOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] },
|
||||||
|
|
@ -368,15 +519,8 @@ func TestInputVariableInputLength(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
|
func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
|
||||||
const definition = `[
|
t.Parallel()
|
||||||
{ "type" : "function", "name" : "fixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
{ "type" : "function", "name" : "fixedArrBytes", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
|
|
||||||
{ "type" : "function", "name" : "mixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type": "uint256[2]" }, { "name" : "dynArr", "type": "uint256[]" } ] },
|
|
||||||
{ "type" : "function", "name" : "doubleFixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "fixedArr2", "type": "uint256[3]" } ] },
|
|
||||||
{ "type" : "function", "name" : "multipleMixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] }
|
|
||||||
]`
|
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
@ -550,7 +694,8 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultFunctionParsing(t *testing.T) {
|
func TestDefaultFunctionParsing(t *testing.T) {
|
||||||
const definition = `[{ "name" : "balance" }]`
|
t.Parallel()
|
||||||
|
const definition = `[{ "name" : "balance", "type" : "function" }]`
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -563,14 +708,15 @@ func TestDefaultFunctionParsing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBareEvents(t *testing.T) {
|
func TestBareEvents(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[
|
const definition = `[
|
||||||
{ "type" : "event", "name" : "balance" },
|
{ "type" : "event", "name" : "balance" },
|
||||||
{ "type" : "event", "name" : "anon", "anonymous" : true},
|
{ "type" : "event", "name" : "anon", "anonymous" : true},
|
||||||
{ "type" : "event", "name" : "args", "inputs" : [{ "indexed":false, "name":"arg0", "type":"uint256" }, { "indexed":true, "name":"arg1", "type":"address" }] }
|
{ "type" : "event", "name" : "args", "inputs" : [{ "indexed":false, "name":"arg0", "type":"uint256" }, { "indexed":true, "name":"arg1", "type":"address" }] },
|
||||||
|
{ "type" : "event", "name" : "tuple", "inputs" : [{ "indexed":false, "name":"t", "type":"tuple", "components":[{"name":"a", "type":"uint256"}] }, { "indexed":true, "name":"arg1", "type":"address" }] }
|
||||||
]`
|
]`
|
||||||
|
|
||||||
arg0, _ := NewType("uint256")
|
tuple, _ := NewType("tuple", "", []ArgumentMarshaling{{Name: "a", Type: "uint256"}})
|
||||||
arg1, _ := NewType("address")
|
|
||||||
|
|
||||||
expectedEvents := map[string]struct {
|
expectedEvents := map[string]struct {
|
||||||
Anonymous bool
|
Anonymous bool
|
||||||
|
|
@ -579,8 +725,12 @@ func TestBareEvents(t *testing.T) {
|
||||||
"balance": {false, nil},
|
"balance": {false, nil},
|
||||||
"anon": {true, nil},
|
"anon": {true, nil},
|
||||||
"args": {false, []Argument{
|
"args": {false, []Argument{
|
||||||
{Name: "arg0", Type: arg0, Indexed: false},
|
{Name: "arg0", Type: Uint256, Indexed: false},
|
||||||
{Name: "arg1", Type: arg1, Indexed: true},
|
{Name: "arg1", Type: Address, Indexed: true},
|
||||||
|
}},
|
||||||
|
"tuple": {false, []Argument{
|
||||||
|
{Name: "t", Type: tuple, Indexed: false},
|
||||||
|
{Name: "arg1", Type: Address, Indexed: true},
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -623,18 +773,19 @@ func TestBareEvents(t *testing.T) {
|
||||||
// TestUnpackEvent is based on this contract:
|
// TestUnpackEvent is based on this contract:
|
||||||
//
|
//
|
||||||
// contract T {
|
// contract T {
|
||||||
// event received(address sender, uint amount, bytes memo);
|
// event received(address sender, uint amount, bytes memo);
|
||||||
// event receivedAddr(address sender);
|
// event receivedAddr(address sender);
|
||||||
// function receive(bytes memo) external payable {
|
// function receive(bytes memo) external payable {
|
||||||
// received(msg.sender, msg.value, memo);
|
// received(msg.sender, msg.value, memo);
|
||||||
// receivedAddr(msg.sender);
|
// receivedAddr(msg.sender);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt:
|
// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt:
|
||||||
//
|
//
|
||||||
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
||||||
func TestUnpackEvent(t *testing.T) {
|
func TestUnpackEvent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
||||||
abi, err := JSON(strings.NewReader(abiJSON))
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -651,72 +802,420 @@ func TestUnpackEvent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReceivedEvent struct {
|
type ReceivedEvent struct {
|
||||||
Address common.Address
|
Sender common.Address
|
||||||
Amount *big.Int
|
Amount *big.Int
|
||||||
Memo []byte
|
Memo []byte
|
||||||
}
|
}
|
||||||
var ev ReceivedEvent
|
var ev ReceivedEvent
|
||||||
|
|
||||||
err = abi.Unpack(&ev, "received", data)
|
err = abi.UnpackIntoInterface(&ev, "received", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
} else {
|
|
||||||
t.Logf("len(data): %d; received event: %+v", len(data), ev)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReceivedAddrEvent struct {
|
type ReceivedAddrEvent struct {
|
||||||
Address common.Address
|
Sender common.Address
|
||||||
}
|
}
|
||||||
var receivedAddrEv ReceivedAddrEvent
|
var receivedAddrEv ReceivedAddrEvent
|
||||||
err = abi.Unpack(&receivedAddrEv, "receivedAddr", data)
|
err = abi.UnpackIntoInterface(&receivedAddrEv, "receivedAddr", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
} else {
|
}
|
||||||
t.Logf("len(data): %d; received event: %+v", len(data), receivedAddrEv)
|
}
|
||||||
|
|
||||||
|
func TestUnpackEventIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
||||||
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158`
|
||||||
|
data, err := hex.DecodeString(hexdata)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(data)%32 == 0 {
|
||||||
|
t.Errorf("len(data) is %d, want a non-multiple of 32", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedMap := map[string]interface{}{}
|
||||||
|
expectedReceivedMap := map[string]interface{}{
|
||||||
|
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
|
||||||
|
"amount": big.NewInt(1),
|
||||||
|
"memo": []byte{88},
|
||||||
|
}
|
||||||
|
if err := abi.UnpackIntoMap(receivedMap, "received", data); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if len(receivedMap) != 3 {
|
||||||
|
t.Error("unpacked `received` map expected to have length 3")
|
||||||
|
}
|
||||||
|
if receivedMap["sender"] != expectedReceivedMap["sender"] {
|
||||||
|
t.Error("unpacked `received` map does not match expected map")
|
||||||
|
}
|
||||||
|
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
|
||||||
|
t.Error("unpacked `received` map does not match expected map")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
|
||||||
|
t.Error("unpacked `received` map does not match expected map")
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedAddrMap := map[string]interface{}{}
|
||||||
|
if err = abi.UnpackIntoMap(receivedAddrMap, "receivedAddr", data); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if len(receivedAddrMap) != 1 {
|
||||||
|
t.Error("unpacked `receivedAddr` map expected to have length 1")
|
||||||
|
}
|
||||||
|
if receivedAddrMap["sender"] != expectedReceivedMap["sender"] {
|
||||||
|
t.Error("unpacked `receivedAddr` map does not match expected map")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpackMethodIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
|
||||||
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
const hexdata = `00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000015800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000158000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001580000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000015800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000158`
|
||||||
|
data, err := hex.DecodeString(hexdata)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(data)%32 != 0 {
|
||||||
|
t.Errorf("len(data) is %d, want a multiple of 32", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests a method with no outputs
|
||||||
|
receiveMap := map[string]interface{}{}
|
||||||
|
if err = abi.UnpackIntoMap(receiveMap, "receive", data); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if len(receiveMap) > 0 {
|
||||||
|
t.Error("unpacked `receive` map expected to have length 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests a method with only outputs
|
||||||
|
sendMap := map[string]interface{}{}
|
||||||
|
if err = abi.UnpackIntoMap(sendMap, "send", data); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if len(sendMap) != 1 {
|
||||||
|
t.Error("unpacked `send` map expected to have length 1")
|
||||||
|
}
|
||||||
|
if sendMap["amount"].(*big.Int).Cmp(big.NewInt(1)) != 0 {
|
||||||
|
t.Error("unpacked `send` map expected `amount` value of 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests a method with outputs and inputs
|
||||||
|
getMap := map[string]interface{}{}
|
||||||
|
if err = abi.UnpackIntoMap(getMap, "get", data); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if len(getMap) != 1 {
|
||||||
|
t.Error("unpacked `get` map expected to have length 1")
|
||||||
|
}
|
||||||
|
expectedBytes := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 0}
|
||||||
|
if !bytes.Equal(getMap["hash"].([]byte), expectedBytes) {
|
||||||
|
t.Errorf("unpacked `get` map expected `hash` value of %v", expectedBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpackIntoMapNamingConflict(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
// Two methods have the same name
|
||||||
|
var abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"get","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
|
||||||
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var hexdata = `00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158`
|
||||||
|
data, err := hex.DecodeString(hexdata)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(data)%32 == 0 {
|
||||||
|
t.Errorf("len(data) is %d, want a non-multiple of 32", len(data))
|
||||||
|
}
|
||||||
|
getMap := map[string]interface{}{}
|
||||||
|
if err = abi.UnpackIntoMap(getMap, "get", data); err == nil {
|
||||||
|
t.Error("naming conflict between two methods; error expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two events have the same name
|
||||||
|
abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"received","type":"event"}]`
|
||||||
|
abi, err = JSON(strings.NewReader(abiJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158`
|
||||||
|
data, err = hex.DecodeString(hexdata)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(data)%32 == 0 {
|
||||||
|
t.Errorf("len(data) is %d, want a non-multiple of 32", len(data))
|
||||||
|
}
|
||||||
|
receivedMap := map[string]interface{}{}
|
||||||
|
if err = abi.UnpackIntoMap(receivedMap, "received", data); err != nil {
|
||||||
|
t.Error("naming conflict between two events; no error expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method and event have the same name
|
||||||
|
abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"received","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
||||||
|
abi, err = JSON(strings.NewReader(abiJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(data)%32 == 0 {
|
||||||
|
t.Errorf("len(data) is %d, want a non-multiple of 32", len(data))
|
||||||
|
}
|
||||||
|
if err = abi.UnpackIntoMap(receivedMap, "received", data); err == nil {
|
||||||
|
t.Error("naming conflict between an event and a method; error expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conflict is case sensitive
|
||||||
|
abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"received","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"Received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
||||||
|
abi, err = JSON(strings.NewReader(abiJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(data)%32 == 0 {
|
||||||
|
t.Errorf("len(data) is %d, want a non-multiple of 32", len(data))
|
||||||
|
}
|
||||||
|
expectedReceivedMap := map[string]interface{}{
|
||||||
|
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
|
||||||
|
"amount": big.NewInt(1),
|
||||||
|
"memo": []byte{88},
|
||||||
|
}
|
||||||
|
if err = abi.UnpackIntoMap(receivedMap, "Received", data); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if len(receivedMap) != 3 {
|
||||||
|
t.Error("unpacked `received` map expected to have length 3")
|
||||||
|
}
|
||||||
|
if receivedMap["sender"] != expectedReceivedMap["sender"] {
|
||||||
|
t.Error("unpacked `received` map does not match expected map")
|
||||||
|
}
|
||||||
|
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
|
||||||
|
t.Error("unpacked `received` map does not match expected map")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
|
||||||
|
t.Error("unpacked `received` map does not match expected map")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestABI_MethodById(t *testing.T) {
|
func TestABI_MethodById(t *testing.T) {
|
||||||
const abiJSON = `[
|
t.Parallel()
|
||||||
{"type":"function","name":"receive","constant":false,"inputs":[{"name":"memo","type":"bytes"}],"outputs":[],"payable":true,"stateMutability":"payable"},
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
{"type":"event","name":"received","anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}]},
|
|
||||||
{"type":"function","name":"fixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"}]},
|
|
||||||
{"type":"function","name":"fixedArrBytes","constant":true,"inputs":[{"name":"str","type":"bytes"},{"name":"fixedArr","type":"uint256[2]"}]},
|
|
||||||
{"type":"function","name":"mixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"}]},
|
|
||||||
{"type":"function","name":"doubleFixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"fixedArr2","type":"uint256[3]"}]},
|
|
||||||
{"type":"function","name":"multipleMixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"},{"name":"fixedArr2","type":"uint256[3]"}]},
|
|
||||||
{"type":"function","name":"balance","constant":true},
|
|
||||||
{"type":"function","name":"send","constant":false,"inputs":[{"name":"amount","type":"uint256"}]},
|
|
||||||
{"type":"function","name":"test","constant":false,"inputs":[{"name":"number","type":"uint32"}]},
|
|
||||||
{"type":"function","name":"string","constant":false,"inputs":[{"name":"inputs","type":"string"}]},
|
|
||||||
{"type":"function","name":"bool","constant":false,"inputs":[{"name":"inputs","type":"bool"}]},
|
|
||||||
{"type":"function","name":"address","constant":false,"inputs":[{"name":"inputs","type":"address"}]},
|
|
||||||
{"type":"function","name":"uint64[2]","constant":false,"inputs":[{"name":"inputs","type":"uint64[2]"}]},
|
|
||||||
{"type":"function","name":"uint64[]","constant":false,"inputs":[{"name":"inputs","type":"uint64[]"}]},
|
|
||||||
{"type":"function","name":"foo","constant":false,"inputs":[{"name":"inputs","type":"uint32"}]},
|
|
||||||
{"type":"function","name":"bar","constant":false,"inputs":[{"name":"inputs","type":"uint32"},{"name":"string","type":"uint16"}]},
|
|
||||||
{"type":"function","name":"_slice","constant":false,"inputs":[{"name":"inputs","type":"uint32[2]"}]},
|
|
||||||
{"type":"function","name":"__slice256","constant":false,"inputs":[{"name":"inputs","type":"uint256[2]"}]},
|
|
||||||
{"type":"function","name":"sliceAddress","constant":false,"inputs":[{"name":"inputs","type":"address[]"}]},
|
|
||||||
{"type":"function","name":"sliceMultiAddress","constant":false,"inputs":[{"name":"a","type":"address[]"},{"name":"b","type":"address[]"}]}
|
|
||||||
]
|
|
||||||
`
|
|
||||||
abi, err := JSON(strings.NewReader(abiJSON))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for name, m := range abi.Methods {
|
for name, m := range abi.Methods {
|
||||||
a := fmt.Sprintf("%v", m)
|
a := fmt.Sprintf("%v", m)
|
||||||
m2, err := abi.MethodById(m.Id())
|
m2, err := abi.MethodById(m.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to look up ABI method: %v", err)
|
t.Fatalf("Failed to look up ABI method: %v", err)
|
||||||
}
|
}
|
||||||
b := fmt.Sprintf("%v", m2)
|
b := fmt.Sprintf("%v", m2)
|
||||||
if a != b {
|
if a != b {
|
||||||
t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, hexutil.Encode(m.Id()))
|
t.Errorf("Method %v (id %x) not 'findable' by id in ABI", name, m.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// test unsuccessful lookups
|
||||||
|
if _, err = abi.MethodById(crypto.Keccak256()); err == nil {
|
||||||
|
t.Error("Expected error: no method with this id")
|
||||||
|
}
|
||||||
|
// Also test empty
|
||||||
|
if _, err := abi.MethodById([]byte{0x00}); err == nil {
|
||||||
|
t.Errorf("Expected error, too short to decode data")
|
||||||
|
}
|
||||||
|
if _, err := abi.MethodById([]byte{}); err == nil {
|
||||||
|
t.Errorf("Expected error, too short to decode data")
|
||||||
|
}
|
||||||
|
if _, err := abi.MethodById(nil); err == nil {
|
||||||
|
t.Errorf("Expected error, nil is short to decode data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestABI_EventById(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
json string
|
||||||
|
event string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
json: `[
|
||||||
|
{"type":"event","name":"received","anonymous":false,"inputs":[
|
||||||
|
{"indexed":false,"name":"sender","type":"address"},
|
||||||
|
{"indexed":false,"name":"amount","type":"uint256"},
|
||||||
|
{"indexed":false,"name":"memo","type":"bytes"}
|
||||||
|
]
|
||||||
|
}]`,
|
||||||
|
event: "received(address,uint256,bytes)",
|
||||||
|
}, {
|
||||||
|
name: "",
|
||||||
|
json: `[
|
||||||
|
{ "constant": true, "inputs": [], "name": "name", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" },
|
||||||
|
{ "constant": false, "inputs": [ { "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "approve", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" },
|
||||||
|
{ "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" },
|
||||||
|
{ "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" },
|
||||||
|
{ "constant": true, "inputs": [], "name": "decimals", "outputs": [ { "name": "", "type": "uint8" } ], "payable": false, "stateMutability": "view", "type": "function" },
|
||||||
|
{ "constant": true, "inputs": [ { "name": "_owner", "type": "address" } ], "name": "balanceOf", "outputs": [ { "name": "balance", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" },
|
||||||
|
{ "constant": true, "inputs": [], "name": "symbol", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" },
|
||||||
|
{ "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transfer", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" },
|
||||||
|
{ "constant": true, "inputs": [ { "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" } ], "name": "allowance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" },
|
||||||
|
{ "payable": true, "stateMutability": "payable", "type": "fallback" },
|
||||||
|
{ "anonymous": false, "inputs": [ { "indexed": true, "name": "owner", "type": "address" }, { "indexed": true, "name": "spender", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Approval", "type": "event" },
|
||||||
|
{ "anonymous": false, "inputs": [ { "indexed": true, "name": "from", "type": "address" }, { "indexed": true, "name": "to", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" }
|
||||||
|
]`,
|
||||||
|
event: "Transfer(address,address,uint256)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testnum, test := range tests {
|
||||||
|
abi, err := JSON(strings.NewReader(test.json))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
topic := test.event
|
||||||
|
topicID := crypto.Keccak256Hash([]byte(topic))
|
||||||
|
|
||||||
|
event, err := abi.EventByID(topicID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to look up ABI method: %v, test #%d", err, testnum)
|
||||||
|
}
|
||||||
|
if event == nil {
|
||||||
|
t.Errorf("We should find a event for topic %s, test #%d", topicID.Hex(), testnum)
|
||||||
|
} else if event.ID != topicID {
|
||||||
|
t.Errorf("Event id %s does not match topic %s, test #%d", event.ID.Hex(), topicID.Hex(), testnum)
|
||||||
|
}
|
||||||
|
|
||||||
|
unknowntopicID := crypto.Keccak256Hash([]byte("unknownEvent"))
|
||||||
|
unknownEvent, err := abi.EventByID(unknowntopicID)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("EventByID should return an error if a topic is not found, test #%d", testnum)
|
||||||
|
}
|
||||||
|
if unknownEvent != nil {
|
||||||
|
t.Errorf("We should not find any event for topic %s, test #%d", unknowntopicID.Hex(), testnum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestABI_ErrorByID(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
abi, err := JSON(strings.NewReader(`[
|
||||||
|
{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"MyError1","type":"error"},
|
||||||
|
{"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"string","name":"b","type":"string"},{"internalType":"address","name":"c","type":"address"}],"internalType":"struct MyError.MyStruct","name":"x","type":"tuple"},{"internalType":"address","name":"y","type":"address"},{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"string","name":"b","type":"string"},{"internalType":"address","name":"c","type":"address"}],"internalType":"struct MyError.MyStruct","name":"z","type":"tuple"}],"name":"MyError2","type":"error"},
|
||||||
|
{"inputs":[{"internalType":"uint256[]","name":"x","type":"uint256[]"}],"name":"MyError3","type":"error"}
|
||||||
|
]`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for name, m := range abi.Errors {
|
||||||
|
a := fmt.Sprintf("%v", &m)
|
||||||
|
var id [4]byte
|
||||||
|
copy(id[:], m.ID[:4])
|
||||||
|
m2, err := abi.ErrorByID(id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to look up ABI error: %v", err)
|
||||||
|
}
|
||||||
|
b := fmt.Sprintf("%v", m2)
|
||||||
|
if a != b {
|
||||||
|
t.Errorf("Error %v (id %x) not 'findable' by id in ABI", name, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// test unsuccessful lookups
|
||||||
|
if _, err = abi.ErrorByID([4]byte{}); err == nil {
|
||||||
|
t.Error("Expected error: no error with this id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDoubleDuplicateMethodNames checks that if transfer0 already exists, there won't be a name
|
||||||
|
// conflict and that the second transfer method will be renamed transfer1.
|
||||||
|
func TestDoubleDuplicateMethodNames(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
abiJSON := `[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"}],"name":"transfer0","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"customFallback","type":"string"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]`
|
||||||
|
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, ok := contractAbi.Methods["transfer"]; !ok {
|
||||||
|
t.Fatalf("Could not find original method")
|
||||||
|
}
|
||||||
|
if _, ok := contractAbi.Methods["transfer0"]; !ok {
|
||||||
|
t.Fatalf("Could not find duplicate method")
|
||||||
|
}
|
||||||
|
if _, ok := contractAbi.Methods["transfer1"]; !ok {
|
||||||
|
t.Fatalf("Could not find duplicate method")
|
||||||
|
}
|
||||||
|
if _, ok := contractAbi.Methods["transfer2"]; ok {
|
||||||
|
t.Fatalf("Should not have found extra method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDoubleDuplicateEventNames checks that if send0 already exists, there won't be a name
|
||||||
|
// conflict and that the second send event will be renamed send1.
|
||||||
|
// The test runs the abi of the following contract.
|
||||||
|
//
|
||||||
|
// contract DuplicateEvent {
|
||||||
|
// event send(uint256 a);
|
||||||
|
// event send0();
|
||||||
|
// event send();
|
||||||
|
// }
|
||||||
|
func TestDoubleDuplicateEventNames(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
abiJSON := `[{"anonymous": false,"inputs": [{"indexed": false,"internalType": "uint256","name": "a","type": "uint256"}],"name": "send","type": "event"},{"anonymous": false,"inputs": [],"name": "send0","type": "event"},{ "anonymous": false, "inputs": [],"name": "send","type": "event"}]`
|
||||||
|
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, ok := contractAbi.Events["send"]; !ok {
|
||||||
|
t.Fatalf("Could not find original event")
|
||||||
|
}
|
||||||
|
if _, ok := contractAbi.Events["send0"]; !ok {
|
||||||
|
t.Fatalf("Could not find duplicate event")
|
||||||
|
}
|
||||||
|
if _, ok := contractAbi.Events["send1"]; !ok {
|
||||||
|
t.Fatalf("Could not find duplicate event")
|
||||||
|
}
|
||||||
|
if _, ok := contractAbi.Events["send2"]; ok {
|
||||||
|
t.Fatalf("Should not have found extra event")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUnnamedEventParam checks that an event with unnamed parameters is
|
||||||
|
// correctly handled.
|
||||||
|
// The test runs the abi of the following contract.
|
||||||
|
//
|
||||||
|
// contract TestEvent {
|
||||||
|
// event send(uint256, uint256);
|
||||||
|
// }
|
||||||
|
func TestUnnamedEventParam(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
abiJSON := `[{ "anonymous": false, "inputs": [{ "indexed": false,"internalType": "uint256", "name": "","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "","type": "uint256"}],"name": "send","type": "event"}]`
|
||||||
|
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
event, ok := contractAbi.Events["send"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Could not find event")
|
||||||
|
}
|
||||||
|
if event.Inputs[0].Name != "arg0" {
|
||||||
|
t.Fatalf("Could not find input")
|
||||||
|
}
|
||||||
|
if event.Inputs[1].Name != "arg1" {
|
||||||
|
t.Fatalf("Could not find input")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackRevert(t *testing.T) {
|
func TestUnpackRevert(t *testing.T) {
|
||||||
|
|
@ -730,9 +1229,12 @@ func TestUnpackRevert(t *testing.T) {
|
||||||
{"", "", errors.New("invalid data for unpacking")},
|
{"", "", errors.New("invalid data for unpacking")},
|
||||||
{"08c379a1", "", errors.New("invalid data for unpacking")},
|
{"08c379a1", "", errors.New("invalid data for unpacking")},
|
||||||
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
|
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
|
||||||
|
{"4e487b710000000000000000000000000000000000000000000000000000000000000000", "generic panic", nil},
|
||||||
|
{"4e487b7100000000000000000000000000000000000000000000000000000000000000ff", "unknown panic code: 0xff", nil},
|
||||||
}
|
}
|
||||||
for index, c := range cases {
|
for index, c := range cases {
|
||||||
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
|
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
got, err := UnpackRevert(common.Hex2Bytes(c.input))
|
got, err := UnpackRevert(common.Hex2Bytes(c.input))
|
||||||
if c.expectErr != nil {
|
if c.expectErr != nil {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -749,3 +1251,10 @@ func TestUnpackRevert(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInternalContractType(t *testing.T) {
|
||||||
|
jsonData := `[{"inputs":[{"components":[{"internalType":"uint256","name":"dailyLimit","type":"uint256"},{"internalType":"uint256","name":"txLimit","type":"uint256"},{"internalType":"uint256","name":"accountDailyLimit","type":"uint256"},{"internalType":"uint256","name":"minAmount","type":"uint256"},{"internalType":"bool","name":"onlyWhitelisted","type":"bool"}],"internalType":"struct IMessagePassingBridge.BridgeLimits","name":"bridgeLimits","type":"tuple"},{"components":[{"internalType":"uint256","name":"lastTransferReset","type":"uint256"},{"internalType":"uint256","name":"bridged24Hours","type":"uint256"}],"internalType":"struct IMessagePassingBridge.AccountLimit","name":"accountDailyLimit","type":"tuple"},{"components":[{"internalType":"uint256","name":"lastTransferReset","type":"uint256"},{"internalType":"uint256","name":"bridged24Hours","type":"uint256"}],"internalType":"struct IMessagePassingBridge.BridgeDailyLimit","name":"bridgeDailyLimit","type":"tuple"},{"internalType":"contract INameService","name":"nameService","type":"INameService"},{"internalType":"bool","name":"isClosed","type":"bool"},{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"canBridge","outputs":[{"internalType":"bool","name":"isWithinLimit","type":"bool"},{"internalType":"string","name":"error","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"normalizeFrom18ToTokenDecimals","outputs":[{"internalType":"uint256","name":"normalized","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"normalizeFromTokenTo18Decimals","outputs":[{"internalType":"uint256","name":"normalized","type":"uint256"}],"stateMutability":"pure","type":"function"}]`
|
||||||
|
if _, err := JSON(strings.NewReader(jsonData)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
179
accounts/abi/abifuzzer_test.go
Normal file
179
accounts/abi/abifuzzer_test.go
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
// Copyright 2020 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
fuzz "github.com/google/gofuzz"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestReplicate can be used to replicate crashers from the fuzzing tests.
|
||||||
|
// Just replace testString with the data in .quoted
|
||||||
|
func TestReplicate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
//t.Skip("Test only useful for reproducing issues")
|
||||||
|
fuzzAbi([]byte("\x20\x20\x20\x20\x20\x20\x20\x20\x80\x00\x00\x00\x20\x20\x20\x20\x00"))
|
||||||
|
//fuzzAbi([]byte("asdfasdfkadsf;lasdf;lasd;lfk"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuzzABI is the main entrypoint for fuzzing
|
||||||
|
func FuzzABI(f *testing.F) {
|
||||||
|
f.Fuzz(func(t *testing.T, data []byte) {
|
||||||
|
fuzzAbi(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"}
|
||||||
|
stateMut = []string{"pure", "view", "payable"}
|
||||||
|
pays = []string{"true", "false"}
|
||||||
|
vNames = []string{"a", "b", "c", "d", "e", "f", "g"}
|
||||||
|
varNames = append(vNames, names...)
|
||||||
|
varTypes = []string{"bool", "address", "bytes", "string",
|
||||||
|
"uint8", "int8", "uint8", "int8", "uint16", "int16",
|
||||||
|
"uint24", "int24", "uint32", "int32", "uint40", "int40", "uint48", "int48", "uint56", "int56",
|
||||||
|
"uint64", "int64", "uint72", "int72", "uint80", "int80", "uint88", "int88", "uint96", "int96",
|
||||||
|
"uint104", "int104", "uint112", "int112", "uint120", "int120", "uint128", "int128", "uint136", "int136",
|
||||||
|
"uint144", "int144", "uint152", "int152", "uint160", "int160", "uint168", "int168", "uint176", "int176",
|
||||||
|
"uint184", "int184", "uint192", "int192", "uint200", "int200", "uint208", "int208", "uint216", "int216",
|
||||||
|
"uint224", "int224", "uint232", "int232", "uint240", "int240", "uint248", "int248", "uint256", "int256",
|
||||||
|
"bytes1", "bytes2", "bytes3", "bytes4", "bytes5", "bytes6", "bytes7", "bytes8", "bytes9", "bytes10", "bytes11",
|
||||||
|
"bytes12", "bytes13", "bytes14", "bytes15", "bytes16", "bytes17", "bytes18", "bytes19", "bytes20", "bytes21",
|
||||||
|
"bytes22", "bytes23", "bytes24", "bytes25", "bytes26", "bytes27", "bytes28", "bytes29", "bytes30", "bytes31",
|
||||||
|
"bytes32", "bytes"}
|
||||||
|
)
|
||||||
|
|
||||||
|
func unpackPack(abi ABI, method string, input []byte) ([]interface{}, bool) {
|
||||||
|
if out, err := abi.Unpack(method, input); err == nil {
|
||||||
|
_, err := abi.Pack(method, out...)
|
||||||
|
if err != nil {
|
||||||
|
// We have some false positives as we can unpack these type successfully, but not pack them
|
||||||
|
if err.Error() == "abi: cannot use []uint8 as type [0]int8 as argument" ||
|
||||||
|
err.Error() == "abi: cannot use uint8 as type int8 as argument" {
|
||||||
|
return out, false
|
||||||
|
}
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return out, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func packUnpack(abi ABI, method string, input *[]interface{}) bool {
|
||||||
|
if packed, err := abi.Pack(method, input); err == nil {
|
||||||
|
outptr := reflect.New(reflect.TypeOf(input))
|
||||||
|
err := abi.UnpackIntoInterface(outptr.Interface(), method, packed)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
out := outptr.Elem().Interface()
|
||||||
|
if !reflect.DeepEqual(input, out) {
|
||||||
|
panic(fmt.Sprintf("unpackPack is not equal, \ninput : %x\noutput: %x", input, out))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type arg struct {
|
||||||
|
name string
|
||||||
|
typ string
|
||||||
|
}
|
||||||
|
|
||||||
|
func createABI(name string, stateMutability, payable *string, inputs []arg) (ABI, error) {
|
||||||
|
sig := fmt.Sprintf(`[{ "type" : "function", "name" : "%v" `, name)
|
||||||
|
if stateMutability != nil {
|
||||||
|
sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability)
|
||||||
|
}
|
||||||
|
if payable != nil {
|
||||||
|
sig += fmt.Sprintf(`, "payable": %v `, *payable)
|
||||||
|
}
|
||||||
|
if len(inputs) > 0 {
|
||||||
|
sig += `, "inputs" : [ {`
|
||||||
|
for i, inp := range inputs {
|
||||||
|
sig += fmt.Sprintf(`"name" : "%v", "type" : "%v" `, inp.name, inp.typ)
|
||||||
|
if i+1 < len(inputs) {
|
||||||
|
sig += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sig += "} ]"
|
||||||
|
sig += `, "outputs" : [ {`
|
||||||
|
for i, inp := range inputs {
|
||||||
|
sig += fmt.Sprintf(`"name" : "%v", "type" : "%v" `, inp.name, inp.typ)
|
||||||
|
if i+1 < len(inputs) {
|
||||||
|
sig += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sig += "} ]"
|
||||||
|
}
|
||||||
|
sig += `}]`
|
||||||
|
//fmt.Printf("sig: %s\n", sig)
|
||||||
|
return JSON(strings.NewReader(sig))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fuzzAbi(input []byte) {
|
||||||
|
var (
|
||||||
|
fuzzer = fuzz.NewFromGoFuzz(input)
|
||||||
|
name = oneOf(fuzzer, names)
|
||||||
|
stateM = oneOfOrNil(fuzzer, stateMut)
|
||||||
|
payable = oneOfOrNil(fuzzer, pays)
|
||||||
|
arguments []arg
|
||||||
|
)
|
||||||
|
for i := 0; i < upTo(fuzzer, 10); i++ {
|
||||||
|
argName := oneOf(fuzzer, varNames)
|
||||||
|
argTyp := oneOf(fuzzer, varTypes)
|
||||||
|
switch upTo(fuzzer, 10) {
|
||||||
|
case 0: // 10% chance to make it a slice
|
||||||
|
argTyp += "[]"
|
||||||
|
case 1: // 10% chance to make it an array
|
||||||
|
argTyp += fmt.Sprintf("[%d]", 1+upTo(fuzzer, 30))
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
arguments = append(arguments, arg{name: argName, typ: argTyp})
|
||||||
|
}
|
||||||
|
abi, err := createABI(name, stateM, payable, arguments)
|
||||||
|
if err != nil {
|
||||||
|
//fmt.Printf("err: %v\n", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
structs, _ := unpackPack(abi, name, input)
|
||||||
|
_ = packUnpack(abi, name, &structs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func upTo(fuzzer *fuzz.Fuzzer, max int) int {
|
||||||
|
var i int
|
||||||
|
fuzzer.Fuzz(&i)
|
||||||
|
if i < 0 {
|
||||||
|
return (-1 - i) % max
|
||||||
|
}
|
||||||
|
return i % max
|
||||||
|
}
|
||||||
|
|
||||||
|
func oneOf(fuzzer *fuzz.Fuzzer, options []string) string {
|
||||||
|
return options[upTo(fuzzer, len(options))]
|
||||||
|
}
|
||||||
|
|
||||||
|
func oneOfOrNil(fuzzer *fuzz.Fuzzer, options []string) *string {
|
||||||
|
if i := upTo(fuzzer, len(options)+1); i < len(options) {
|
||||||
|
return &options[i]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -34,41 +34,33 @@ type Argument struct {
|
||||||
|
|
||||||
type Arguments []Argument
|
type Arguments []Argument
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler interface
|
type ArgumentMarshaling struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
InternalType string
|
||||||
|
Components []ArgumentMarshaling
|
||||||
|
Indexed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler interface.
|
||||||
func (argument *Argument) UnmarshalJSON(data []byte) error {
|
func (argument *Argument) UnmarshalJSON(data []byte) error {
|
||||||
var extarg struct {
|
var arg ArgumentMarshaling
|
||||||
Name string
|
err := json.Unmarshal(data, &arg)
|
||||||
Type string
|
|
||||||
Indexed bool
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(data, &extarg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("argument json err: %v", err)
|
return fmt.Errorf("argument json err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
argument.Type, err = NewType(extarg.Type)
|
argument.Type, err = NewType(arg.Type, arg.InternalType, arg.Components)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
argument.Name = extarg.Name
|
argument.Name = arg.Name
|
||||||
argument.Indexed = extarg.Indexed
|
argument.Indexed = arg.Indexed
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LengthNonIndexed returns the number of arguments when not counting 'indexed' ones. Only events
|
// NonIndexed returns the arguments with indexed arguments filtered out.
|
||||||
// can ever have 'indexed' arguments, it should always be false on arguments for method input/output
|
|
||||||
func (arguments Arguments) LengthNonIndexed() int {
|
|
||||||
out := 0
|
|
||||||
for _, arg := range arguments {
|
|
||||||
if !arg.Indexed {
|
|
||||||
out++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// NonIndexed returns the arguments with indexed arguments filtered out
|
|
||||||
func (arguments Arguments) NonIndexed() Arguments {
|
func (arguments Arguments) NonIndexed() Arguments {
|
||||||
var ret []Argument
|
var ret []Argument
|
||||||
for _, arg := range arguments {
|
for _, arg := range arguments {
|
||||||
|
|
@ -79,131 +71,126 @@ func (arguments Arguments) NonIndexed() Arguments {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]
|
// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[].
|
||||||
func (arguments Arguments) isTuple() bool {
|
func (arguments Arguments) isTuple() bool {
|
||||||
return len(arguments) > 1
|
return len(arguments) > 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unpack performs the operation hexdata -> Go format
|
// Unpack performs the operation hexdata -> Go format.
|
||||||
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
|
func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) {
|
||||||
|
|
||||||
// make sure the passed value is arguments pointer
|
|
||||||
if reflect.Ptr != reflect.ValueOf(v).Kind() {
|
|
||||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
|
||||||
}
|
|
||||||
marshalledValues, err := arguments.UnpackValues(data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if arguments.isTuple() {
|
|
||||||
return arguments.unpackTuple(v, marshalledValues)
|
|
||||||
}
|
|
||||||
return arguments.unpackAtomic(v, marshalledValues)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unpack2 performs the operation hexdata -> Go format.
|
|
||||||
func (arguments Arguments) Unpack2(data []byte) ([]interface{}, error) {
|
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
if len(arguments.NonIndexed()) != 0 {
|
if len(arguments.NonIndexed()) != 0 {
|
||||||
return nil, errors.New("abi: attempting to unmarshall an empty string while arguments are expected")
|
return nil, errors.New("abi: attempting to unmarshal an empty string while arguments are expected")
|
||||||
}
|
}
|
||||||
return make([]interface{}, 0), nil
|
return make([]interface{}, 0), nil
|
||||||
}
|
}
|
||||||
return arguments.UnpackValues(data)
|
return arguments.UnpackValues(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error {
|
// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value.
|
||||||
|
func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error {
|
||||||
var (
|
// Make sure map is not nil
|
||||||
value = reflect.ValueOf(v).Elem()
|
if v == nil {
|
||||||
typ = value.Type()
|
return errors.New("abi: cannot unpack into a nil map")
|
||||||
kind = value.Kind()
|
}
|
||||||
)
|
if len(data) == 0 {
|
||||||
|
if len(arguments.NonIndexed()) != 0 {
|
||||||
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
|
return errors.New("abi: attempting to unmarshal an empty string while arguments are expected")
|
||||||
|
}
|
||||||
|
return nil // Nothing to unmarshal, return
|
||||||
|
}
|
||||||
|
marshalledValues, err := arguments.UnpackValues(data)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// If the output interface is a struct, make sure names don't collide
|
|
||||||
if kind == reflect.Struct {
|
|
||||||
if err := requireUniqueStructFieldNames(arguments); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, arg := range arguments.NonIndexed() {
|
for i, arg := range arguments.NonIndexed() {
|
||||||
|
v[arg.Name] = marshalledValues[i]
|
||||||
reflectValue := reflect.ValueOf(marshalledValues[i])
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case reflect.Struct:
|
|
||||||
err := unpackStruct(value, reflectValue, arg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case reflect.Slice, reflect.Array:
|
|
||||||
if value.Len() < i {
|
|
||||||
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
|
|
||||||
}
|
|
||||||
v := value.Index(i)
|
|
||||||
if err := requireAssignable(v, reflectValue); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := set(v.Elem(), reflectValue, arg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", typ)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// unpackAtomic unpacks ( hexdata -> go ) a single value
|
// Copy performs the operation go format -> provided struct.
|
||||||
func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interface{}) error {
|
func (arguments Arguments) Copy(v interface{}, values []interface{}) error {
|
||||||
if len(marshalledValues) != 1 {
|
// make sure the passed value is arguments pointer
|
||||||
return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues))
|
if reflect.Ptr != reflect.ValueOf(v).Kind() {
|
||||||
|
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||||
}
|
}
|
||||||
elem := reflect.ValueOf(v).Elem()
|
if len(values) == 0 {
|
||||||
kind := elem.Kind()
|
if len(arguments.NonIndexed()) != 0 {
|
||||||
reflectValue := reflect.ValueOf(marshalledValues[0])
|
return errors.New("abi: attempting to copy no values while arguments are expected")
|
||||||
|
|
||||||
if kind == reflect.Struct {
|
|
||||||
//make sure names don't collide
|
|
||||||
if err := requireUniqueStructFieldNames(arguments); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
return nil // Nothing to copy, return
|
||||||
return unpackStruct(elem, reflectValue, arguments[0])
|
|
||||||
}
|
}
|
||||||
|
if arguments.isTuple() {
|
||||||
return set(elem, reflectValue, arguments.NonIndexed()[0])
|
return arguments.copyTuple(v, values)
|
||||||
|
}
|
||||||
|
return arguments.copyAtomic(v, values[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computes the full size of an array;
|
// copyAtomic copies ( hexdata -> go ) a single value
|
||||||
// i.e. counting nested arrays, which count towards size for unpacking.
|
func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{}) error {
|
||||||
func getArraySize(arr *Type) int {
|
dst := reflect.ValueOf(v).Elem()
|
||||||
size := arr.Size
|
src := reflect.ValueOf(marshalledValues)
|
||||||
// Arrays can be nested, with each element being the same size
|
|
||||||
arr = arr.Elem
|
if dst.Kind() == reflect.Struct {
|
||||||
for arr.T == ArrayTy {
|
return set(dst.Field(0), src)
|
||||||
// Keep multiplying by elem.Size while the elem is an array.
|
|
||||||
size *= arr.Size
|
|
||||||
arr = arr.Elem
|
|
||||||
}
|
}
|
||||||
// Now we have the full array size, including its children.
|
return set(dst, src)
|
||||||
return size
|
}
|
||||||
|
|
||||||
|
// copyTuple copies a batch of values from marshalledValues to v.
|
||||||
|
func (arguments Arguments) copyTuple(v interface{}, marshalledValues []interface{}) error {
|
||||||
|
value := reflect.ValueOf(v).Elem()
|
||||||
|
nonIndexedArgs := arguments.NonIndexed()
|
||||||
|
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
argNames := make([]string, len(nonIndexedArgs))
|
||||||
|
for i, arg := range nonIndexedArgs {
|
||||||
|
argNames[i] = arg.Name
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
abi2struct, err := mapArgNamesToStructFields(argNames, value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i, arg := range nonIndexedArgs {
|
||||||
|
field := value.FieldByName(abi2struct[arg.Name])
|
||||||
|
if !field.IsValid() {
|
||||||
|
return fmt.Errorf("abi: field %s can't be found in the given value", arg.Name)
|
||||||
|
}
|
||||||
|
if err := set(field, reflect.ValueOf(marshalledValues[i])); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
if value.Len() < len(marshalledValues) {
|
||||||
|
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
|
||||||
|
}
|
||||||
|
for i := range nonIndexedArgs {
|
||||||
|
if err := set(value.Index(i), reflect.ValueOf(marshalledValues[i])); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", value.Type())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification,
|
// UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification,
|
||||||
// without supplying a struct to unpack into. Instead, this method returns a list containing the
|
// without supplying a struct to unpack into. Instead, this method returns a list containing the
|
||||||
// values. An atomic argument will be a list with one element.
|
// values. An atomic argument will be a list with one element.
|
||||||
func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
|
func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
|
||||||
retval := make([]interface{}, 0, arguments.LengthNonIndexed())
|
nonIndexedArgs := arguments.NonIndexed()
|
||||||
|
retval := make([]interface{}, 0, len(nonIndexedArgs))
|
||||||
virtualArgs := 0
|
virtualArgs := 0
|
||||||
for index, arg := range arguments.NonIndexed() {
|
for index, arg := range nonIndexedArgs {
|
||||||
marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data)
|
marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data)
|
||||||
if arg.Type.T == ArrayTy {
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) {
|
||||||
// If we have a static array, like [3]uint256, these are coded as
|
// If we have a static array, like [3]uint256, these are coded as
|
||||||
// just like uint256,uint256,uint256.
|
// just like uint256,uint256,uint256.
|
||||||
// This means that we need to add two 'virtual' arguments when
|
// This means that we need to add two 'virtual' arguments when
|
||||||
|
|
@ -214,28 +201,29 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
|
||||||
//
|
//
|
||||||
// Calculate the full array size to get the correct offset for the next argument.
|
// Calculate the full array size to get the correct offset for the next argument.
|
||||||
// Decrement it by 1, as the normal index increment is still applied.
|
// Decrement it by 1, as the normal index increment is still applied.
|
||||||
virtualArgs += getArraySize(&arg.Type) - 1
|
virtualArgs += getTypeSize(arg.Type)/32 - 1
|
||||||
}
|
} else if arg.Type.T == TupleTy && !isDynamicType(arg.Type) {
|
||||||
if err != nil {
|
// If we have a static tuple, like (uint256, bool, uint256), these are
|
||||||
return nil, err
|
// coded as just like uint256,bool,uint256
|
||||||
|
virtualArgs += getTypeSize(arg.Type)/32 - 1
|
||||||
}
|
}
|
||||||
retval = append(retval, marshalledValue)
|
retval = append(retval, marshalledValue)
|
||||||
}
|
}
|
||||||
return retval, nil
|
return retval, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PackValues performs the operation Go format -> Hexdata
|
// PackValues performs the operation Go format -> Hexdata.
|
||||||
// It is the semantic opposite of UnpackValues
|
// It is the semantic opposite of UnpackValues.
|
||||||
func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) {
|
func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) {
|
||||||
return arguments.Pack(args...)
|
return arguments.Pack(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pack performs the operation Go format -> Hexdata
|
// Pack performs the operation Go format -> Hexdata.
|
||||||
func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
||||||
// Make sure arguments match up and pack them
|
// Make sure arguments match up and pack them
|
||||||
abiArgs := arguments
|
abiArgs := arguments
|
||||||
if len(args) != len(abiArgs) {
|
if len(args) != len(abiArgs) {
|
||||||
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs))
|
return nil, fmt.Errorf("argument count mismatch: got %d for %d", len(args), len(abiArgs))
|
||||||
}
|
}
|
||||||
// variable input is the output appended at the end of packed
|
// variable input is the output appended at the end of packed
|
||||||
// output. This is used for strings and bytes types input.
|
// output. This is used for strings and bytes types input.
|
||||||
|
|
@ -244,11 +232,7 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
||||||
// input offset is the bytes offset for packed output
|
// input offset is the bytes offset for packed output
|
||||||
inputOffset := 0
|
inputOffset := 0
|
||||||
for _, abiArg := range abiArgs {
|
for _, abiArg := range abiArgs {
|
||||||
if abiArg.Type.T == ArrayTy {
|
inputOffset += getTypeSize(abiArg.Type)
|
||||||
inputOffset += 32 * abiArg.Type.Size
|
|
||||||
} else {
|
|
||||||
inputOffset += 32
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var ret []byte
|
var ret []byte
|
||||||
for i, a := range args {
|
for i, a := range args {
|
||||||
|
|
@ -258,14 +242,13 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// check for a slice type (string, bytes, slice)
|
// check for dynamic types
|
||||||
if input.Type.requiresLengthPrefix() {
|
if isDynamicType(input.Type) {
|
||||||
// calculate the offset
|
|
||||||
offset := inputOffset + len(variableInput)
|
|
||||||
// set the offset
|
// set the offset
|
||||||
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
ret = append(ret, packNum(reflect.ValueOf(inputOffset))...)
|
||||||
// Append the packed output to the variable input. The variable input
|
// calculate next offset
|
||||||
// will be appended at the end of the input.
|
inputOffset += len(packed)
|
||||||
|
// append to variable input
|
||||||
variableInput = append(variableInput, packed...)
|
variableInput = append(variableInput, packed...)
|
||||||
} else {
|
} else {
|
||||||
// append the packed value to the input
|
// append the packed value to the input
|
||||||
|
|
@ -288,18 +271,3 @@ func ToCamelCase(input string) string {
|
||||||
}
|
}
|
||||||
return strings.Join(parts, "")
|
return strings.Join(parts, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// unpackStruct extracts each argument into its corresponding struct field
|
|
||||||
func unpackStruct(value, reflectValue reflect.Value, arg Argument) error {
|
|
||||||
name := ToCamelCase(arg.Name)
|
|
||||||
typ := value.Type()
|
|
||||||
for j := 0; j < typ.NumField(); j++ {
|
|
||||||
// TODO read tags: `abi:"fieldName"`
|
|
||||||
if typ.Field(j).Name == name {
|
|
||||||
if err := set(value.Field(j), reflectValue, arg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package bind
|
package bind
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -53,6 +54,29 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) {
|
||||||
return NewKeyedTransactor(key.PrivateKey), nil
|
return NewKeyedTransactor(key.PrivateKey), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewKeyStoreTransactor is a utility method to easily create a transaction signer from
|
||||||
|
// a decrypted key from a keystore.
|
||||||
|
//
|
||||||
|
// Deprecated: Use NewKeyStoreTransactorWithChainID instead.
|
||||||
|
func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
|
||||||
|
log.Warn("WARNING: NewKeyStoreTransactor has been deprecated in favour of NewTransactorWithChainID")
|
||||||
|
signer := types.HomesteadSigner{}
|
||||||
|
return &TransactOpts{
|
||||||
|
From: account.Address,
|
||||||
|
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||||||
|
if address != account.Address {
|
||||||
|
return nil, ErrNotAuthorized
|
||||||
|
}
|
||||||
|
signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tx.WithSignature(signer, signature)
|
||||||
|
},
|
||||||
|
Context: context.Background(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewKeyedTransactor is a utility method to easily create a transaction signer
|
// NewKeyedTransactor is a utility method to easily create a transaction signer
|
||||||
// from a single private key.
|
// from a single private key.
|
||||||
//
|
//
|
||||||
|
|
@ -73,6 +97,7 @@ func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts {
|
||||||
}
|
}
|
||||||
return tx.WithSignature(signer, signature)
|
return tx.WithSignature(signer, signature)
|
||||||
},
|
},
|
||||||
|
Context: context.Background(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,12 +116,12 @@ func NewTransactorWithChainID(keyin io.Reader, passphrase string, chainID *big.I
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from
|
// NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from
|
||||||
// an decrypted key from a keystore.
|
// a decrypted key from a keystore.
|
||||||
func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) {
|
func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) {
|
||||||
if chainID == nil {
|
if chainID == nil {
|
||||||
return nil, ErrNoChainID
|
return nil, ErrNoChainID
|
||||||
}
|
}
|
||||||
signer := types.NewEIP155Signer(chainID)
|
signer := types.LatestSignerForChainID(chainID)
|
||||||
return &TransactOpts{
|
return &TransactOpts{
|
||||||
From: account.Address,
|
From: account.Address,
|
||||||
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||||||
|
|
@ -109,17 +134,18 @@ func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accou
|
||||||
}
|
}
|
||||||
return tx.WithSignature(signer, signature)
|
return tx.WithSignature(signer, signature)
|
||||||
},
|
},
|
||||||
|
Context: context.Background(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKeyedTransactorWithChainID is a utility method to easily create a transaction signer
|
// NewKeyedTransactorWithChainID is a utility method to easily create a transaction signer
|
||||||
// from a single private key.
|
// from a single private key.
|
||||||
func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*TransactOpts, error) {
|
func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*TransactOpts, error) {
|
||||||
keyAddr := crypto.PubkeyToAddress(key.PublicKey)
|
|
||||||
if chainID == nil {
|
if chainID == nil {
|
||||||
return nil, ErrNoChainID
|
return nil, ErrNoChainID
|
||||||
}
|
}
|
||||||
signer := types.NewEIP155Signer(chainID)
|
keyAddr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
signer := types.LatestSignerForChainID(chainID)
|
||||||
return &TransactOpts{
|
return &TransactOpts{
|
||||||
From: keyAddr,
|
From: keyAddr,
|
||||||
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||||||
|
|
@ -132,5 +158,6 @@ func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*Tr
|
||||||
}
|
}
|
||||||
return tx.WithSignature(signer, signature)
|
return tx.WithSignature(signer, signature)
|
||||||
},
|
},
|
||||||
|
Context: context.Background(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain"
|
ethereum "github.com/XinFinOrg/XDPoSChain"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
)
|
)
|
||||||
|
|
@ -29,19 +29,23 @@ import (
|
||||||
var (
|
var (
|
||||||
// ErrNoCode is returned by call and transact operations for which the requested
|
// ErrNoCode is returned by call and transact operations for which the requested
|
||||||
// recipient contract to operate on does not exist in the state db or does not
|
// recipient contract to operate on does not exist in the state db or does not
|
||||||
// have any code associated with it (i.e. suicided).
|
// have any code associated with it (i.e. self-destructed).
|
||||||
ErrNoCode = errors.New("no contract code at given address")
|
ErrNoCode = errors.New("no contract code at given address")
|
||||||
|
|
||||||
// ErrNoPendingState is raised when attempting to perform a pending state action
|
// ErrNoPendingState is raised when attempting to perform a pending state action
|
||||||
// on a backend that doesn't implement PendingContractCaller.
|
// on a backend that doesn't implement PendingContractCaller.
|
||||||
ErrNoPendingState = errors.New("backend does not support pending state")
|
ErrNoPendingState = errors.New("backend does not support pending state")
|
||||||
|
|
||||||
|
// ErrNoBlockHashState is raised when attempting to perform a block hash action
|
||||||
|
// on a backend that doesn't implement BlockHashContractCaller.
|
||||||
|
ErrNoBlockHashState = errors.New("backend does not support block hash state")
|
||||||
|
|
||||||
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
|
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
|
||||||
// an empty contract behind.
|
// an empty contract behind.
|
||||||
ErrNoCodeAfterDeploy = errors.New("no contract code after deployment")
|
ErrNoCodeAfterDeploy = errors.New("no contract code after deployment")
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContractCaller defines the methods needed to allow operating with contract on a read
|
// ContractCaller defines the methods needed to allow operating with a contract on a read
|
||||||
// only basis.
|
// only basis.
|
||||||
type ContractCaller interface {
|
type ContractCaller interface {
|
||||||
// CodeAt returns the code of the given account. This is needed to differentiate
|
// CodeAt returns the code of the given account. This is needed to differentiate
|
||||||
|
|
@ -50,7 +54,7 @@ type ContractCaller interface {
|
||||||
|
|
||||||
// CallContract executes an Ethereum contract call with the specified data as the
|
// CallContract executes an Ethereum contract call with the specified data as the
|
||||||
// input.
|
// input.
|
||||||
CallContract(ctx context.Context, call XDPoSChain.CallMsg, blockNumber *big.Int) ([]byte, error)
|
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PendingContractCaller defines methods to perform contract calls on the pending state.
|
// PendingContractCaller defines methods to perform contract calls on the pending state.
|
||||||
|
|
@ -61,11 +65,22 @@ type PendingContractCaller interface {
|
||||||
PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error)
|
PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error)
|
||||||
|
|
||||||
// PendingCallContract executes an Ethereum contract call against the pending state.
|
// PendingCallContract executes an Ethereum contract call against the pending state.
|
||||||
PendingCallContract(ctx context.Context, call XDPoSChain.CallMsg) ([]byte, error)
|
PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContractTransactor defines the methods needed to allow operating with contract
|
// BlockHashContractCaller defines methods to perform contract calls on a specific block hash.
|
||||||
// on a write only basis. Beside the transacting method, the remainder are helpers
|
// Call will try to discover this interface when access to a block by hash is requested.
|
||||||
|
// If the backend does not support the block hash state, Call returns ErrNoBlockHashState.
|
||||||
|
type BlockHashContractCaller interface {
|
||||||
|
// CodeAtHash returns the code of the given account in the state at the specified block hash.
|
||||||
|
CodeAtHash(ctx context.Context, contract common.Address, blockHash common.Hash) ([]byte, error)
|
||||||
|
|
||||||
|
// CallContractAtHash executes an Ethereum contract call against the state at the specified block hash.
|
||||||
|
CallContractAtHash(ctx context.Context, call ethereum.CallMsg, blockHash common.Hash) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContractTransactor defines the methods needed to allow operating with a contract
|
||||||
|
// on a write only basis. Besides the transacting method, the remainder are helpers
|
||||||
// used when the user does not provide some needed values, but rather leaves it up
|
// used when the user does not provide some needed values, but rather leaves it up
|
||||||
// to the transactor to decide.
|
// to the transactor to decide.
|
||||||
type ContractTransactor interface {
|
type ContractTransactor interface {
|
||||||
|
|
@ -92,7 +107,7 @@ type ContractTransactor interface {
|
||||||
// There is no guarantee that this is the true gas limit requirement as other
|
// There is no guarantee that this is the true gas limit requirement as other
|
||||||
// transactions may be added or removed by miners, but it should provide a basis
|
// transactions may be added or removed by miners, but it should provide a basis
|
||||||
// for setting a reasonable default.
|
// for setting a reasonable default.
|
||||||
EstimateGas(ctx context.Context, call XDPoSChain.CallMsg) (gas uint64, err error)
|
EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
|
||||||
|
|
||||||
// SendTransaction injects the transaction into the pending pool for execution.
|
// SendTransaction injects the transaction into the pending pool for execution.
|
||||||
SendTransaction(ctx context.Context, tx *types.Transaction) error
|
SendTransaction(ctx context.Context, tx *types.Transaction) error
|
||||||
|
|
@ -105,11 +120,11 @@ type ContractFilterer interface {
|
||||||
// returning all the results in one batch.
|
// returning all the results in one batch.
|
||||||
//
|
//
|
||||||
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
||||||
FilterLogs(ctx context.Context, query XDPoSChain.FilterQuery) ([]types.Log, error)
|
FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error)
|
||||||
|
|
||||||
// SubscribeFilterLogs creates a background log filtering operation, returning
|
// SubscribeFilterLogs creates a background log filtering operation, returning
|
||||||
// a subscription immediately, which can be used to stream the found events.
|
// a subscription immediately, which can be used to stream the found events.
|
||||||
SubscribeFilterLogs(ctx context.Context, query XDPoSChain.FilterQuery, ch chan<- types.Log) (XDPoSChain.Subscription, error)
|
SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
|
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain"
|
ethereum "github.com/XinFinOrg/XDPoSChain"
|
||||||
"github.com/XinFinOrg/XDPoSChain/XDCx"
|
"github.com/XinFinOrg/XDPoSChain/XDCx"
|
||||||
"github.com/XinFinOrg/XDPoSChain/XDCxlending"
|
"github.com/XinFinOrg/XDPoSChain/XDCxlending"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
|
|
@ -34,6 +34,7 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common/math"
|
"github.com/XinFinOrg/XDPoSChain/common/math"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||||
|
|
@ -52,13 +53,21 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/rpc"
|
"github.com/XinFinOrg/XDPoSChain/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend.
|
// This nil assignment ensures at compile time that SimulatedBackend implements bind.ContractBackend.
|
||||||
var _ bind.ContractBackend = (*SimulatedBackend)(nil)
|
var _ bind.ContractBackend = (*SimulatedBackend)(nil)
|
||||||
|
|
||||||
var errBlockNumberUnsupported = errors.New("SimulatedBackend cannot access blocks other than the latest block")
|
var (
|
||||||
|
errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block")
|
||||||
|
errBlockHashUnsupported = errors.New("simulatedBackend cannot access blocks by hash other than the latest block")
|
||||||
|
errBlockDoesNotExist = errors.New("block does not exist in blockchain")
|
||||||
|
errTransactionDoesNotExist = errors.New("transaction does not exist")
|
||||||
|
)
|
||||||
|
|
||||||
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
|
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
|
||||||
// the background. Its main purpose is to allow easily testing contract bindings.
|
// the background. Its main purpose is to allow for easy testing of contract bindings.
|
||||||
|
// Simulated backend implements the following interfaces:
|
||||||
|
// ChainReader, ChainStateReader, ContractBackend, ContractCaller, ContractFilterer, ContractTransactor,
|
||||||
|
// DeployBackend, GasEstimator, GasPricer, LogFilterer, PendingContractCaller, TransactionReader, and TransactionSender
|
||||||
type SimulatedBackend struct {
|
type SimulatedBackend struct {
|
||||||
database ethdb.Database // In memory database to store our testing data
|
database ethdb.Database // In memory database to store our testing data
|
||||||
blockchain *core.BlockChain // Ethereum blockchain to handle the consensus
|
blockchain *core.BlockChain // Ethereum blockchain to handle the consensus
|
||||||
|
|
@ -78,13 +87,9 @@ func SimulateWalletAddressAndSignFn() (common.Address, func(account accounts.Acc
|
||||||
veryLightScryptN := 2
|
veryLightScryptN := 2
|
||||||
veryLightScryptP := 1
|
veryLightScryptP := 1
|
||||||
dir, _ := os.MkdirTemp("", "eth-SimulateWalletAddressAndSignFn-test")
|
dir, _ := os.MkdirTemp("", "eth-SimulateWalletAddressAndSignFn-test")
|
||||||
|
|
||||||
new := func(kd string) *keystore.KeyStore {
|
|
||||||
return keystore.NewKeyStore(kd, veryLightScryptN, veryLightScryptP)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
ks := new(dir)
|
|
||||||
|
ks := keystore.NewKeyStore(dir, veryLightScryptN, veryLightScryptP)
|
||||||
pass := "" // not used but required by API
|
pass := "" // not used but required by API
|
||||||
a1, err := ks.NewAccount(pass)
|
a1, err := ks.NewAccount(pass)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -96,8 +101,8 @@ func SimulateWalletAddressAndSignFn() (common.Address, func(account accounts.Acc
|
||||||
return a1.Address, ks.SignHash, nil
|
return a1.Address, ks.SignHash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// XDC simulated backend for testing purpose.
|
// NewXDCSimulatedBackend creates a new backend for testing purpose.
|
||||||
func NewXDCSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64, chainConfig *params.ChainConfig) *SimulatedBackend {
|
func NewXDCSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64, chainConfig *params.ChainConfig) *SimulatedBackend {
|
||||||
database := rawdb.NewMemoryDatabase()
|
database := rawdb.NewMemoryDatabase()
|
||||||
genesis := core.Genesis{
|
genesis := core.Genesis{
|
||||||
GasLimit: gasLimit, // need this big, support initial smart contract
|
GasLimit: gasLimit, // need this big, support initial smart contract
|
||||||
|
|
@ -135,16 +140,16 @@ func NewXDCSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64, chainConfi
|
||||||
backend.events = filters.NewEventSystem(backend.filterSystem, false)
|
backend.events = filters.NewEventSystem(backend.filterSystem, false)
|
||||||
|
|
||||||
blockchain.Client = backend
|
blockchain.Client = backend
|
||||||
backend.rollback()
|
backend.rollback(blockchain.CurrentBlock())
|
||||||
return backend
|
return backend
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
|
// SimulOldNewSimulatedBackendatedBackend creates a new binding backend based on the given database
|
||||||
// for testing purposes.
|
// and uses a simulated blockchain for testing purposes.
|
||||||
// A simulated backend always uses chainID 1337.
|
// A simulated backend always uses chainID 1337.
|
||||||
func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend {
|
func NewSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
|
||||||
database := rawdb.NewMemoryDatabase()
|
database := rawdb.NewMemoryDatabase()
|
||||||
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, Alloc: alloc, GasLimit: 42000000}
|
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: gasLimit, Alloc: alloc}
|
||||||
genesis.MustCommit(database)
|
genesis.MustCommit(database)
|
||||||
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{})
|
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{})
|
||||||
|
|
||||||
|
|
@ -158,7 +163,7 @@ func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend {
|
||||||
backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{})
|
backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{})
|
||||||
backend.events = filters.NewEventSystem(backend.filterSystem, false)
|
backend.events = filters.NewEventSystem(backend.filterSystem, false)
|
||||||
|
|
||||||
backend.rollback()
|
backend.rollback(blockchain.CurrentBlock())
|
||||||
return backend
|
return backend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,14 +175,20 @@ func (b *SimulatedBackend) Close() error {
|
||||||
|
|
||||||
// Commit imports all the pending transactions as a single block and starts a
|
// Commit imports all the pending transactions as a single block and starts a
|
||||||
// fresh new state.
|
// fresh new state.
|
||||||
func (b *SimulatedBackend) Commit() {
|
func (b *SimulatedBackend) Commit() common.Hash {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil {
|
if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil {
|
||||||
panic(err) // This cannot happen unless the simulator is wrong, fail in that case
|
panic(err) // This cannot happen unless the simulator is wrong, fail in that case
|
||||||
}
|
}
|
||||||
b.rollback()
|
blockHash := b.pendingBlock.Hash()
|
||||||
|
|
||||||
|
// Using the last inserted block here makes it possible to build on a side
|
||||||
|
// chain after a fork.
|
||||||
|
b.rollback(b.pendingBlock)
|
||||||
|
|
||||||
|
return blockHash
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rollback aborts all pending transactions, reverting to the last committed state.
|
// Rollback aborts all pending transactions, reverting to the last committed state.
|
||||||
|
|
@ -185,15 +196,54 @@ func (b *SimulatedBackend) Rollback() {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
b.rollback()
|
b.rollback(b.blockchain.CurrentBlock())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SimulatedBackend) rollback() {
|
func (b *SimulatedBackend) rollback(parent *types.Block) {
|
||||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.blockchain.Engine(), b.database, 1, func(int, *core.BlockGen) {})
|
blocks, _ := core.GenerateChain(b.config, parent, b.blockchain.Engine(), b.database, 1, func(int, *core.BlockGen) {})
|
||||||
statedb, _ := b.blockchain.State()
|
stateDB, _ := b.blockchain.State()
|
||||||
|
|
||||||
b.pendingBlock = blocks[0]
|
b.pendingBlock = blocks[0]
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
|
b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fork creates a side-chain that can be used to simulate reorgs.
|
||||||
|
//
|
||||||
|
// This function should be called with the ancestor block where the new side
|
||||||
|
// chain should be started. Transactions (old and new) can then be applied on
|
||||||
|
// top and Commit-ed.
|
||||||
|
//
|
||||||
|
// Note, the side-chain will only become canonical (and trigger the events) when
|
||||||
|
// it becomes longer. Until then CallContract will still operate on the current
|
||||||
|
// canonical chain.
|
||||||
|
//
|
||||||
|
// There is a % chance that the side chain becomes canonical at the same length
|
||||||
|
// to simulate live network behavior.
|
||||||
|
func (b *SimulatedBackend) Fork(ctx context.Context, parent common.Hash) error {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
if len(b.pendingBlock.Transactions()) != 0 {
|
||||||
|
return errors.New("pending block dirty")
|
||||||
|
}
|
||||||
|
block, err := b.blockByHash(ctx, parent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.rollback(block)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateByBlockNumber retrieves a state by a given blocknumber.
|
||||||
|
func (b *SimulatedBackend) stateByBlockNumber(ctx context.Context, blockNumber *big.Int) (*state.StateDB, error) {
|
||||||
|
if blockNumber == nil || blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) == 0 {
|
||||||
|
return b.blockchain.State()
|
||||||
|
}
|
||||||
|
block, err := b.blockByNumber(ctx, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b.blockchain.StateAt(block.Root())
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodeAt returns the code associated with a certain account in the blockchain.
|
// CodeAt returns the code associated with a certain account in the blockchain.
|
||||||
|
|
@ -201,11 +251,29 @@ func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address,
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||||
return nil, errBlockNumberUnsupported
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
statedb, _ := b.blockchain.State()
|
return stateDB.GetCode(contract), nil
|
||||||
return statedb.GetCode(contract), nil
|
}
|
||||||
|
|
||||||
|
// CodeAtHash returns the code associated with a certain account in the blockchain.
|
||||||
|
func (b *SimulatedBackend) CodeAtHash(ctx context.Context, contract common.Address, blockHash common.Hash) ([]byte, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
header, err := b.headerByHash(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stateDB, err := b.blockchain.StateAt(header.Root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return stateDB.GetCode(contract), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BalanceAt returns the wei balance of a certain account in the blockchain.
|
// BalanceAt returns the wei balance of a certain account in the blockchain.
|
||||||
|
|
@ -213,11 +281,11 @@ func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Addres
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||||
return nil, errBlockNumberUnsupported
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
statedb, _ := b.blockchain.State()
|
return stateDB.GetBalance(contract), nil
|
||||||
return statedb.GetBalance(contract), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NonceAt returns the nonce of a certain account in the blockchain.
|
// NonceAt returns the nonce of a certain account in the blockchain.
|
||||||
|
|
@ -225,11 +293,11 @@ func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address,
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||||
return 0, errBlockNumberUnsupported
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
statedb, _ := b.blockchain.State()
|
return stateDB.GetNonce(contract), nil
|
||||||
return statedb.GetNonce(contract), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageAt returns the value of key in the storage of an account in the blockchain.
|
// StorageAt returns the value of key in the storage of an account in the blockchain.
|
||||||
|
|
@ -237,11 +305,11 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||||
return nil, errBlockNumberUnsupported
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
statedb, _ := b.blockchain.State()
|
val := stateDB.GetState(contract, key)
|
||||||
val := statedb.GetState(contract, key)
|
|
||||||
return val[:], nil
|
return val[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,17 +321,109 @@ func (b *SimulatedBackend) ForEachStorageAt(ctx context.Context, contract common
|
||||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
||||||
return errBlockNumberUnsupported
|
return errBlockNumberUnsupported
|
||||||
}
|
}
|
||||||
statedb, _ := b.blockchain.State()
|
stateDB, _ := b.blockchain.State()
|
||||||
statedb.ForEachStorage(contract, f)
|
stateDB.ForEachStorage(contract, f)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionReceipt returns the receipt of a transaction.
|
// TransactionReceipt returns the receipt of a transaction.
|
||||||
func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash, b.config)
|
receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash, b.config)
|
||||||
|
if receipt == nil {
|
||||||
|
return nil, ethereum.ErrNotFound
|
||||||
|
}
|
||||||
return receipt, nil
|
return receipt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransactionByHash checks the pool of pending transactions in addition to the
|
||||||
|
// blockchain. The isPending return value indicates whether the transaction has been
|
||||||
|
// mined yet. Note that the transaction may not be part of the canonical chain even if
|
||||||
|
// it's not pending.
|
||||||
|
func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
tx := b.pendingBlock.Transaction(txHash)
|
||||||
|
if tx != nil {
|
||||||
|
return tx, true, nil
|
||||||
|
}
|
||||||
|
tx, _, _, _ = rawdb.ReadTransaction(b.database, txHash)
|
||||||
|
if tx != nil {
|
||||||
|
return tx, false, nil
|
||||||
|
}
|
||||||
|
return nil, false, ethereum.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockByHash retrieves a block based on the block hash.
|
||||||
|
func (b *SimulatedBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
return b.blockByHash(ctx, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockByHash retrieves a block based on the block hash without Locking.
|
||||||
|
func (b *SimulatedBackend) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||||
|
if hash == b.pendingBlock.Hash() {
|
||||||
|
return b.pendingBlock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
block := b.blockchain.GetBlockByHash(hash)
|
||||||
|
if block != nil {
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errBlockDoesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockByNumber retrieves a block from the database by number, caching it
|
||||||
|
// (associated with its hash) if found.
|
||||||
|
func (b *SimulatedBackend) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
return b.blockByNumber(ctx, number)
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockByNumber retrieves a block from the database by number, caching it
|
||||||
|
// (associated with its hash) if found without Lock.
|
||||||
|
func (b *SimulatedBackend) blockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
|
||||||
|
if number == nil || number.Cmp(b.pendingBlock.Number()) == 0 {
|
||||||
|
return b.blockchain.CurrentBlock(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
block := b.blockchain.GetBlockByNumber(uint64(number.Int64()))
|
||||||
|
if block == nil {
|
||||||
|
return nil, errBlockDoesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderByHash returns a block header from the current canonical chain.
|
||||||
|
func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
return b.headerByHash(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerByHash retrieves a header from the database by hash without Lock.
|
||||||
|
func (b *SimulatedBackend) headerByHash(hash common.Hash) (*types.Header, error) {
|
||||||
|
if hash == b.pendingBlock.Hash() {
|
||||||
|
return b.pendingBlock.Header(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
header := b.blockchain.GetHeaderByHash(hash)
|
||||||
|
if header == nil {
|
||||||
|
return nil, errBlockDoesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
return header, nil
|
||||||
|
}
|
||||||
|
|
||||||
// HeaderByNumber returns a block header from the current canonical chain. If number is
|
// HeaderByNumber returns a block header from the current canonical chain. If number is
|
||||||
// nil, the latest known header is returned.
|
// nil, the latest known header is returned.
|
||||||
func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (*types.Header, error) {
|
func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (*types.Header, error) {
|
||||||
|
|
@ -277,6 +437,50 @@ func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (
|
||||||
return b.blockchain.GetHeaderByNumber(uint64(block.Int64())), nil
|
return b.blockchain.GetHeaderByNumber(uint64(block.Int64())), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransactionCount returns the number of transactions in a given block.
|
||||||
|
func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
if blockHash == b.pendingBlock.Hash() {
|
||||||
|
return uint(b.pendingBlock.Transactions().Len()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
block := b.blockchain.GetBlockByHash(blockHash)
|
||||||
|
if block == nil {
|
||||||
|
return uint(0), errBlockDoesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint(block.Transactions().Len()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionInBlock returns the transaction for a specific block at a specific index.
|
||||||
|
func (b *SimulatedBackend) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
if blockHash == b.pendingBlock.Hash() {
|
||||||
|
transactions := b.pendingBlock.Transactions()
|
||||||
|
if uint(len(transactions)) < index+1 {
|
||||||
|
return nil, errTransactionDoesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions[index], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
block := b.blockchain.GetBlockByHash(blockHash)
|
||||||
|
if block == nil {
|
||||||
|
return nil, errBlockDoesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions := block.Transactions()
|
||||||
|
if uint(len(transactions)) < index+1 {
|
||||||
|
return nil, errTransactionDoesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions[index], nil
|
||||||
|
}
|
||||||
|
|
||||||
// PendingCodeAt returns the code associated with an account in the pending state.
|
// PendingCodeAt returns the code associated with an account in the pending state.
|
||||||
func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
|
func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
|
|
@ -285,27 +489,77 @@ func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Ad
|
||||||
return b.pendingState.GetCode(contract), nil
|
return b.pendingState.GetCode(contract), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newRevertError(result *core.ExecutionResult) *revertError {
|
||||||
|
reason, errUnpack := abi.UnpackRevert(result.Revert())
|
||||||
|
err := errors.New("execution reverted")
|
||||||
|
if errUnpack == nil {
|
||||||
|
err = fmt.Errorf("execution reverted: %v", reason)
|
||||||
|
}
|
||||||
|
return &revertError{
|
||||||
|
error: err,
|
||||||
|
reason: hexutil.Encode(result.Revert()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// revertError is an API error that encompasses an EVM revert with JSON error
|
||||||
|
// code and a binary data blob.
|
||||||
|
type revertError struct {
|
||||||
|
error
|
||||||
|
reason string // revert reason hex encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorCode returns the JSON error code for a revert.
|
||||||
|
// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal
|
||||||
|
func (e *revertError) ErrorCode() int {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorData returns the hex encoded revert reason.
|
||||||
|
func (e *revertError) ErrorData() interface{} {
|
||||||
|
return e.reason
|
||||||
|
}
|
||||||
|
|
||||||
// CallContract executes a contract call.
|
// CallContract executes a contract call.
|
||||||
func (b *SimulatedBackend) CallContract(ctx context.Context, call XDPoSChain.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
||||||
return nil, errBlockNumberUnsupported
|
return nil, errBlockNumberUnsupported
|
||||||
}
|
}
|
||||||
state, err := b.blockchain.State()
|
return b.callContractAtHead(ctx, call)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallContractAtHash executes a contract call on a specific block hash.
|
||||||
|
func (b *SimulatedBackend) CallContractAtHash(ctx context.Context, call ethereum.CallMsg, blockHash common.Hash) ([]byte, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
if blockHash != b.blockchain.CurrentBlock().Hash() {
|
||||||
|
return nil, errBlockHashUnsupported
|
||||||
|
}
|
||||||
|
return b.callContractAtHead(ctx, call)
|
||||||
|
}
|
||||||
|
|
||||||
|
// callContractAtHead executes a contract call against the latest block state.
|
||||||
|
func (b *SimulatedBackend) callContractAtHead(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
|
||||||
|
stateDB, err := b.blockchain.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state)
|
res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), stateDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return res.Return(), nil
|
// If the result contains a revert reason, try to unpack and return it.
|
||||||
|
if len(res.Revert()) > 0 {
|
||||||
|
return nil, newRevertError(res)
|
||||||
|
}
|
||||||
|
return res.Return(), res.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PendingCallContract executes a contract call on the pending state.
|
// PendingCallContract executes a contract call on the pending state.
|
||||||
func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call XDPoSChain.CallMsg) ([]byte, error) {
|
func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot())
|
defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot())
|
||||||
|
|
@ -314,7 +568,11 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call XDPoSCh
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return res.Return(), nil
|
// If the result contains a revert reason, try to unpack and return it.
|
||||||
|
if len(res.Revert()) > 0 {
|
||||||
|
return nil, newRevertError(res)
|
||||||
|
}
|
||||||
|
return res.Return(), res.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
|
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
|
||||||
|
|
@ -329,6 +587,9 @@ func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Ad
|
||||||
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
|
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
|
||||||
// chain doesn't have miners, we just return a gas price of 1 for any call.
|
// chain doesn't have miners, we just return a gas price of 1 for any call.
|
||||||
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
if b.pendingBlock.Header().BaseFee != nil {
|
if b.pendingBlock.Header().BaseFee != nil {
|
||||||
return b.pendingBlock.Header().BaseFee, nil
|
return b.pendingBlock.Header().BaseFee, nil
|
||||||
}
|
}
|
||||||
|
|
@ -343,7 +604,7 @@ func (b *SimulatedBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, erro
|
||||||
|
|
||||||
// EstimateGas executes the requested code against the currently pending block/state and
|
// EstimateGas executes the requested code against the currently pending block/state and
|
||||||
// returns the used amount of gas.
|
// returns the used amount of gas.
|
||||||
func (b *SimulatedBackend) EstimateGas(ctx context.Context, call XDPoSChain.CallMsg) (uint64, error) {
|
func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
|
@ -400,17 +661,11 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call XDPoSChain.Call
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if failed {
|
if failed {
|
||||||
if result != nil && result.Err != vm.ErrOutOfGas {
|
if result != nil && !errors.Is(result.Err, vm.ErrOutOfGas) {
|
||||||
errMsg := fmt.Sprintf("always failing transaction (%v)", result.Err)
|
|
||||||
if len(result.Revert()) > 0 {
|
if len(result.Revert()) > 0 {
|
||||||
ret, err := abi.UnpackRevert(result.Revert())
|
return 0, newRevertError(result)
|
||||||
if err != nil {
|
|
||||||
errMsg += fmt.Sprintf(" (%#x)", result.Revert())
|
|
||||||
} else {
|
|
||||||
errMsg += fmt.Sprintf(" (%s)", ret)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 0, errors.New(errMsg)
|
return 0, result.Err
|
||||||
}
|
}
|
||||||
// Otherwise, the specified gas cap is too low
|
// Otherwise, the specified gas cap is too low
|
||||||
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
|
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
|
||||||
|
|
@ -421,7 +676,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call XDPoSChain.Call
|
||||||
|
|
||||||
// callContract implements common code between normal and pending contract calls.
|
// callContract implements common code between normal and pending contract calls.
|
||||||
// state is modified during execution, make sure to copy it if necessary.
|
// state is modified during execution, make sure to copy it if necessary.
|
||||||
func (b *SimulatedBackend) callContract(ctx context.Context, call XDPoSChain.CallMsg, block *types.Block, statedb *state.StateDB) (*core.ExecutionResult, error) {
|
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, stateDB *state.StateDB) (*core.ExecutionResult, error) {
|
||||||
// Gas prices post 1559 need to be initialized
|
// Gas prices post 1559 need to be initialized
|
||||||
if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) {
|
if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) {
|
||||||
return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
||||||
|
|
@ -439,7 +694,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call XDPoSChain.Cal
|
||||||
// User specified the legacy gas field, convert to 1559 gas typing
|
// User specified the legacy gas field, convert to 1559 gas typing
|
||||||
call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice
|
call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice
|
||||||
} else {
|
} else {
|
||||||
// User specified 1559 gas feilds (or none), use those
|
// User specified 1559 gas fields (or none), use those
|
||||||
if call.GasFeeCap == nil {
|
if call.GasFeeCap == nil {
|
||||||
call.GasFeeCap = new(big.Int)
|
call.GasFeeCap = new(big.Int)
|
||||||
}
|
}
|
||||||
|
|
@ -458,17 +713,17 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call XDPoSChain.Cal
|
||||||
}
|
}
|
||||||
// Ensure message is initialized properly.
|
// Ensure message is initialized properly.
|
||||||
if call.Gas == 0 {
|
if call.Gas == 0 {
|
||||||
call.Gas = 50000000
|
call.Gas = 10 * head.GasLimit
|
||||||
}
|
}
|
||||||
if call.Value == nil {
|
if call.Value == nil {
|
||||||
call.Value = new(big.Int)
|
call.Value = new(big.Int)
|
||||||
}
|
}
|
||||||
// Set infinite balance to the fake caller account.
|
// Set infinite balance to the fake caller account.
|
||||||
from := statedb.GetOrNewStateObject(call.From)
|
from := stateDB.GetOrNewStateObject(call.From)
|
||||||
from.SetBalance(math.MaxBig256)
|
from.SetBalance(math.MaxBig256)
|
||||||
// Execute the call.
|
// Execute the call.
|
||||||
msg := callMsg{call}
|
msg := callMsg{call}
|
||||||
feeCapacity := state.GetTRC21FeeCapacityFromState(statedb)
|
feeCapacity := state.GetTRC21FeeCapacityFromState(stateDB)
|
||||||
if msg.To() != nil {
|
if msg.To() != nil {
|
||||||
if value, ok := feeCapacity[*msg.To()]; ok {
|
if value, ok := feeCapacity[*msg.To()]; ok {
|
||||||
msg.CallMsg.BalanceTokenFee = value
|
msg.CallMsg.BalanceTokenFee = value
|
||||||
|
|
@ -479,41 +734,45 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call XDPoSChain.Cal
|
||||||
evmContext := core.NewEVMBlockContext(block.Header(), b.blockchain, nil)
|
evmContext := core.NewEVMBlockContext(block.Header(), b.blockchain, nil)
|
||||||
// Create a new environment which holds all relevant information
|
// Create a new environment which holds all relevant information
|
||||||
// about the transaction and calling mechanisms.
|
// about the transaction and calling mechanisms.
|
||||||
vmenv := vm.NewEVM(evmContext, txContext, statedb, nil, b.config, vm.Config{NoBaseFee: true})
|
vmenv := vm.NewEVM(evmContext, txContext, stateDB, nil, b.config, vm.Config{NoBaseFee: true})
|
||||||
gaspool := new(core.GasPool).AddGas(gomath.MaxUint64)
|
gaspool := new(core.GasPool).AddGas(gomath.MaxUint64)
|
||||||
owner := common.Address{}
|
owner := common.Address{}
|
||||||
return core.NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner)
|
return core.NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTransaction updates the pending block to include the given transaction.
|
// SendTransaction updates the pending block to include the given transaction.
|
||||||
// It panics if the transaction is invalid.
|
|
||||||
func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
|
func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
// Check transaction validity.
|
// Get the last block
|
||||||
block := b.blockchain.CurrentBlock()
|
block, err := b.blockByHash(ctx, b.pendingBlock.ParentHash())
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("could not fetch parent")
|
||||||
|
}
|
||||||
|
// Check transaction validity
|
||||||
signer := types.MakeSigner(b.blockchain.Config(), block.Number())
|
signer := types.MakeSigner(b.blockchain.Config(), block.Number())
|
||||||
sender, err := types.Sender(signer, tx)
|
sender, err := types.Sender(signer, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("invalid transaction: %v", err))
|
return fmt.Errorf("invalid transaction: %v", err)
|
||||||
}
|
}
|
||||||
nonce := b.pendingState.GetNonce(sender)
|
nonce := b.pendingState.GetNonce(sender)
|
||||||
if tx.Nonce() != nonce {
|
if tx.Nonce() != nonce {
|
||||||
panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce))
|
return fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce)
|
||||||
}
|
}
|
||||||
|
// Include tx in chain
|
||||||
// Include tx in chain.
|
|
||||||
blocks, receipts := core.GenerateChain(b.config, block, b.blockchain.Engine(), b.database, 1, func(number int, block *core.BlockGen) {
|
blocks, receipts := core.GenerateChain(b.config, block, b.blockchain.Engine(), b.database, 1, func(number int, block *core.BlockGen) {
|
||||||
for _, tx := range b.pendingBlock.Transactions() {
|
for _, tx := range b.pendingBlock.Transactions() {
|
||||||
block.AddTxWithChain(b.blockchain, tx)
|
block.AddTxWithChain(b.blockchain, tx)
|
||||||
}
|
}
|
||||||
block.AddTxWithChain(b.blockchain, tx)
|
block.AddTxWithChain(b.blockchain, tx)
|
||||||
})
|
})
|
||||||
statedb, _ := b.blockchain.State()
|
stateDB, err := b.blockchain.State()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
b.pendingBlock = blocks[0]
|
b.pendingBlock = blocks[0]
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
|
b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database())
|
||||||
b.pendingReceipts = receipts[0]
|
b.pendingReceipts = receipts[0]
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -522,7 +781,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
|
||||||
// returning all the results in one batch.
|
// returning all the results in one batch.
|
||||||
//
|
//
|
||||||
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
||||||
func (b *SimulatedBackend) FilterLogs(ctx context.Context, query XDPoSChain.FilterQuery) ([]types.Log, error) {
|
func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
|
||||||
var filter *filters.Filter
|
var filter *filters.Filter
|
||||||
if query.BlockHash != nil {
|
if query.BlockHash != nil {
|
||||||
// Block filter requested, construct a single-shot filter
|
// Block filter requested, construct a single-shot filter
|
||||||
|
|
@ -554,7 +813,7 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query XDPoSChain.Filt
|
||||||
|
|
||||||
// SubscribeFilterLogs creates a background log filtering operation, returning a
|
// SubscribeFilterLogs creates a background log filtering operation, returning a
|
||||||
// subscription immediately, which can be used to stream the found events.
|
// subscription immediately, which can be used to stream the found events.
|
||||||
func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query XDPoSChain.FilterQuery, ch chan<- types.Log) (XDPoSChain.Subscription, error) {
|
func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
|
||||||
// Subscribe to contract events
|
// Subscribe to contract events
|
||||||
sink := make(chan []*types.Log)
|
sink := make(chan []*types.Log)
|
||||||
|
|
||||||
|
|
@ -586,32 +845,67 @@ func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query XDPoSC
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubscribeNewHead returns an event subscription for a new header.
|
||||||
|
func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
|
||||||
|
// subscribe to a new head
|
||||||
|
sink := make(chan *types.Header)
|
||||||
|
sub := b.events.SubscribeNewHeads(sink)
|
||||||
|
|
||||||
|
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case head := <-sink:
|
||||||
|
select {
|
||||||
|
case ch <- head:
|
||||||
|
case err := <-sub.Err():
|
||||||
|
return err
|
||||||
|
case <-quit:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case err := <-sub.Err():
|
||||||
|
return err
|
||||||
|
case <-quit:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
// AdjustTime adds a time shift to the simulated clock.
|
// AdjustTime adds a time shift to the simulated clock.
|
||||||
|
// It can only be called on empty blocks.
|
||||||
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.blockchain.Engine(), b.database, 1, func(number int, block *core.BlockGen) {
|
|
||||||
// blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.blockchain.Engine(), b.database, 1, func(number int, block *core.BlockGen) {
|
if len(b.pendingBlock.Transactions()) != 0 {
|
||||||
for _, tx := range b.pendingBlock.Transactions() {
|
return errors.New("could not adjust time on non-empty block")
|
||||||
block.AddTx(tx)
|
}
|
||||||
}
|
// Get the last block
|
||||||
|
block := b.blockchain.GetBlockByHash(b.pendingBlock.ParentHash())
|
||||||
|
if block == nil {
|
||||||
|
return errors.New("could not find parent")
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks, _ := core.GenerateChain(b.config, block, b.blockchain.Engine(), b.database, 1, func(number int, block *core.BlockGen) {
|
||||||
block.OffsetTime(int64(adjustment.Seconds()))
|
block.OffsetTime(int64(adjustment.Seconds()))
|
||||||
})
|
})
|
||||||
statedb, _ := b.blockchain.State()
|
stateDB, _ := b.blockchain.State()
|
||||||
|
|
||||||
b.pendingBlock = blocks[0]
|
b.pendingBlock = blocks[0]
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
|
b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SimulatedBackend) GetBlockChain() *core.BlockChain {
|
// Blockchain returns the underlying blockchain.
|
||||||
|
func (b *SimulatedBackend) BlockChain() *core.BlockChain {
|
||||||
return b.blockchain
|
return b.blockchain
|
||||||
}
|
}
|
||||||
|
|
||||||
// callMsg implements core.Message to allow passing it as a transaction simulator.
|
// callMsg implements core.Message to allow passing it as a transaction simulator.
|
||||||
type callMsg struct {
|
type callMsg struct {
|
||||||
XDPoSChain.CallMsg
|
ethereum.CallMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m callMsg) From() common.Address { return m.CallMsg.From }
|
func (m callMsg) From() common.Address { return m.CallMsg.From }
|
||||||
|
|
@ -638,11 +932,32 @@ type filterBackend struct {
|
||||||
func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db }
|
func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db }
|
||||||
func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") }
|
func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") }
|
||||||
|
|
||||||
func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumber) (*types.Header, error) {
|
func (fb *filterBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
|
||||||
if block == rpc.LatestBlockNumber {
|
switch number {
|
||||||
|
case rpc.PendingBlockNumber:
|
||||||
|
if block := fb.backend.pendingBlock; block != nil {
|
||||||
|
return block.Header(), nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
case rpc.LatestBlockNumber:
|
||||||
return fb.bc.CurrentHeader(), nil
|
return fb.bc.CurrentHeader(), nil
|
||||||
|
case rpc.CommittedBlockNumber:
|
||||||
|
if fb.bc.Config().XDPoS == nil {
|
||||||
|
return nil, errors.New("only XDPoS v2 supports committed block lookup")
|
||||||
|
}
|
||||||
|
current := fb.bc.CurrentBlock().Header()
|
||||||
|
if fb.bc.Config().XDPoS.BlockConsensusVersion(
|
||||||
|
current.Number,
|
||||||
|
current.Extra,
|
||||||
|
XDPoS.ExtraFieldCheck,
|
||||||
|
) == params.ConsensusEngineVersion2 {
|
||||||
|
confirmedHash := fb.bc.Engine().(*XDPoS.XDPoS).EngineV2.GetLatestCommittedBlockInfo().Hash
|
||||||
|
return fb.bc.GetHeaderByHash(confirmedHash), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("only XDPoS v2 can lookup committed block")
|
||||||
|
default:
|
||||||
|
return fb.bc.GetHeaderByNumber(uint64(number.Int64())), nil
|
||||||
}
|
}
|
||||||
return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||||
|
|
@ -699,6 +1014,14 @@ func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.Matche
|
||||||
panic("not supported")
|
panic("not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fb *filterBackend) ChainConfig() *params.ChainConfig {
|
||||||
|
panic("not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fb *filterBackend) CurrentHeader() *types.Header {
|
||||||
|
panic("not supported")
|
||||||
|
}
|
||||||
|
|
||||||
func nullSubscription() event.Subscription {
|
func nullSubscription() event.Subscription {
|
||||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||||
<-quit
|
<-quit
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -21,8 +21,10 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain"
|
ethereum "github.com/XinFinOrg/XDPoSChain"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
|
|
@ -30,8 +32,11 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/event"
|
"github.com/XinFinOrg/XDPoSChain/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const basefeeWiggleMultiplier = 2
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errNoEventSignature = errors.New("no event signature")
|
errNoEventSignature = errors.New("no event signature")
|
||||||
|
errEventSignatureMismatch = errors.New("event signature mismatch")
|
||||||
)
|
)
|
||||||
|
|
||||||
// SignerFn is a signer function callback when a contract requires a method to
|
// SignerFn is a signer function callback when a contract requires a method to
|
||||||
|
|
@ -40,10 +45,11 @@ type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, erro
|
||||||
|
|
||||||
// CallOpts is the collection of options to fine tune a contract call request.
|
// CallOpts is the collection of options to fine tune a contract call request.
|
||||||
type CallOpts struct {
|
type CallOpts struct {
|
||||||
Pending bool // Whether to operate on the pending state or the last known one
|
Pending bool // Whether to operate on the pending state or the last known one
|
||||||
From common.Address // Optional the sender address, otherwise the first account is used
|
From common.Address // Optional the sender address, otherwise the first account is used
|
||||||
|
BlockNumber *big.Int // Optional the block number on which the call should be performed
|
||||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
BlockHash common.Hash // Optional the block hash on which the call should be performed
|
||||||
|
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactOpts is the collection of authorization data required to create a
|
// TransactOpts is the collection of authorization data required to create a
|
||||||
|
|
@ -53,11 +59,12 @@ type TransactOpts struct {
|
||||||
Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state)
|
Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state)
|
||||||
Signer SignerFn // Method to use for signing the transaction (mandatory)
|
Signer SignerFn // Method to use for signing the transaction (mandatory)
|
||||||
|
|
||||||
Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds)
|
Value *big.Int // Funds to transfer along the transaction (nil = 0 = no funds)
|
||||||
GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle)
|
GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle)
|
||||||
GasFeeCap *big.Int // Gas fee cap to use for the 1559 transaction execution (nil = gas price oracle)
|
GasFeeCap *big.Int // Gas fee cap to use for the 1559 transaction execution (nil = gas price oracle)
|
||||||
GasTipCap *big.Int // Gas priority fee cap to use for the 1559 transaction execution (nil = gas price oracle)
|
GasTipCap *big.Int // Gas priority fee cap to use for the 1559 transaction execution (nil = gas price oracle)
|
||||||
GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate)
|
GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate)
|
||||||
|
AccessList types.AccessList // Access list to set for the transaction execution (nil = no access list)
|
||||||
|
|
||||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
||||||
|
|
||||||
|
|
@ -80,6 +87,29 @@ type WatchOpts struct {
|
||||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MetaData collects all metadata for a bound contract.
|
||||||
|
type MetaData struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
Sigs map[string]string
|
||||||
|
Bin string
|
||||||
|
ABI string
|
||||||
|
ab *abi.ABI
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MetaData) GetAbi() (*abi.ABI, error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
if m.ab != nil {
|
||||||
|
return m.ab, nil
|
||||||
|
}
|
||||||
|
if parsed, err := abi.JSON(strings.NewReader(m.ABI)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
m.ab = &parsed
|
||||||
|
}
|
||||||
|
return m.ab, nil
|
||||||
|
}
|
||||||
|
|
||||||
// BoundContract is the base wrapper object that reflects a contract on the
|
// BoundContract is the base wrapper object that reflects a contract on the
|
||||||
// Ethereum network. It contains a collection of methods that are used by the
|
// Ethereum network. It contains a collection of methods that are used by the
|
||||||
// higher level contract bindings to operate.
|
// higher level contract bindings to operate.
|
||||||
|
|
@ -125,18 +155,21 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co
|
||||||
// sets the output to result. The result type might be a single field for simple
|
// sets the output to result. The result type might be a single field for simple
|
||||||
// returns, a slice of interfaces for anonymous returns and a struct for named
|
// returns, a slice of interfaces for anonymous returns and a struct for named
|
||||||
// returns.
|
// returns.
|
||||||
func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, params ...interface{}) error {
|
func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method string, params ...interface{}) error {
|
||||||
// Don't crash on a lazy user
|
// Don't crash on a lazy user
|
||||||
if opts == nil {
|
if opts == nil {
|
||||||
opts = new(CallOpts)
|
opts = new(CallOpts)
|
||||||
}
|
}
|
||||||
|
if results == nil {
|
||||||
|
results = new([]interface{})
|
||||||
|
}
|
||||||
// Pack the input, call and unpack the results
|
// Pack the input, call and unpack the results
|
||||||
input, err := c.abi.Pack(method, params...)
|
input, err := c.abi.Pack(method, params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
msg = XDPoSChain.CallMsg{From: opts.From, To: &c.address, Data: input, GasPrice: common.MinGasPrice50x, Gas: uint64(4200000)}
|
msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input, GasPrice: common.MinGasPrice50x, Gas: uint64(4200000)}
|
||||||
ctx = ensureContext(opts.Context)
|
ctx = ensureContext(opts.Context)
|
||||||
code []byte
|
code []byte
|
||||||
output []byte
|
output []byte
|
||||||
|
|
@ -147,7 +180,10 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
|
||||||
return ErrNoPendingState
|
return ErrNoPendingState
|
||||||
}
|
}
|
||||||
output, err = pb.PendingCallContract(ctx, msg)
|
output, err = pb.PendingCallContract(ctx, msg)
|
||||||
if err == nil && len(output) == 0 {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(output) == 0 {
|
||||||
// Make sure we have a contract to operate on, and bail out otherwise.
|
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||||
if code, err = pb.PendingCodeAt(ctx, c.address); err != nil {
|
if code, err = pb.PendingCodeAt(ctx, c.address); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -155,21 +191,45 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
|
||||||
return ErrNoCode
|
return ErrNoCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if opts.BlockHash != (common.Hash{}) {
|
||||||
output, err = c.caller.CallContract(ctx, msg, nil)
|
bh, ok := c.caller.(BlockHashContractCaller)
|
||||||
if err == nil && len(output) == 0 {
|
if !ok {
|
||||||
|
return ErrNoBlockHashState
|
||||||
|
}
|
||||||
|
output, err = bh.CallContractAtHash(ctx, msg, opts.BlockHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(output) == 0 {
|
||||||
// Make sure we have a contract to operate on, and bail out otherwise.
|
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||||
if code, err = c.caller.CodeAt(ctx, c.address, nil); err != nil {
|
if code, err = bh.CodeAtHash(ctx, c.address, opts.BlockHash); err != nil {
|
||||||
|
return err
|
||||||
|
} else if len(code) == 0 {
|
||||||
|
return ErrNoCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(output) == 0 {
|
||||||
|
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||||
|
if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if len(code) == 0 {
|
} else if len(code) == 0 {
|
||||||
return ErrNoCode
|
return ErrNoCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
|
if len(*results) == 0 {
|
||||||
|
res, err := c.abi.Unpack(method, output)
|
||||||
|
*results = res
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.abi.Unpack(result, method, output)
|
res := *results
|
||||||
|
return c.abi.UnpackIntoInterface(res[0], method, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transact invokes the (paid) contract method with params as input values.
|
// Transact invokes the (paid) contract method with params as input values.
|
||||||
|
|
@ -179,12 +239,24 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// todo(rjl493456442) check whether the method is payable or not,
|
||||||
|
// reject invalid transaction at the first place
|
||||||
return c.transact(opts, &c.address, input)
|
return c.transact(opts, &c.address, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RawTransact initiates a transaction with the given raw calldata as the input.
|
||||||
|
// It's usually used to initiate transactions for invoking **Fallback** function.
|
||||||
|
func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) {
|
||||||
|
// todo(rjl493456442) check whether the method is payable or not,
|
||||||
|
// reject invalid transaction at the first place
|
||||||
|
return c.transact(opts, &c.address, calldata)
|
||||||
|
}
|
||||||
|
|
||||||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||||
// its default method if one is available.
|
// its default method if one is available.
|
||||||
func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
|
func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
|
||||||
|
// todo(rjl493456442) check the payable fallback or receive is defined
|
||||||
|
// or not, reject invalid transaction at the first place
|
||||||
return c.transact(opts, &c.address, nil)
|
return c.transact(opts, &c.address, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,7 +280,7 @@ func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Add
|
||||||
if gasFeeCap == nil {
|
if gasFeeCap == nil {
|
||||||
gasFeeCap = new(big.Int).Add(
|
gasFeeCap = new(big.Int).Add(
|
||||||
gasTipCap,
|
gasTipCap,
|
||||||
new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
|
new(big.Int).Mul(head.BaseFee, big.NewInt(basefeeWiggleMultiplier)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if gasFeeCap.Cmp(gasTipCap) < 0 {
|
if gasFeeCap.Cmp(gasTipCap) < 0 {
|
||||||
|
|
@ -229,20 +301,21 @@ func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Add
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
baseTx := &types.DynamicFeeTx{
|
baseTx := &types.DynamicFeeTx{
|
||||||
To: contract,
|
To: contract,
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
GasFeeCap: gasFeeCap,
|
GasFeeCap: gasFeeCap,
|
||||||
GasTipCap: gasTipCap,
|
GasTipCap: gasTipCap,
|
||||||
Gas: gasLimit,
|
Gas: gasLimit,
|
||||||
Value: value,
|
Value: value,
|
||||||
Data: input,
|
Data: input,
|
||||||
|
AccessList: opts.AccessList,
|
||||||
}
|
}
|
||||||
return types.NewTx(baseTx), nil
|
return types.NewTx(baseTx), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) {
|
func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) {
|
||||||
if opts.GasFeeCap != nil || opts.GasTipCap != nil {
|
if opts.GasFeeCap != nil || opts.GasTipCap != nil || opts.AccessList != nil {
|
||||||
return nil, errors.New("maxFeePerGas or maxPriorityFeePerGas specified but EIP-1559 is not active yet")
|
return nil, errors.New("maxFeePerGas or maxPriorityFeePerGas or accessList specified but EIP-1559 is not active yet")
|
||||||
}
|
}
|
||||||
// Normalize value
|
// Normalize value
|
||||||
value := opts.Value
|
value := opts.Value
|
||||||
|
|
@ -292,7 +365,7 @@ func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Ad
|
||||||
return 0, ErrNoCode
|
return 0, ErrNoCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
msg := XDPoSChain.CallMsg{
|
msg := ethereum.CallMsg{
|
||||||
From: opts.From,
|
From: opts.From,
|
||||||
To: contract,
|
To: contract,
|
||||||
GasPrice: gasPrice,
|
GasPrice: gasPrice,
|
||||||
|
|
@ -325,6 +398,8 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
|
||||||
)
|
)
|
||||||
if opts.GasPrice != nil {
|
if opts.GasPrice != nil {
|
||||||
rawTx, err = c.createLegacyTx(opts, contract, input)
|
rawTx, err = c.createLegacyTx(opts, contract, input)
|
||||||
|
} else if opts.GasFeeCap != nil && opts.GasTipCap != nil {
|
||||||
|
rawTx, err = c.createDynamicTx(opts, contract, input, nil)
|
||||||
} else {
|
} else {
|
||||||
// Only query for basefee if gasPrice not specified
|
// Only query for basefee if gasPrice not specified
|
||||||
if head, errHead := c.transactor.HeaderByNumber(ensureContext(opts.Context), nil); errHead != nil {
|
if head, errHead := c.transactor.HeaderByNumber(ensureContext(opts.Context), nil); errHead != nil {
|
||||||
|
|
@ -364,16 +439,16 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int
|
||||||
opts = new(FilterOpts)
|
opts = new(FilterOpts)
|
||||||
}
|
}
|
||||||
// Append the event selector to the query parameters and construct the topic set
|
// Append the event selector to the query parameters and construct the topic set
|
||||||
query = append([][]interface{}{{c.abi.Events[name].Id()}}, query...)
|
query = append([][]interface{}{{c.abi.Events[name].ID}}, query...)
|
||||||
|
|
||||||
topics, err := makeTopics(query...)
|
topics, err := abi.MakeTopics(query...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
// Start the background filtering
|
// Start the background filtering
|
||||||
logs := make(chan types.Log, 128)
|
logs := make(chan types.Log, 128)
|
||||||
|
|
||||||
config := XDPoSChain.FilterQuery{
|
config := ethereum.FilterQuery{
|
||||||
Addresses: []common.Address{c.address},
|
Addresses: []common.Address{c.address},
|
||||||
Topics: topics,
|
Topics: topics,
|
||||||
FromBlock: new(big.Int).SetUint64(opts.Start),
|
FromBlock: new(big.Int).SetUint64(opts.Start),
|
||||||
|
|
@ -410,16 +485,16 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter
|
||||||
opts = new(WatchOpts)
|
opts = new(WatchOpts)
|
||||||
}
|
}
|
||||||
// Append the event selector to the query parameters and construct the topic set
|
// Append the event selector to the query parameters and construct the topic set
|
||||||
query = append([][]interface{}{{c.abi.Events[name].Id()}}, query...)
|
query = append([][]interface{}{{c.abi.Events[name].ID}}, query...)
|
||||||
|
|
||||||
topics, err := makeTopics(query...)
|
topics, err := abi.MakeTopics(query...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
// Start the background filtering
|
// Start the background filtering
|
||||||
logs := make(chan types.Log, 128)
|
logs := make(chan types.Log, 128)
|
||||||
|
|
||||||
config := XDPoSChain.FilterQuery{
|
config := ethereum.FilterQuery{
|
||||||
Addresses: []common.Address{c.address},
|
Addresses: []common.Address{c.address},
|
||||||
Topics: topics,
|
Topics: topics,
|
||||||
}
|
}
|
||||||
|
|
@ -439,11 +514,11 @@ func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log)
|
||||||
if len(log.Topics) == 0 {
|
if len(log.Topics) == 0 {
|
||||||
return errNoEventSignature
|
return errNoEventSignature
|
||||||
}
|
}
|
||||||
if log.Topics[0] != c.abi.Events[event].Id() {
|
if log.Topics[0] != c.abi.Events[event].ID {
|
||||||
return errors.New("event signature mismatch")
|
return errEventSignatureMismatch
|
||||||
}
|
}
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
if err := c.abi.Unpack(out, event, log.Data); err != nil {
|
if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -453,14 +528,37 @@ func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log)
|
||||||
indexed = append(indexed, arg)
|
indexed = append(indexed, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return parseTopics(out, indexed, log.Topics[1:])
|
return abi.ParseTopics(out, indexed, log.Topics[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpackLogIntoMap unpacks a retrieved log into the provided map.
|
||||||
|
func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event string, log types.Log) error {
|
||||||
|
// Anonymous events are not supported.
|
||||||
|
if len(log.Topics) == 0 {
|
||||||
|
return errNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != c.abi.Events[event].ID {
|
||||||
|
return errEventSignatureMismatch
|
||||||
|
}
|
||||||
|
if len(log.Data) > 0 {
|
||||||
|
if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var indexed abi.Arguments
|
||||||
|
for _, arg := range c.abi.Events[event].Inputs {
|
||||||
|
if arg.Indexed {
|
||||||
|
indexed = append(indexed, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return abi.ParseTopicsIntoMap(out, indexed, log.Topics[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensureContext is a helper method to ensure a context is not nil, even if the
|
// ensureContext is a helper method to ensure a context is not nil, even if the
|
||||||
// user specified it as such.
|
// user specified it as such.
|
||||||
func ensureContext(ctx context.Context) context.Context {
|
func ensureContext(ctx context.Context) context.Context {
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
return context.TODO()
|
return context.Background()
|
||||||
}
|
}
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,20 @@ package bind_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain"
|
ethereum "github.com/XinFinOrg/XDPoSChain"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -61,7 +67,7 @@ func (mt *mockTransactor) SuggestGasTipCap(ctx context.Context) (*big.Int, error
|
||||||
return mt.gasTipCap, nil
|
return mt.gasTipCap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mt *mockTransactor) EstimateGas(ctx context.Context, call XDPoSChain.CallMsg) (gas uint64, err error) {
|
func (mt *mockTransactor) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,34 +76,71 @@ func (mt *mockTransactor) SendTransaction(ctx context.Context, tx *types.Transac
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockCaller struct {
|
type mockCaller struct {
|
||||||
codeAtBlockNumber *big.Int
|
codeAtBlockNumber *big.Int
|
||||||
callContractBlockNumber *big.Int
|
callContractBlockNumber *big.Int
|
||||||
pendingCodeAtCalled bool
|
callContractBytes []byte
|
||||||
pendingCallContractCalled bool
|
callContractErr error
|
||||||
|
codeAtBytes []byte
|
||||||
|
codeAtErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
|
func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
|
||||||
mc.codeAtBlockNumber = blockNumber
|
mc.codeAtBlockNumber = blockNumber
|
||||||
return []byte{1, 2, 3}, nil
|
return mc.codeAtBytes, mc.codeAtErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *mockCaller) CallContract(ctx context.Context, call XDPoSChain.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
||||||
mc.callContractBlockNumber = blockNumber
|
mc.callContractBlockNumber = blockNumber
|
||||||
return nil, nil
|
return mc.callContractBytes, mc.callContractErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *mockCaller) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
|
type mockPendingCaller struct {
|
||||||
|
*mockCaller
|
||||||
|
pendingCodeAtBytes []byte
|
||||||
|
pendingCodeAtErr error
|
||||||
|
pendingCodeAtCalled bool
|
||||||
|
pendingCallContractCalled bool
|
||||||
|
pendingCallContractBytes []byte
|
||||||
|
pendingCallContractErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockPendingCaller) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
|
||||||
mc.pendingCodeAtCalled = true
|
mc.pendingCodeAtCalled = true
|
||||||
return nil, nil
|
return mc.pendingCodeAtBytes, mc.pendingCodeAtErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *mockCaller) PendingCallContract(ctx context.Context, call XDPoSChain.CallMsg) ([]byte, error) {
|
func (mc *mockPendingCaller) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
|
||||||
mc.pendingCallContractCalled = true
|
mc.pendingCallContractCalled = true
|
||||||
return nil, nil
|
return mc.pendingCallContractBytes, mc.pendingCallContractErr
|
||||||
}
|
}
|
||||||
func TestPassingBlockNumber(t *testing.T) {
|
|
||||||
|
|
||||||
mc := &mockCaller{}
|
type mockBlockHashCaller struct {
|
||||||
|
*mockCaller
|
||||||
|
codeAtHashBytes []byte
|
||||||
|
codeAtHashErr error
|
||||||
|
codeAtHashCalled bool
|
||||||
|
callContractAtHashCalled bool
|
||||||
|
callContractAtHashBytes []byte
|
||||||
|
callContractAtHashErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockBlockHashCaller) CodeAtHash(ctx context.Context, contract common.Address, hash common.Hash) ([]byte, error) {
|
||||||
|
mc.codeAtHashCalled = true
|
||||||
|
return mc.codeAtHashBytes, mc.codeAtHashErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockBlockHashCaller) CallContractAtHash(ctx context.Context, call ethereum.CallMsg, hash common.Hash) ([]byte, error) {
|
||||||
|
mc.callContractAtHashCalled = true
|
||||||
|
return mc.callContractAtHashBytes, mc.callContractAtHashErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassingBlockNumber(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
mc := &mockPendingCaller{
|
||||||
|
mockCaller: &mockCaller{
|
||||||
|
codeAtBytes: []byte{1, 2, 3},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
|
||||||
Methods: map[string]abi.Method{
|
Methods: map[string]abi.Method{
|
||||||
|
|
@ -110,28 +153,172 @@ func TestPassingBlockNumber(t *testing.T) {
|
||||||
|
|
||||||
bc.Call(&bind.CallOpts{}, nil, "something")
|
bc.Call(&bind.CallOpts{}, nil, "something")
|
||||||
|
|
||||||
bc.Call(&bind.CallOpts{}, nil, "something")
|
|
||||||
|
|
||||||
if mc.callContractBlockNumber != nil {
|
if mc.callContractBlockNumber != nil {
|
||||||
t.Fatalf("CallContract() was passed a block number when it should not have been")
|
t.Fatal("CallContract() was passed a block number when it should not have been")
|
||||||
}
|
}
|
||||||
|
|
||||||
if mc.codeAtBlockNumber != nil {
|
if mc.codeAtBlockNumber != nil {
|
||||||
t.Fatalf("CodeAt() was passed a block number when it should not have been")
|
t.Fatal("CodeAt() was passed a block number when it should not have been")
|
||||||
}
|
}
|
||||||
|
|
||||||
bc.Call(&bind.CallOpts{Pending: true}, nil, "something")
|
bc.Call(&bind.CallOpts{Pending: true}, nil, "something")
|
||||||
|
|
||||||
if !mc.pendingCallContractCalled {
|
if !mc.pendingCallContractCalled {
|
||||||
t.Fatalf("CallContract() was not passed the block number")
|
t.Fatal("CallContract() was not passed the block number")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !mc.pendingCodeAtCalled {
|
if !mc.pendingCodeAtCalled {
|
||||||
t.Fatalf("CodeAt() was not passed the block number")
|
t.Fatal("CodeAt() was not passed the block number")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158"
|
||||||
|
|
||||||
|
func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
hash := crypto.Keccak256Hash([]byte("testName"))
|
||||||
|
topics := []common.Hash{
|
||||||
|
crypto.Keccak256Hash([]byte("received(string,address,uint256,bytes)")),
|
||||||
|
hash,
|
||||||
|
}
|
||||||
|
mockLog := newMockLog(topics, common.HexToHash("0x0"))
|
||||||
|
|
||||||
|
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
|
||||||
|
parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
|
||||||
|
|
||||||
|
expectedReceivedMap := map[string]interface{}{
|
||||||
|
"name": hash,
|
||||||
|
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
|
||||||
|
"amount": big.NewInt(1),
|
||||||
|
"memo": []byte{88},
|
||||||
|
}
|
||||||
|
unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpackAnonymousLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
mockLog := newMockLog(nil, common.HexToHash("0x0"))
|
||||||
|
|
||||||
|
abiString := `[{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"received","type":"event"}]`
|
||||||
|
parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
|
||||||
|
|
||||||
|
var received map[string]interface{}
|
||||||
|
err := bc.UnpackLogIntoMap(received, "received", mockLog)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("unpacking anonymous event is not supported")
|
||||||
|
}
|
||||||
|
if err.Error() != "no event signature" {
|
||||||
|
t.Errorf("expected error 'no event signature', got '%s'", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hash := crypto.Keccak256Hash(sliceBytes)
|
||||||
|
topics := []common.Hash{
|
||||||
|
crypto.Keccak256Hash([]byte("received(string[],address,uint256,bytes)")),
|
||||||
|
hash,
|
||||||
|
}
|
||||||
|
mockLog := newMockLog(topics, common.HexToHash("0x0"))
|
||||||
|
|
||||||
|
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"names","type":"string[]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
|
||||||
|
parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
|
||||||
|
|
||||||
|
expectedReceivedMap := map[string]interface{}{
|
||||||
|
"names": hash,
|
||||||
|
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
|
||||||
|
"amount": big.NewInt(1),
|
||||||
|
"memo": []byte{88},
|
||||||
|
}
|
||||||
|
unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hash := crypto.Keccak256Hash(arrBytes)
|
||||||
|
topics := []common.Hash{
|
||||||
|
crypto.Keccak256Hash([]byte("received(address[2],address,uint256,bytes)")),
|
||||||
|
hash,
|
||||||
|
}
|
||||||
|
mockLog := newMockLog(topics, common.HexToHash("0x0"))
|
||||||
|
|
||||||
|
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"addresses","type":"address[2]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
|
||||||
|
parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
|
||||||
|
|
||||||
|
expectedReceivedMap := map[string]interface{}{
|
||||||
|
"addresses": hash,
|
||||||
|
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
|
||||||
|
"amount": big.NewInt(1),
|
||||||
|
"memo": []byte{88},
|
||||||
|
}
|
||||||
|
unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")
|
||||||
|
addrBytes := mockAddress.Bytes()
|
||||||
|
hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)"))
|
||||||
|
functionSelector := hash[:4]
|
||||||
|
functionTyBytes := append(addrBytes, functionSelector...)
|
||||||
|
var functionTy [24]byte
|
||||||
|
copy(functionTy[:], functionTyBytes[0:24])
|
||||||
|
topics := []common.Hash{
|
||||||
|
crypto.Keccak256Hash([]byte("received(function,address,uint256,bytes)")),
|
||||||
|
common.BytesToHash(functionTyBytes),
|
||||||
|
}
|
||||||
|
mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"))
|
||||||
|
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"function","type":"function"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
|
||||||
|
parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
|
||||||
|
|
||||||
|
expectedReceivedMap := map[string]interface{}{
|
||||||
|
"function": functionTy,
|
||||||
|
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
|
||||||
|
"amount": big.NewInt(1),
|
||||||
|
"memo": []byte{88},
|
||||||
|
}
|
||||||
|
unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
bytes := []byte{1, 2, 3, 4, 5}
|
||||||
|
hash := crypto.Keccak256Hash(bytes)
|
||||||
|
topics := []common.Hash{
|
||||||
|
crypto.Keccak256Hash([]byte("received(bytes,address,uint256,bytes)")),
|
||||||
|
hash,
|
||||||
|
}
|
||||||
|
mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"))
|
||||||
|
|
||||||
|
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"content","type":"bytes"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
|
||||||
|
parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
|
||||||
|
|
||||||
|
expectedReceivedMap := map[string]interface{}{
|
||||||
|
"content": hash,
|
||||||
|
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
|
||||||
|
"amount": big.NewInt(1),
|
||||||
|
"memo": []byte{88},
|
||||||
|
}
|
||||||
|
unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestTransactGasFee(t *testing.T) {
|
func TestTransactGasFee(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
// GasTipCap and GasFeeCap
|
// GasTipCap and GasFeeCap
|
||||||
|
|
@ -175,3 +362,217 @@ func TestTransactGasFee(t *testing.T) {
|
||||||
assert.Equal(big.NewInt(6), tx.GasPrice())
|
assert.Equal(big.NewInt(6), tx.GasPrice())
|
||||||
assert.True(mt.suggestGasPriceCalled)
|
assert.True(mt.suggestGasPriceCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unpackAndCheck(t *testing.T, bc *bind.BoundContract, expected map[string]interface{}, mockLog types.Log) {
|
||||||
|
received := make(map[string]interface{})
|
||||||
|
if err := bc.UnpackLogIntoMap(received, "received", mockLog); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(received) != len(expected) {
|
||||||
|
t.Fatalf("unpacked map length %v not equal expected length of %v", len(received), len(expected))
|
||||||
|
}
|
||||||
|
for name, elem := range expected {
|
||||||
|
if !reflect.DeepEqual(elem, received[name]) {
|
||||||
|
t.Errorf("field %v does not match expected, want %v, got %v", name, elem, received[name])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockLog(topics []common.Hash, txHash common.Hash) types.Log {
|
||||||
|
return types.Log{
|
||||||
|
Address: common.HexToAddress("0x0"),
|
||||||
|
Topics: topics,
|
||||||
|
Data: hexutil.MustDecode(hexData),
|
||||||
|
BlockNumber: uint64(26),
|
||||||
|
TxHash: txHash,
|
||||||
|
TxIndex: 111,
|
||||||
|
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
|
||||||
|
Index: 7,
|
||||||
|
Removed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCall(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var method, methodWithArg = "something", "somethingArrrrg"
|
||||||
|
tests := []struct {
|
||||||
|
name, method string
|
||||||
|
opts *bind.CallOpts
|
||||||
|
mc bind.ContractCaller
|
||||||
|
results *[]interface{}
|
||||||
|
wantErr bool
|
||||||
|
wantErrExact error
|
||||||
|
}{{
|
||||||
|
name: "ok not pending",
|
||||||
|
mc: &mockCaller{
|
||||||
|
codeAtBytes: []byte{0},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
}, {
|
||||||
|
name: "ok pending",
|
||||||
|
mc: &mockPendingCaller{
|
||||||
|
pendingCodeAtBytes: []byte{0},
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
Pending: true,
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
}, {
|
||||||
|
name: "ok hash",
|
||||||
|
mc: &mockBlockHashCaller{
|
||||||
|
codeAtHashBytes: []byte{0},
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
}, {
|
||||||
|
name: "pack error, no method",
|
||||||
|
mc: new(mockCaller),
|
||||||
|
method: "else",
|
||||||
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
name: "interface error, pending but not a PendingContractCaller",
|
||||||
|
mc: new(mockCaller),
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
Pending: true,
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoPendingState,
|
||||||
|
}, {
|
||||||
|
name: "interface error, blockHash but not a BlockHashContractCaller",
|
||||||
|
mc: new(mockCaller),
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoBlockHashState,
|
||||||
|
}, {
|
||||||
|
name: "pending call canceled",
|
||||||
|
mc: &mockPendingCaller{
|
||||||
|
pendingCallContractErr: context.DeadlineExceeded,
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
Pending: true,
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: context.DeadlineExceeded,
|
||||||
|
}, {
|
||||||
|
name: "pending code at error",
|
||||||
|
mc: &mockPendingCaller{
|
||||||
|
pendingCodeAtErr: errors.New(""),
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
Pending: true,
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
name: "no pending code at",
|
||||||
|
mc: new(mockPendingCaller),
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
Pending: true,
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoCode,
|
||||||
|
}, {
|
||||||
|
name: "call contract error",
|
||||||
|
mc: &mockCaller{
|
||||||
|
callContractErr: context.DeadlineExceeded,
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: context.DeadlineExceeded,
|
||||||
|
}, {
|
||||||
|
name: "code at error",
|
||||||
|
mc: &mockCaller{
|
||||||
|
codeAtErr: errors.New(""),
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
name: "no code at",
|
||||||
|
mc: new(mockCaller),
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoCode,
|
||||||
|
}, {
|
||||||
|
name: "call contract at hash error",
|
||||||
|
mc: &mockBlockHashCaller{
|
||||||
|
callContractAtHashErr: context.DeadlineExceeded,
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: context.DeadlineExceeded,
|
||||||
|
}, {
|
||||||
|
name: "code at error",
|
||||||
|
mc: &mockBlockHashCaller{
|
||||||
|
codeAtHashErr: errors.New(""),
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
name: "no code at hash",
|
||||||
|
mc: new(mockBlockHashCaller),
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoCode,
|
||||||
|
}, {
|
||||||
|
name: "unpack error missing arg",
|
||||||
|
mc: &mockCaller{
|
||||||
|
codeAtBytes: []byte{0},
|
||||||
|
},
|
||||||
|
method: methodWithArg,
|
||||||
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
name: "interface unpack error",
|
||||||
|
mc: &mockCaller{
|
||||||
|
codeAtBytes: []byte{0},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
results: &[]interface{}{0},
|
||||||
|
wantErr: true,
|
||||||
|
}}
|
||||||
|
for _, test := range tests {
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
|
||||||
|
Methods: map[string]abi.Method{
|
||||||
|
method: {
|
||||||
|
Name: method,
|
||||||
|
Outputs: abi.Arguments{},
|
||||||
|
},
|
||||||
|
methodWithArg: {
|
||||||
|
Name: methodWithArg,
|
||||||
|
Outputs: abi.Arguments{abi.Argument{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, test.mc, nil, nil)
|
||||||
|
err := bc.Call(test.opts, test.results, test.method)
|
||||||
|
if test.wantErr || test.wantErrExact != nil {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("%q expected error", test.name)
|
||||||
|
}
|
||||||
|
if test.wantErrExact != nil && !errors.Is(err, test.wantErrExact) {
|
||||||
|
t.Fatalf("%q expected error %q but got %q", test.name, test.wantErrExact, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%q unexpected error: %v", test.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCrashers contains some strings which previously caused the abi codec to crash.
|
||||||
|
func TestCrashers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"_1"}]}]}]`))
|
||||||
|
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"&"}]}]}]`))
|
||||||
|
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`))
|
||||||
|
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"foo.Bar"}]}]}]`))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,17 +40,58 @@ const (
|
||||||
LangGo Lang = iota
|
LangGo Lang = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func isKeyWord(arg string) bool {
|
||||||
|
switch arg {
|
||||||
|
case "break":
|
||||||
|
case "case":
|
||||||
|
case "chan":
|
||||||
|
case "const":
|
||||||
|
case "continue":
|
||||||
|
case "default":
|
||||||
|
case "defer":
|
||||||
|
case "else":
|
||||||
|
case "fallthrough":
|
||||||
|
case "for":
|
||||||
|
case "func":
|
||||||
|
case "go":
|
||||||
|
case "goto":
|
||||||
|
case "if":
|
||||||
|
case "import":
|
||||||
|
case "interface":
|
||||||
|
case "iota":
|
||||||
|
case "map":
|
||||||
|
case "make":
|
||||||
|
case "new":
|
||||||
|
case "package":
|
||||||
|
case "range":
|
||||||
|
case "return":
|
||||||
|
case "select":
|
||||||
|
case "struct":
|
||||||
|
case "switch":
|
||||||
|
case "type":
|
||||||
|
case "var":
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
|
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
|
||||||
// to be used as is in client code, but rather as an intermediate struct which
|
// to be used as is in client code, but rather as an intermediate struct which
|
||||||
// enforces compile time type safety and naming convention opposed to having to
|
// enforces compile time type safety and naming convention as opposed to having to
|
||||||
// manually maintain hard coded strings that break on runtime.
|
// manually maintain hard coded strings that break on runtime.
|
||||||
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string) (string, error) {
|
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) {
|
||||||
// Process each individual contract requested binding
|
var (
|
||||||
contracts := make(map[string]*tmplContract)
|
// contracts is the map of each individual contract requested binding
|
||||||
|
contracts = make(map[string]*tmplContract)
|
||||||
|
|
||||||
// Map used to flag each encountered library as such
|
// structs is the map of all redeclared structs shared by passed contracts.
|
||||||
isLib := make(map[string]struct{})
|
structs = make(map[string]*tmplStruct)
|
||||||
|
|
||||||
|
// isLib is the map used to flag each encountered library as such
|
||||||
|
isLib = make(map[string]struct{})
|
||||||
|
)
|
||||||
for i := 0; i < len(types); i++ {
|
for i := 0; i < len(types); i++ {
|
||||||
// Parse the actual ABI to generate the binding for
|
// Parse the actual ABI to generate the binding for
|
||||||
evmABI, err := abi.JSON(strings.NewReader(abis[i]))
|
evmABI, err := abi.JSON(strings.NewReader(abis[i]))
|
||||||
|
|
@ -65,23 +106,61 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
return r
|
return r
|
||||||
}, abis[i])
|
}, abis[i])
|
||||||
|
|
||||||
// Extract the call and transact methods; events; and sort them alphabetically
|
// Extract the call and transact methods; events, struct definitions; and sort them alphabetically
|
||||||
var (
|
var (
|
||||||
calls = make(map[string]*tmplMethod)
|
calls = make(map[string]*tmplMethod)
|
||||||
transacts = make(map[string]*tmplMethod)
|
transacts = make(map[string]*tmplMethod)
|
||||||
events = make(map[string]*tmplEvent)
|
events = make(map[string]*tmplEvent)
|
||||||
|
fallback *tmplMethod
|
||||||
|
receive *tmplMethod
|
||||||
|
|
||||||
|
// identifiers are used to detect duplicated identifiers of functions
|
||||||
|
// and events. For all calls, transacts and events, abigen will generate
|
||||||
|
// corresponding bindings. However we have to ensure there is no
|
||||||
|
// identifier collisions in the bindings of these categories.
|
||||||
|
callIdentifiers = make(map[string]bool)
|
||||||
|
transactIdentifiers = make(map[string]bool)
|
||||||
|
eventIdentifiers = make(map[string]bool)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for _, input := range evmABI.Constructor.Inputs {
|
||||||
|
if hasStruct(input.Type) {
|
||||||
|
bindStructType[lang](input.Type, structs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, original := range evmABI.Methods {
|
for _, original := range evmABI.Methods {
|
||||||
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
||||||
normalized := original
|
normalized := original
|
||||||
normalized.Name = methodNormalizer[lang](original.Name)
|
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
|
||||||
|
// Ensure there is no duplicated identifier
|
||||||
|
var identifiers = callIdentifiers
|
||||||
|
if !original.IsConstant() {
|
||||||
|
identifiers = transactIdentifiers
|
||||||
|
}
|
||||||
|
// Name shouldn't start with a digit. It will make the generated code invalid.
|
||||||
|
if len(normalizedName) > 0 && unicode.IsDigit(rune(normalizedName[0])) {
|
||||||
|
normalizedName = fmt.Sprintf("M%s", normalizedName)
|
||||||
|
normalizedName = abi.ResolveNameConflict(normalizedName, func(name string) bool {
|
||||||
|
_, ok := identifiers[name]
|
||||||
|
return ok
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if identifiers[normalizedName] {
|
||||||
|
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
|
||||||
|
}
|
||||||
|
identifiers[normalizedName] = true
|
||||||
|
|
||||||
|
normalized.Name = normalizedName
|
||||||
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
|
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
|
||||||
copy(normalized.Inputs, original.Inputs)
|
copy(normalized.Inputs, original.Inputs)
|
||||||
for j, input := range normalized.Inputs {
|
for j, input := range normalized.Inputs {
|
||||||
if input.Name == "" {
|
if input.Name == "" || isKeyWord(input.Name) {
|
||||||
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
||||||
}
|
}
|
||||||
|
if hasStruct(input.Type) {
|
||||||
|
bindStructType[lang](input.Type, structs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
normalized.Outputs = make([]abi.Argument, len(original.Outputs))
|
normalized.Outputs = make([]abi.Argument, len(original.Outputs))
|
||||||
copy(normalized.Outputs, original.Outputs)
|
copy(normalized.Outputs, original.Outputs)
|
||||||
|
|
@ -89,9 +168,12 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
if output.Name != "" {
|
if output.Name != "" {
|
||||||
normalized.Outputs[j].Name = capitalise(output.Name)
|
normalized.Outputs[j].Name = capitalise(output.Name)
|
||||||
}
|
}
|
||||||
|
if hasStruct(output.Type) {
|
||||||
|
bindStructType[lang](output.Type, structs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Append the methods to the call or transact lists
|
// Append the methods to the call or transact lists
|
||||||
if original.Const {
|
if original.IsConstant() {
|
||||||
calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
|
calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
|
||||||
} else {
|
} else {
|
||||||
transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
|
transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
|
||||||
|
|
@ -104,21 +186,53 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
}
|
}
|
||||||
// Normalize the event for capital cases and non-anonymous outputs
|
// Normalize the event for capital cases and non-anonymous outputs
|
||||||
normalized := original
|
normalized := original
|
||||||
normalized.Name = methodNormalizer[lang](original.Name)
|
|
||||||
|
|
||||||
|
// Ensure there is no duplicated identifier
|
||||||
|
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
|
||||||
|
// Name shouldn't start with a digit. It will make the generated code invalid.
|
||||||
|
if len(normalizedName) > 0 && unicode.IsDigit(rune(normalizedName[0])) {
|
||||||
|
normalizedName = fmt.Sprintf("E%s", normalizedName)
|
||||||
|
normalizedName = abi.ResolveNameConflict(normalizedName, func(name string) bool {
|
||||||
|
_, ok := eventIdentifiers[name]
|
||||||
|
return ok
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if eventIdentifiers[normalizedName] {
|
||||||
|
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
|
||||||
|
}
|
||||||
|
eventIdentifiers[normalizedName] = true
|
||||||
|
normalized.Name = normalizedName
|
||||||
|
|
||||||
|
used := make(map[string]bool)
|
||||||
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
|
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
|
||||||
copy(normalized.Inputs, original.Inputs)
|
copy(normalized.Inputs, original.Inputs)
|
||||||
for j, input := range normalized.Inputs {
|
for j, input := range normalized.Inputs {
|
||||||
// Indexed fields are input, non-indexed ones are outputs
|
if input.Name == "" || isKeyWord(input.Name) {
|
||||||
if input.Indexed {
|
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
||||||
if input.Name == "" {
|
}
|
||||||
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
// Event is a bit special, we need to define event struct in binding,
|
||||||
|
// ensure there is no camel-case-style name conflict.
|
||||||
|
for index := 0; ; index++ {
|
||||||
|
if !used[capitalise(normalized.Inputs[j].Name)] {
|
||||||
|
used[capitalise(normalized.Inputs[j].Name)] = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index)
|
||||||
|
}
|
||||||
|
if hasStruct(input.Type) {
|
||||||
|
bindStructType[lang](input.Type, structs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Append the event to the accumulator list
|
// Append the event to the accumulator list
|
||||||
events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
|
events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
|
||||||
}
|
}
|
||||||
|
// Add two special fallback functions if they exist
|
||||||
|
if evmABI.HasFallback() {
|
||||||
|
fallback = &tmplMethod{Original: evmABI.Fallback}
|
||||||
|
}
|
||||||
|
if evmABI.HasReceive() {
|
||||||
|
receive = &tmplMethod{Original: evmABI.Receive}
|
||||||
|
}
|
||||||
|
|
||||||
contracts[types[i]] = &tmplContract{
|
contracts[types[i]] = &tmplContract{
|
||||||
Type: capitalise(types[i]),
|
Type: capitalise(types[i]),
|
||||||
|
|
@ -127,6 +241,8 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
Constructor: evmABI.Constructor,
|
Constructor: evmABI.Constructor,
|
||||||
Calls: calls,
|
Calls: calls,
|
||||||
Transacts: transacts,
|
Transacts: transacts,
|
||||||
|
Fallback: fallback,
|
||||||
|
Receive: receive,
|
||||||
Events: events,
|
Events: events,
|
||||||
Libraries: make(map[string]string),
|
Libraries: make(map[string]string),
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +253,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
}
|
}
|
||||||
// Parse library references.
|
// Parse library references.
|
||||||
for pattern, name := range libs {
|
for pattern, name := range libs {
|
||||||
matched, err := regexp.Match("__\\$"+pattern+"\\$__", []byte(contracts[types[i]].InputBin))
|
matched, err := regexp.MatchString("__\\$"+pattern+"\\$__", contracts[types[i]].InputBin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Could not search for pattern", "pattern", pattern, "contract", contracts[types[i]], "err", err)
|
log.Error("Could not search for pattern", "pattern", pattern, "contract", contracts[types[i]], "err", err)
|
||||||
}
|
}
|
||||||
|
|
@ -160,6 +276,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
Package: pkg,
|
Package: pkg,
|
||||||
Contracts: contracts,
|
Contracts: contracts,
|
||||||
Libraries: libs,
|
Libraries: libs,
|
||||||
|
Structs: structs,
|
||||||
}
|
}
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
|
|
||||||
|
|
@ -188,11 +305,11 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
|
|
||||||
// bindType is a set of type binders that convert Solidity types to some supported
|
// bindType is a set of type binders that convert Solidity types to some supported
|
||||||
// programming language types.
|
// programming language types.
|
||||||
var bindType = map[Lang]func(kind abi.Type) string{
|
var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
|
||||||
LangGo: bindTypeGo,
|
LangGo: bindTypeGo,
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go one.
|
// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones.
|
||||||
func bindBasicTypeGo(kind abi.Type) string {
|
func bindBasicTypeGo(kind abi.Type) string {
|
||||||
switch kind.T {
|
switch kind.T {
|
||||||
case abi.AddressTy:
|
case abi.AddressTy:
|
||||||
|
|
@ -219,13 +336,14 @@ func bindBasicTypeGo(kind abi.Type) string {
|
||||||
// bindTypeGo converts solidity types to Go ones. Since there is no clear mapping
|
// bindTypeGo converts solidity types to Go ones. Since there is no clear mapping
|
||||||
// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
|
// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
|
||||||
// mapped will use an upscaled type (e.g. BigDecimal).
|
// mapped will use an upscaled type (e.g. BigDecimal).
|
||||||
func bindTypeGo(kind abi.Type) string {
|
func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
// todo(rjl493456442) tuple
|
|
||||||
switch kind.T {
|
switch kind.T {
|
||||||
|
case abi.TupleTy:
|
||||||
|
return structs[kind.TupleRawName+kind.String()].Name
|
||||||
case abi.ArrayTy:
|
case abi.ArrayTy:
|
||||||
return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem)
|
return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs)
|
||||||
case abi.SliceTy:
|
case abi.SliceTy:
|
||||||
return "[]" + bindTypeGo(*kind.Elem)
|
return "[]" + bindTypeGo(*kind.Elem, structs)
|
||||||
default:
|
default:
|
||||||
return bindBasicTypeGo(kind)
|
return bindBasicTypeGo(kind)
|
||||||
}
|
}
|
||||||
|
|
@ -233,36 +351,102 @@ func bindTypeGo(kind abi.Type) string {
|
||||||
|
|
||||||
// bindTopicType is a set of type binders that convert Solidity types to some
|
// bindTopicType is a set of type binders that convert Solidity types to some
|
||||||
// supported programming language topic types.
|
// supported programming language topic types.
|
||||||
var bindTopicType = map[Lang]func(kind abi.Type) string{
|
var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
|
||||||
LangGo: bindTopicTypeGo,
|
LangGo: bindTopicTypeGo,
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindTypeGo converts a Solidity topic type to a Go one. It is almost the same
|
// bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same
|
||||||
// funcionality as for simple types, but dynamic types get converted to hashes.
|
// functionality as for simple types, but dynamic types get converted to hashes.
|
||||||
func bindTopicTypeGo(kind abi.Type) string {
|
func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
bound := bindTypeGo(kind)
|
bound := bindTypeGo(kind, structs)
|
||||||
|
|
||||||
|
// todo(rjl493456442) according solidity documentation, indexed event
|
||||||
|
// parameters that are not value types i.e. arrays and structs are not
|
||||||
|
// stored directly but instead a keccak256-hash of an encoding is stored.
|
||||||
|
//
|
||||||
|
// We only convert strings and bytes to hash, still need to deal with
|
||||||
|
// array(both fixed-size and dynamic-size) and struct.
|
||||||
if bound == "string" || bound == "[]byte" {
|
if bound == "string" || bound == "[]byte" {
|
||||||
bound = "common.Hash"
|
bound = "common.Hash"
|
||||||
}
|
}
|
||||||
return bound
|
return bound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bindStructType is a set of type binders that convert Solidity tuple types to some supported
|
||||||
|
// programming language struct definition.
|
||||||
|
var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
|
||||||
|
LangGo: bindStructTypeGo,
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping
|
||||||
|
// in the given map.
|
||||||
|
// Notably, this function will resolve and record nested struct recursively.
|
||||||
|
func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
|
switch kind.T {
|
||||||
|
case abi.TupleTy:
|
||||||
|
// We compose a raw struct name and a canonical parameter expression
|
||||||
|
// together here. The reason is before solidity v0.5.11, kind.TupleRawName
|
||||||
|
// is empty, so we use canonical parameter expression to distinguish
|
||||||
|
// different struct definition. From the consideration of backward
|
||||||
|
// compatibility, we concat these two together so that if kind.TupleRawName
|
||||||
|
// is not empty, it can have unique id.
|
||||||
|
id := kind.TupleRawName + kind.String()
|
||||||
|
if s, exist := structs[id]; exist {
|
||||||
|
return s.Name
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
names = make(map[string]bool)
|
||||||
|
fields []*tmplField
|
||||||
|
)
|
||||||
|
for i, elem := range kind.TupleElems {
|
||||||
|
name := capitalise(kind.TupleRawNames[i])
|
||||||
|
name = abi.ResolveNameConflict(name, func(s string) bool { return names[s] })
|
||||||
|
names[name] = true
|
||||||
|
fields = append(fields, &tmplField{Type: bindStructTypeGo(*elem, structs), Name: name, SolKind: *elem})
|
||||||
|
}
|
||||||
|
name := kind.TupleRawName
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("Struct%d", len(structs))
|
||||||
|
}
|
||||||
|
name = capitalise(name)
|
||||||
|
|
||||||
|
structs[id] = &tmplStruct{
|
||||||
|
Name: name,
|
||||||
|
Fields: fields,
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
case abi.ArrayTy:
|
||||||
|
return fmt.Sprintf("[%d]", kind.Size) + bindStructTypeGo(*kind.Elem, structs)
|
||||||
|
case abi.SliceTy:
|
||||||
|
return "[]" + bindStructTypeGo(*kind.Elem, structs)
|
||||||
|
default:
|
||||||
|
return bindBasicTypeGo(kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// namedType is a set of functions that transform language specific types to
|
// namedType is a set of functions that transform language specific types to
|
||||||
// named versions that my be used inside method names.
|
// named versions that may be used inside method names.
|
||||||
var namedType = map[Lang]func(string, abi.Type) string{
|
var namedType = map[Lang]func(string, abi.Type) string{
|
||||||
LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") },
|
LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// alias returns an alias of the given string based on the aliasing rules
|
||||||
|
// or returns itself if no rule is matched.
|
||||||
|
func alias(aliases map[string]string, n string) string {
|
||||||
|
if alias, exist := aliases[n]; exist {
|
||||||
|
return alias
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
// methodNormalizer is a name transformer that modifies Solidity method names to
|
// methodNormalizer is a name transformer that modifies Solidity method names to
|
||||||
// conform to target language naming concentions.
|
// conform to target language naming conventions.
|
||||||
var methodNormalizer = map[Lang]func(string) string{
|
var methodNormalizer = map[Lang]func(string) string{
|
||||||
LangGo: abi.ToCamelCase,
|
LangGo: abi.ToCamelCase,
|
||||||
}
|
}
|
||||||
|
|
||||||
// capitalise makes a camel-case string which starts with an upper case character.
|
// capitalise makes a camel-case string which starts with an upper case character.
|
||||||
func capitalise(input string) string {
|
var capitalise = abi.ToCamelCase
|
||||||
return abi.ToCamelCase(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
// decapitalise makes a camel-case string which starts with a lower case character.
|
// decapitalise makes a camel-case string which starts with a lower case character.
|
||||||
func decapitalise(input string) string {
|
func decapitalise(input string) string {
|
||||||
|
|
@ -296,3 +480,18 @@ func structured(args abi.Arguments) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasStruct returns an indicator whether the given type is struct, struct slice
|
||||||
|
// or struct array.
|
||||||
|
func hasStruct(t abi.Type) bool {
|
||||||
|
switch t.T {
|
||||||
|
case abi.SliceTy:
|
||||||
|
return hasStruct(*t.Elem)
|
||||||
|
case abi.ArrayTy:
|
||||||
|
return hasStruct(*t.Elem)
|
||||||
|
case abi.TupleTy:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
486
accounts/abi/bind/source.go.tpl
Normal file
486
accounts/abi/bind/source.go.tpl
Normal file
|
|
@ -0,0 +1,486 @@
|
||||||
|
// Code generated - DO NOT EDIT.
|
||||||
|
// This file is a generated binding and any manual changes will be lost.
|
||||||
|
|
||||||
|
package {{.Package}}
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
ethereum "github.com/XinFinOrg/XDPoSChain"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var (
|
||||||
|
_ = errors.New
|
||||||
|
_ = big.NewInt
|
||||||
|
_ = strings.NewReader
|
||||||
|
_ = ethereum.ErrNotFound
|
||||||
|
_ = bind.Bind
|
||||||
|
_ = common.Big1
|
||||||
|
_ = types.BloomLookup
|
||||||
|
_ = event.NewSubscription
|
||||||
|
)
|
||||||
|
|
||||||
|
{{$structs := .Structs}}
|
||||||
|
{{range $structs}}
|
||||||
|
// {{.Name}} is an auto generated low-level Go binding around an user-defined struct.
|
||||||
|
type {{.Name}} struct {
|
||||||
|
{{range $field := .Fields}}
|
||||||
|
{{$field.Name}} {{$field.Type}}{{end}}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range $contract := .Contracts}}
|
||||||
|
// {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract.
|
||||||
|
var {{.Type}}MetaData = &bind.MetaData{
|
||||||
|
ABI: "{{.InputABI}}",
|
||||||
|
{{if $contract.FuncSigs -}}
|
||||||
|
Sigs: map[string]string{
|
||||||
|
{{range $strsig, $binsig := .FuncSigs}}"{{$binsig}}": "{{$strsig}}",
|
||||||
|
{{end}}
|
||||||
|
},
|
||||||
|
{{end -}}
|
||||||
|
{{if .InputBin -}}
|
||||||
|
Bin: "0x{{.InputBin}}",
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
// {{.Type}}ABI is the input ABI used to generate the binding from.
|
||||||
|
// Deprecated: Use {{.Type}}MetaData.ABI instead.
|
||||||
|
var {{.Type}}ABI = {{.Type}}MetaData.ABI
|
||||||
|
|
||||||
|
{{if $contract.FuncSigs}}
|
||||||
|
// Deprecated: Use {{.Type}}MetaData.Sigs instead.
|
||||||
|
// {{.Type}}FuncSigs maps the 4-byte function signature to its string representation.
|
||||||
|
var {{.Type}}FuncSigs = {{.Type}}MetaData.Sigs
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .InputBin}}
|
||||||
|
// {{.Type}}Bin is the compiled bytecode used for deploying new contracts.
|
||||||
|
// Deprecated: Use {{.Type}}MetaData.Bin instead.
|
||||||
|
var {{.Type}}Bin = {{.Type}}MetaData.Bin
|
||||||
|
|
||||||
|
// Deploy{{.Type}} deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
|
||||||
|
func Deploy{{.Type}}(auth *bind.TransactOpts, backend bind.ContractBackend {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type $structs}}{{end}}) (common.Address, *types.Transaction, *{{.Type}}, error) {
|
||||||
|
parsed, err := {{.Type}}MetaData.GetAbi()
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, nil, err
|
||||||
|
}
|
||||||
|
if parsed == nil {
|
||||||
|
return common.Address{}, nil, nil, errors.New("GetABI returned nil")
|
||||||
|
}
|
||||||
|
{{range $pattern, $name := .Libraries}}
|
||||||
|
{{decapitalise $name}}Addr, _, _, _ := Deploy{{capitalise $name}}(auth, backend)
|
||||||
|
{{$contract.Type}}Bin = strings.ReplaceAll({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:])
|
||||||
|
{{end}}
|
||||||
|
address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}})
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, nil, err
|
||||||
|
}
|
||||||
|
return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}} struct {
|
||||||
|
{{.Type}}Caller // Read-only binding to the contract
|
||||||
|
{{.Type}}Transactor // Write-only binding to the contract
|
||||||
|
{{.Type}}Filterer // Log filterer for contract events
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}Caller struct {
|
||||||
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Transactor is an auto generated write-only Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}Transactor struct {
|
||||||
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Filterer is an auto generated log filtering Go binding around an Ethereum contract events.
|
||||||
|
type {{.Type}}Filterer struct {
|
||||||
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Session is an auto generated Go binding around an Ethereum contract,
|
||||||
|
// with pre-set call and transact options.
|
||||||
|
type {{.Type}}Session struct {
|
||||||
|
Contract *{{.Type}} // Generic contract binding to set the session for
|
||||||
|
CallOpts bind.CallOpts // Call options to use throughout this session
|
||||||
|
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}CallerSession is an auto generated read-only Go binding around an Ethereum contract,
|
||||||
|
// with pre-set call options.
|
||||||
|
type {{.Type}}CallerSession struct {
|
||||||
|
Contract *{{.Type}}Caller // Generic contract caller binding to set the session for
|
||||||
|
CallOpts bind.CallOpts // Call options to use throughout this session
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}TransactorSession is an auto generated write-only Go binding around an Ethereum contract,
|
||||||
|
// with pre-set transact options.
|
||||||
|
type {{.Type}}TransactorSession struct {
|
||||||
|
Contract *{{.Type}}Transactor // Generic contract transactor binding to set the session for
|
||||||
|
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Raw is an auto generated low-level Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}Raw struct {
|
||||||
|
Contract *{{.Type}} // Generic contract binding to access the raw methods on
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}CallerRaw struct {
|
||||||
|
Contract *{{.Type}}Caller // Generic read-only contract binding to access the raw methods on
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}TransactorRaw struct {
|
||||||
|
Contract *{{.Type}}Transactor // Generic write-only contract binding to access the raw methods on
|
||||||
|
}
|
||||||
|
|
||||||
|
// New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) {
|
||||||
|
contract, err := bind{{.Type}}(address, backend, backend, backend)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) {
|
||||||
|
contract, err := bind{{.Type}}(address, caller, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{.Type}}Caller{contract: contract}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) {
|
||||||
|
contract, err := bind{{.Type}}(address, nil, transactor, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{.Type}}Transactor{contract: contract}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New{{.Type}}Filterer creates a new log filterer instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
func New{{.Type}}Filterer(address common.Address, filterer bind.ContractFilterer) (*{{.Type}}Filterer, error) {
|
||||||
|
contract, err := bind{{.Type}}(address, nil, nil, filterer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{.Type}}Filterer{contract: contract}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind{{.Type}} binds a generic wrapper to an already deployed contract.
|
||||||
|
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
|
||||||
|
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call invokes the (constant) contract method with params as input values and
|
||||||
|
// sets the output to result. The result type might be a single field for simple
|
||||||
|
// returns, a slice of interfaces for anonymous returns and a struct for named
|
||||||
|
// returns.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
|
||||||
|
return _{{$contract.Type}}.Contract.{{$contract.Type}}Caller.contract.Call(opts, result, method, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||||
|
// its default method if one is available.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transfer(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transact invokes the (paid) contract method with params as input values.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transact(opts, method, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call invokes the (constant) contract method with params as input values and
|
||||||
|
// sets the output to result. The result type might be a single field for simple
|
||||||
|
// returns, a slice of interfaces for anonymous returns and a struct for named
|
||||||
|
// returns.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
|
||||||
|
return _{{$contract.Type}}.Contract.contract.Call(opts, result, method, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||||
|
// its default method if one is available.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.contract.Transfer(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transact invokes the (paid) contract method with params as input values.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.contract.Transact(opts, method, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
{{range .Calls}}
|
||||||
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) {
|
||||||
|
var out []interface{}
|
||||||
|
err := _{{$contract.Type}}.contract.Call(opts, &out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
{{if .Structured}}
|
||||||
|
outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} })
|
||||||
|
if err != nil {
|
||||||
|
return *outstruct, err
|
||||||
|
}
|
||||||
|
{{range $i, $t := .Normalized.Outputs}}
|
||||||
|
outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}}
|
||||||
|
|
||||||
|
return *outstruct, err
|
||||||
|
{{else}}
|
||||||
|
if err != nil {
|
||||||
|
return {{range $i, $_ := .Normalized.Outputs}}*new({{bindtype .Type $structs}}), {{end}} err
|
||||||
|
}
|
||||||
|
{{range $i, $t := .Normalized.Outputs}}
|
||||||
|
out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}}
|
||||||
|
|
||||||
|
return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range .Transacts}}
|
||||||
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.contract.Transact(opts, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .Fallback}}
|
||||||
|
// Fallback is a paid mutator transaction binding the contract fallback function.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Fallback.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.contract.RawTransact(opts, calldata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback is a paid mutator transaction binding the contract fallback function.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Fallback.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Session) Fallback(calldata []byte) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback is a paid mutator transaction binding the contract fallback function.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Fallback.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Fallback(calldata []byte) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata)
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .Receive}}
|
||||||
|
// Receive is a paid mutator transaction binding the contract receive function.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Receive.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.contract.RawTransact(opts, nil) // calldata is disallowed for receive function
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive is a paid mutator transaction binding the contract receive function.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Receive.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Session) Receive() (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive is a paid mutator transaction binding the contract receive function.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Receive.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Receive() (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts)
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range .Events}}
|
||||||
|
// {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract.
|
||||||
|
type {{$contract.Type}}{{.Normalized.Name}}Iterator struct {
|
||||||
|
Event *{{$contract.Type}}{{.Normalized.Name}} // Event containing the contract specifics and raw log
|
||||||
|
|
||||||
|
contract *bind.BoundContract // Generic contract to use for unpacking event data
|
||||||
|
event string // Event name to use for unpacking event data
|
||||||
|
|
||||||
|
logs chan types.Log // Log channel receiving the found contract events
|
||||||
|
sub ethereum.Subscription // Subscription for errors, completion and termination
|
||||||
|
done bool // Whether the subscription completed delivering logs
|
||||||
|
fail error // Occurred error to stop iteration
|
||||||
|
}
|
||||||
|
// Next advances the iterator to the subsequent event, returning whether there
|
||||||
|
// are any more events found. In case of a retrieval or parsing error, false is
|
||||||
|
// returned and Error() can be queried for the exact failure.
|
||||||
|
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Next() bool {
|
||||||
|
// If the iterator failed, stop iterating
|
||||||
|
if (it.fail != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// If the iterator completed, deliver directly whatever's available
|
||||||
|
if (it.done) {
|
||||||
|
select {
|
||||||
|
case log := <-it.logs:
|
||||||
|
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
|
||||||
|
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
||||||
|
it.fail = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
it.Event.Raw = log
|
||||||
|
return true
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Iterator still in progress, wait for either a data or an error event
|
||||||
|
select {
|
||||||
|
case log := <-it.logs:
|
||||||
|
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
|
||||||
|
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
||||||
|
it.fail = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
it.Event.Raw = log
|
||||||
|
return true
|
||||||
|
|
||||||
|
case err := <-it.sub.Err():
|
||||||
|
it.done = true
|
||||||
|
it.fail = err
|
||||||
|
return it.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Error returns any retrieval or parsing error occurred during filtering.
|
||||||
|
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Error() error {
|
||||||
|
return it.fail
|
||||||
|
}
|
||||||
|
// Close terminates the iteration process, releasing any pending underlying
|
||||||
|
// resources.
|
||||||
|
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Close() error {
|
||||||
|
it.sub.Unsubscribe()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract.
|
||||||
|
type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}}
|
||||||
|
{{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type $structs}}{{else}}{{bindtype .Type $structs}}{{end}}; {{end}}
|
||||||
|
Raw types.Log // Blockchain specific contextual infos
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) {
|
||||||
|
{{range .Normalized.Inputs}}
|
||||||
|
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
||||||
|
for _, {{.Name}}Item := range {{.Name}} {
|
||||||
|
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
||||||
|
}{{end}}{{end}}
|
||||||
|
|
||||||
|
logs, sub, err := _{{$contract.Type}}.contract.FilterLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{$contract.Type}}{{.Normalized.Name}}Iterator{contract: _{{$contract.Type}}.contract, event: "{{.Original.Name}}", logs: logs, sub: sub}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (event.Subscription, error) {
|
||||||
|
{{range .Normalized.Inputs}}
|
||||||
|
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
||||||
|
for _, {{.Name}}Item := range {{.Name}} {
|
||||||
|
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
||||||
|
}{{end}}{{end}}
|
||||||
|
|
||||||
|
logs, sub, err := _{{$contract.Type}}.contract.WatchLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case log := <-logs:
|
||||||
|
// New log arrived, parse the event and forward to the user
|
||||||
|
event := new({{$contract.Type}}{{.Normalized.Name}})
|
||||||
|
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
event.Raw = log
|
||||||
|
|
||||||
|
select {
|
||||||
|
case sink <- event:
|
||||||
|
case err := <-sub.Err():
|
||||||
|
return err
|
||||||
|
case <-quit:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case err := <-sub.Err():
|
||||||
|
return err
|
||||||
|
case <-quit:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
|
||||||
|
event := new({{$contract.Type}}{{.Normalized.Name}})
|
||||||
|
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
event.Raw = log
|
||||||
|
return event, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
@ -16,27 +16,34 @@
|
||||||
|
|
||||||
package bind
|
package bind
|
||||||
|
|
||||||
import "github.com/XinFinOrg/XDPoSChain/accounts/abi"
|
import (
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
|
||||||
|
)
|
||||||
|
|
||||||
// tmplData is the data structure required to fill the binding template.
|
// tmplData is the data structure required to fill the binding template.
|
||||||
type tmplData struct {
|
type tmplData struct {
|
||||||
Package string // Name of the package to place the generated file in
|
Package string // Name of the package to place the generated file in
|
||||||
Contracts map[string]*tmplContract // List of contracts to generate into this file
|
Contracts map[string]*tmplContract // List of contracts to generate into this file
|
||||||
Libraries map[string]string // Map the bytecode's link pattern to the library name
|
Libraries map[string]string // Map the bytecode's link pattern to the library name
|
||||||
|
Structs map[string]*tmplStruct // Contract struct type definitions
|
||||||
}
|
}
|
||||||
|
|
||||||
// tmplContract contains the data needed to generate an individual contract binding.
|
// tmplContract contains the data needed to generate an individual contract binding.
|
||||||
type tmplContract struct {
|
type tmplContract struct {
|
||||||
Type string // Type name of the main contract binding
|
Type string // Type name of the main contract binding
|
||||||
InputABI string // JSON ABI used as the input to generate the binding from
|
InputABI string // JSON ABI used as the input to generate the binding from
|
||||||
InputBin string // Optional EVM bytecode used to denetare deploy code from
|
InputBin string // Optional EVM bytecode used to generate deploy code from
|
||||||
FuncSigs map[string]string // Optional map: string signature -> 4-byte signature
|
FuncSigs map[string]string // Optional map: string signature -> 4-byte signature
|
||||||
Constructor abi.Method // Contract constructor for deploy parametrization
|
Constructor abi.Method // Contract constructor for deploy parametrization
|
||||||
Calls map[string]*tmplMethod // Contract calls that only read state data
|
Calls map[string]*tmplMethod // Contract calls that only read state data
|
||||||
Transacts map[string]*tmplMethod // Contract calls that write state data
|
Transacts map[string]*tmplMethod // Contract calls that write state data
|
||||||
|
Fallback *tmplMethod // Additional special fallback function
|
||||||
|
Receive *tmplMethod // Additional special receive function
|
||||||
Events map[string]*tmplEvent // Contract events accessors
|
Events map[string]*tmplEvent // Contract events accessors
|
||||||
Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs
|
Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs
|
||||||
Library bool
|
Library bool // Indicator whether the contract is a library
|
||||||
}
|
}
|
||||||
|
|
||||||
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
|
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
|
||||||
|
|
@ -47,412 +54,36 @@ type tmplMethod struct {
|
||||||
Structured bool // Whether the returns should be accumulated into a struct
|
Structured bool // Whether the returns should be accumulated into a struct
|
||||||
}
|
}
|
||||||
|
|
||||||
// tmplEvent is a wrapper around an a
|
// tmplEvent is a wrapper around an abi.Event that contains a few preprocessed
|
||||||
|
// and cached data fields.
|
||||||
type tmplEvent struct {
|
type tmplEvent struct {
|
||||||
Original abi.Event // Original event as parsed by the abi package
|
Original abi.Event // Original event as parsed by the abi package
|
||||||
Normalized abi.Event // Normalized version of the parsed fields
|
Normalized abi.Event // Normalized version of the parsed fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tmplField is a wrapper around a struct field with binding language
|
||||||
|
// struct type definition and relative filed name.
|
||||||
|
type tmplField struct {
|
||||||
|
Type string // Field type representation depends on target binding language
|
||||||
|
Name string // Field name converted from the raw user-defined field name
|
||||||
|
SolKind abi.Type // Raw abi type information
|
||||||
|
}
|
||||||
|
|
||||||
|
// tmplStruct is a wrapper around an abi.tuple and contains an auto-generated
|
||||||
|
// struct name.
|
||||||
|
type tmplStruct struct {
|
||||||
|
Name string // Auto-generated struct name(before solidity v0.5.11) or raw name.
|
||||||
|
Fields []*tmplField // Struct fields definition depends on the binding language.
|
||||||
|
}
|
||||||
|
|
||||||
// tmplSource is language to template mapping containing all the supported
|
// tmplSource is language to template mapping containing all the supported
|
||||||
// programming languages the package can generate to.
|
// programming languages the package can generate to.
|
||||||
var tmplSource = map[Lang]string{
|
var tmplSource = map[Lang]string{
|
||||||
LangGo: tmplSourceGo,
|
LangGo: tmplSourceGo,
|
||||||
}
|
}
|
||||||
|
|
||||||
// tmplSourceGo is the Go source template use to generate the contract binding
|
// tmplSourceGo is the Go source template that the generated Go contract binding
|
||||||
// based on.
|
// is based on.
|
||||||
const tmplSourceGo = `
|
//
|
||||||
// Code generated - DO NOT EDIT.
|
//go:embed source.go.tpl
|
||||||
// This file is a generated binding and any manual changes will be lost.
|
var tmplSourceGo string
|
||||||
|
|
||||||
package {{.Package}}
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
ethereum "github.com/XinFinOrg/XDPoSChain"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/event"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reference imports to suppress errors if they are not otherwise used.
|
|
||||||
var (
|
|
||||||
_ = big.NewInt
|
|
||||||
_ = strings.NewReader
|
|
||||||
_ = ethereum.ErrNotFound
|
|
||||||
_ = bind.Bind
|
|
||||||
_ = common.Big1
|
|
||||||
_ = types.BloomLookup
|
|
||||||
_ = event.NewSubscription
|
|
||||||
)
|
|
||||||
|
|
||||||
{{range $contract := .Contracts}}
|
|
||||||
// {{.Type}}ABI is the input ABI used to generate the binding from.
|
|
||||||
const {{.Type}}ABI = "{{.InputABI}}"
|
|
||||||
|
|
||||||
{{if $contract.FuncSigs}}
|
|
||||||
// {{.Type}}FuncSigs maps the 4-byte function signature to its string representation.
|
|
||||||
var {{.Type}}FuncSigs = map[string]string{
|
|
||||||
{{range $strsig, $binsig := .FuncSigs}}"{{$binsig}}": "{{$strsig}}",
|
|
||||||
{{end}}
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if .InputBin}}
|
|
||||||
// {{.Type}}Bin is the compiled bytecode used for deploying new contracts.
|
|
||||||
var {{.Type}}Bin = "0x{{.InputBin}}"
|
|
||||||
|
|
||||||
// Deploy{{.Type}} deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
|
|
||||||
func Deploy{{.Type}}(auth *bind.TransactOpts, backend bind.ContractBackend {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type}}{{end}}) (common.Address, *types.Transaction, *{{.Type}}, error) {
|
|
||||||
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
|
|
||||||
if err != nil {
|
|
||||||
return common.Address{}, nil, nil, err
|
|
||||||
}
|
|
||||||
{{range $pattern, $name := .Libraries}}
|
|
||||||
{{decapitalise $name}}Addr, _, _, _ := Deploy{{capitalise $name}}(auth, backend)
|
|
||||||
{{$contract.Type}}Bin = strings.Replace({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:], -1)
|
|
||||||
{{end}}
|
|
||||||
address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}})
|
|
||||||
if err != nil {
|
|
||||||
return common.Address{}, nil, nil, err
|
|
||||||
}
|
|
||||||
return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
|
|
||||||
type {{.Type}} struct {
|
|
||||||
{{.Type}}Caller // Read-only binding to the contract
|
|
||||||
{{.Type}}Transactor // Write-only binding to the contract
|
|
||||||
{{.Type}}Filterer // Log filterer for contract events
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract.
|
|
||||||
type {{.Type}}Caller struct {
|
|
||||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}Transactor is an auto generated write-only Go binding around an Ethereum contract.
|
|
||||||
type {{.Type}}Transactor struct {
|
|
||||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}Filterer is an auto generated log filtering Go binding around an Ethereum contract events.
|
|
||||||
type {{.Type}}Filterer struct {
|
|
||||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}Session is an auto generated Go binding around an Ethereum contract,
|
|
||||||
// with pre-set call and transact options.
|
|
||||||
type {{.Type}}Session struct {
|
|
||||||
Contract *{{.Type}} // Generic contract binding to set the session for
|
|
||||||
CallOpts bind.CallOpts // Call options to use throughout this session
|
|
||||||
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}CallerSession is an auto generated read-only Go binding around an Ethereum contract,
|
|
||||||
// with pre-set call options.
|
|
||||||
type {{.Type}}CallerSession struct {
|
|
||||||
Contract *{{.Type}}Caller // Generic contract caller binding to set the session for
|
|
||||||
CallOpts bind.CallOpts // Call options to use throughout this session
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}TransactorSession is an auto generated write-only Go binding around an Ethereum contract,
|
|
||||||
// with pre-set transact options.
|
|
||||||
type {{.Type}}TransactorSession struct {
|
|
||||||
Contract *{{.Type}}Transactor // Generic contract transactor binding to set the session for
|
|
||||||
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}Raw is an auto generated low-level Go binding around an Ethereum contract.
|
|
||||||
type {{.Type}}Raw struct {
|
|
||||||
Contract *{{.Type}} // Generic contract binding to access the raw methods on
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
|
|
||||||
type {{.Type}}CallerRaw struct {
|
|
||||||
Contract *{{.Type}}Caller // Generic read-only contract binding to access the raw methods on
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
|
|
||||||
type {{.Type}}TransactorRaw struct {
|
|
||||||
Contract *{{.Type}}Transactor // Generic write-only contract binding to access the raw methods on
|
|
||||||
}
|
|
||||||
|
|
||||||
// New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract.
|
|
||||||
func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) {
|
|
||||||
contract, err := bind{{.Type}}(address, backend, backend, backend)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract.
|
|
||||||
func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) {
|
|
||||||
contract, err := bind{{.Type}}(address, caller, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &{{.Type}}Caller{contract: contract}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract.
|
|
||||||
func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) {
|
|
||||||
contract, err := bind{{.Type}}(address, nil, transactor, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &{{.Type}}Transactor{contract: contract}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// New{{.Type}}Filterer creates a new log filterer instance of {{.Type}}, bound to a specific deployed contract.
|
|
||||||
func New{{.Type}}Filterer(address common.Address, filterer bind.ContractFilterer) (*{{.Type}}Filterer, error) {
|
|
||||||
contract, err := bind{{.Type}}(address, nil, nil, filterer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &{{.Type}}Filterer{contract: contract}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// bind{{.Type}} binds a generic wrapper to an already deployed contract.
|
|
||||||
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
|
|
||||||
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call invokes the (constant) contract method with params as input values and
|
|
||||||
// sets the output to result. The result type might be a single field for simple
|
|
||||||
// returns, a slice of interfaces for anonymous returns and a struct for named
|
|
||||||
// returns.
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
|
|
||||||
return _{{$contract.Type}}.Contract.{{$contract.Type}}Caller.contract.Call(opts, result, method, params...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
|
||||||
// its default method if one is available.
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transfer(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transact invokes the (paid) contract method with params as input values.
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transact(opts, method, params...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call invokes the (constant) contract method with params as input values and
|
|
||||||
// sets the output to result. The result type might be a single field for simple
|
|
||||||
// returns, a slice of interfaces for anonymous returns and a struct for named
|
|
||||||
// returns.
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
|
|
||||||
return _{{$contract.Type}}.Contract.contract.Call(opts, result, method, params...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
|
||||||
// its default method if one is available.
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.contract.Transfer(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transact invokes the (paid) contract method with params as input values.
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.contract.Transact(opts, method, params...)
|
|
||||||
}
|
|
||||||
|
|
||||||
{{range .Calls}}
|
|
||||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type}},{{end}}{{end}} error) {
|
|
||||||
{{if .Structured}}ret := new(struct{
|
|
||||||
{{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}}
|
|
||||||
{{end}}
|
|
||||||
}){{else}}var (
|
|
||||||
{{range $i, $_ := .Normalized.Outputs}}ret{{$i}} = new({{bindtype .Type}})
|
|
||||||
{{end}}
|
|
||||||
){{end}}
|
|
||||||
out := {{if .Structured}}ret{{else}}{{if eq (len .Normalized.Outputs) 1}}ret0{{else}}&[]interface{}{
|
|
||||||
{{range $i, $_ := .Normalized.Outputs}}ret{{$i}},
|
|
||||||
{{end}}
|
|
||||||
}{{end}}{{end}}
|
|
||||||
err := _{{$contract.Type}}.contract.Call(opts, out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
|
||||||
return {{if .Structured}}*ret,{{else}}{{range $i, $_ := .Normalized.Outputs}}*ret{{$i}},{{end}}{{end}} err
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type}},{{end}} {{end}} error) {
|
|
||||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type}},{{end}} {{end}} error) {
|
|
||||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{range .Transacts}}
|
|
||||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type}} {{end}}) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.contract.Transact(opts, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{range .Events}}
|
|
||||||
// {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract.
|
|
||||||
type {{$contract.Type}}{{.Normalized.Name}}Iterator struct {
|
|
||||||
Event *{{$contract.Type}}{{.Normalized.Name}} // Event containing the contract specifics and raw log
|
|
||||||
|
|
||||||
contract *bind.BoundContract // Generic contract to use for unpacking event data
|
|
||||||
event string // Event name to use for unpacking event data
|
|
||||||
|
|
||||||
logs chan types.Log // Log channel receiving the found contract events
|
|
||||||
sub ethereum.Subscription // Subscription for errors, completion and termination
|
|
||||||
done bool // Whether the subscription completed delivering logs
|
|
||||||
fail error // Occurred error to stop iteration
|
|
||||||
}
|
|
||||||
// Next advances the iterator to the subsequent event, returning whether there
|
|
||||||
// are any more events found. In case of a retrieval or parsing error, false is
|
|
||||||
// returned and Error() can be queried for the exact failure.
|
|
||||||
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Next() bool {
|
|
||||||
// If the iterator failed, stop iterating
|
|
||||||
if (it.fail != nil) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// If the iterator completed, deliver directly whatever's available
|
|
||||||
if (it.done) {
|
|
||||||
select {
|
|
||||||
case log := <-it.logs:
|
|
||||||
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
|
|
||||||
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
|
||||||
it.fail = err
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
it.Event.Raw = log
|
|
||||||
return true
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Iterator still in progress, wait for either a data or an error event
|
|
||||||
select {
|
|
||||||
case log := <-it.logs:
|
|
||||||
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
|
|
||||||
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
|
||||||
it.fail = err
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
it.Event.Raw = log
|
|
||||||
return true
|
|
||||||
|
|
||||||
case err := <-it.sub.Err():
|
|
||||||
it.done = true
|
|
||||||
it.fail = err
|
|
||||||
return it.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Error returns any retrieval or parsing error occurred during filtering.
|
|
||||||
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Error() error {
|
|
||||||
return it.fail
|
|
||||||
}
|
|
||||||
// Close terminates the iteration process, releasing any pending underlying
|
|
||||||
// resources.
|
|
||||||
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Close() error {
|
|
||||||
it.sub.Unsubscribe()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract.
|
|
||||||
type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}}
|
|
||||||
{{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type}}{{else}}{{bindtype .Type}}{{end}}; {{end}}
|
|
||||||
Raw types.Log // Blockchain specific contextual infos
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.Id}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) {
|
|
||||||
{{range .Normalized.Inputs}}
|
|
||||||
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
|
||||||
for _, {{.Name}}Item := range {{.Name}} {
|
|
||||||
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
|
||||||
}{{end}}{{end}}
|
|
||||||
|
|
||||||
logs, sub, err := _{{$contract.Type}}.contract.FilterLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &{{$contract.Type}}{{.Normalized.Name}}Iterator{contract: _{{$contract.Type}}.contract, event: "{{.Original.Name}}", logs: logs, sub: sub}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.Id}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type}}{{end}}{{end}}) (event.Subscription, error) {
|
|
||||||
{{range .Normalized.Inputs}}
|
|
||||||
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
|
||||||
for _, {{.Name}}Item := range {{.Name}} {
|
|
||||||
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
|
||||||
}{{end}}{{end}}
|
|
||||||
|
|
||||||
logs, sub, err := _{{$contract.Type}}.contract.WatchLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case log := <-logs:
|
|
||||||
// New log arrived, parse the event and forward to the user
|
|
||||||
event := new({{$contract.Type}}{{.Normalized.Name}})
|
|
||||||
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
event.Raw = log
|
|
||||||
|
|
||||||
select {
|
|
||||||
case sink <- event:
|
|
||||||
case err := <-sub.Err():
|
|
||||||
return err
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
case err := <-sub.Err():
|
|
||||||
return err
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
`
|
|
||||||
|
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
// Copyright 2018 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package bind
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// makeTopics converts a filter query argument list into a filter topic set.
|
|
||||||
func makeTopics(query ...[]interface{}) ([][]common.Hash, error) {
|
|
||||||
topics := make([][]common.Hash, len(query))
|
|
||||||
for i, filter := range query {
|
|
||||||
for _, rule := range filter {
|
|
||||||
var topic common.Hash
|
|
||||||
|
|
||||||
// Try to generate the topic based on simple types
|
|
||||||
switch rule := rule.(type) {
|
|
||||||
case common.Hash:
|
|
||||||
copy(topic[:], rule[:])
|
|
||||||
case common.Address:
|
|
||||||
copy(topic[common.HashLength-common.AddressLength:], rule[:])
|
|
||||||
case *big.Int:
|
|
||||||
blob := rule.Bytes()
|
|
||||||
copy(topic[common.HashLength-len(blob):], blob)
|
|
||||||
case bool:
|
|
||||||
if rule {
|
|
||||||
topic[common.HashLength-1] = 1
|
|
||||||
}
|
|
||||||
case int8:
|
|
||||||
blob := big.NewInt(int64(rule)).Bytes()
|
|
||||||
copy(topic[common.HashLength-len(blob):], blob)
|
|
||||||
case int16:
|
|
||||||
blob := big.NewInt(int64(rule)).Bytes()
|
|
||||||
copy(topic[common.HashLength-len(blob):], blob)
|
|
||||||
case int32:
|
|
||||||
blob := big.NewInt(int64(rule)).Bytes()
|
|
||||||
copy(topic[common.HashLength-len(blob):], blob)
|
|
||||||
case int64:
|
|
||||||
blob := big.NewInt(rule).Bytes()
|
|
||||||
copy(topic[common.HashLength-len(blob):], blob)
|
|
||||||
case uint8:
|
|
||||||
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
|
|
||||||
copy(topic[common.HashLength-len(blob):], blob)
|
|
||||||
case uint16:
|
|
||||||
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
|
|
||||||
copy(topic[common.HashLength-len(blob):], blob)
|
|
||||||
case uint32:
|
|
||||||
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
|
|
||||||
copy(topic[common.HashLength-len(blob):], blob)
|
|
||||||
case uint64:
|
|
||||||
blob := new(big.Int).SetUint64(rule).Bytes()
|
|
||||||
copy(topic[common.HashLength-len(blob):], blob)
|
|
||||||
case string:
|
|
||||||
hash := crypto.Keccak256Hash([]byte(rule))
|
|
||||||
copy(topic[:], hash[:])
|
|
||||||
case []byte:
|
|
||||||
hash := crypto.Keccak256Hash(rule)
|
|
||||||
copy(topic[:], hash[:])
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Attempt to generate the topic from funky types
|
|
||||||
val := reflect.ValueOf(rule)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
|
|
||||||
reflect.Copy(reflect.ValueOf(topic[common.HashLength-val.Len():]), val)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported indexed type: %T", rule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
topics[i] = append(topics[i], topic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return topics, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Big batch of reflect types for topic reconstruction.
|
|
||||||
var (
|
|
||||||
reflectHash = reflect.TypeOf(common.Hash{})
|
|
||||||
reflectAddress = reflect.TypeOf(common.Address{})
|
|
||||||
reflectBigInt = reflect.TypeOf(new(big.Int))
|
|
||||||
)
|
|
||||||
|
|
||||||
// parseTopics converts the indexed topic fields into actual log field values.
|
|
||||||
//
|
|
||||||
// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256
|
|
||||||
// hashes as the topic value!
|
|
||||||
func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error {
|
|
||||||
// Sanity check that the fields and topics match up
|
|
||||||
if len(fields) != len(topics) {
|
|
||||||
return errors.New("topic/field count mismatch")
|
|
||||||
}
|
|
||||||
// Iterate over all the fields and reconstruct them from topics
|
|
||||||
for _, arg := range fields {
|
|
||||||
if !arg.Indexed {
|
|
||||||
return errors.New("non-indexed field in topic reconstruction")
|
|
||||||
}
|
|
||||||
field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name))
|
|
||||||
|
|
||||||
// Try to parse the topic back into the fields based on primitive types
|
|
||||||
switch field.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
if topics[0][common.HashLength-1] == 1 {
|
|
||||||
field.Set(reflect.ValueOf(true))
|
|
||||||
}
|
|
||||||
case reflect.Int8:
|
|
||||||
num := new(big.Int).SetBytes(topics[0][:])
|
|
||||||
field.Set(reflect.ValueOf(int8(num.Int64())))
|
|
||||||
|
|
||||||
case reflect.Int16:
|
|
||||||
num := new(big.Int).SetBytes(topics[0][:])
|
|
||||||
field.Set(reflect.ValueOf(int16(num.Int64())))
|
|
||||||
|
|
||||||
case reflect.Int32:
|
|
||||||
num := new(big.Int).SetBytes(topics[0][:])
|
|
||||||
field.Set(reflect.ValueOf(int32(num.Int64())))
|
|
||||||
|
|
||||||
case reflect.Int64:
|
|
||||||
num := new(big.Int).SetBytes(topics[0][:])
|
|
||||||
field.Set(reflect.ValueOf(num.Int64()))
|
|
||||||
|
|
||||||
case reflect.Uint8:
|
|
||||||
num := new(big.Int).SetBytes(topics[0][:])
|
|
||||||
field.Set(reflect.ValueOf(uint8(num.Uint64())))
|
|
||||||
|
|
||||||
case reflect.Uint16:
|
|
||||||
num := new(big.Int).SetBytes(topics[0][:])
|
|
||||||
field.Set(reflect.ValueOf(uint16(num.Uint64())))
|
|
||||||
|
|
||||||
case reflect.Uint32:
|
|
||||||
num := new(big.Int).SetBytes(topics[0][:])
|
|
||||||
field.Set(reflect.ValueOf(uint32(num.Uint64())))
|
|
||||||
|
|
||||||
case reflect.Uint64:
|
|
||||||
num := new(big.Int).SetBytes(topics[0][:])
|
|
||||||
field.Set(reflect.ValueOf(num.Uint64()))
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Ran out of plain primitive types, try custom types
|
|
||||||
switch field.Type() {
|
|
||||||
case reflectHash: // Also covers all dynamic types
|
|
||||||
field.Set(reflect.ValueOf(topics[0]))
|
|
||||||
|
|
||||||
case reflectAddress:
|
|
||||||
var addr common.Address
|
|
||||||
copy(addr[:], topics[0][common.HashLength-common.AddressLength:])
|
|
||||||
field.Set(reflect.ValueOf(addr))
|
|
||||||
|
|
||||||
case reflectBigInt:
|
|
||||||
num := new(big.Int).SetBytes(topics[0][:])
|
|
||||||
field.Set(reflect.ValueOf(num))
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Ran out of custom types, try the crazies
|
|
||||||
switch {
|
|
||||||
case arg.Type.T == abi.FixedBytesTy:
|
|
||||||
reflect.Copy(field, reflect.ValueOf(topics[0][common.HashLength-arg.Type.Size:]))
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported indexed type: %v", arg.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
topics = topics[1:]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
ethereum "github.com/XinFinOrg/XDPoSChain"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
"github.com/XinFinOrg/XDPoSChain/log"
|
"github.com/XinFinOrg/XDPoSChain/log"
|
||||||
|
|
@ -29,20 +30,28 @@ import (
|
||||||
// WaitMined waits for tx to be mined on the blockchain.
|
// WaitMined waits for tx to be mined on the blockchain.
|
||||||
// It stops waiting when the context is canceled.
|
// It stops waiting when the context is canceled.
|
||||||
func WaitMined(ctx context.Context, b DeployBackend, tx *types.Transaction) (*types.Receipt, error) {
|
func WaitMined(ctx context.Context, b DeployBackend, tx *types.Transaction) (*types.Receipt, error) {
|
||||||
|
return WaitMinedHash(ctx, b, tx.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitMinedHash waits for a transaction with the provided hash to be mined on the blockchain.
|
||||||
|
// It stops waiting when the context is canceled.
|
||||||
|
func WaitMinedHash(ctx context.Context, b DeployBackend, hash common.Hash) (*types.Receipt, error) {
|
||||||
queryTicker := time.NewTicker(time.Second)
|
queryTicker := time.NewTicker(time.Second)
|
||||||
defer queryTicker.Stop()
|
defer queryTicker.Stop()
|
||||||
|
|
||||||
logger := log.New("hash", tx.Hash())
|
logger := log.New("hash", hash)
|
||||||
for {
|
for {
|
||||||
receipt, err := b.TransactionReceipt(ctx, tx.Hash())
|
receipt, err := b.TransactionReceipt(ctx, hash)
|
||||||
if receipt != nil {
|
if err == nil {
|
||||||
return receipt, nil
|
return receipt, nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
logger.Trace("Receipt retrieval failed", "err", err)
|
if errors.Is(err, ethereum.ErrNotFound) {
|
||||||
} else {
|
|
||||||
logger.Trace("Transaction not yet mined")
|
logger.Trace("Transaction not yet mined")
|
||||||
|
} else {
|
||||||
|
logger.Trace("Receipt retrieval failed", "err", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the next round.
|
// Wait for the next round.
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|
@ -58,7 +67,13 @@ func WaitDeployed(ctx context.Context, b DeployBackend, tx *types.Transaction) (
|
||||||
if tx.To() != nil {
|
if tx.To() != nil {
|
||||||
return common.Address{}, errors.New("tx is not contract creation")
|
return common.Address{}, errors.New("tx is not contract creation")
|
||||||
}
|
}
|
||||||
receipt, err := WaitMined(ctx, b, tx)
|
return WaitDeployedHash(ctx, b, tx.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitDeployedHash waits for a contract deployment transaction with the provided hash and returns the on-chain
|
||||||
|
// contract address when it is mined. It stops waiting when ctx is canceled.
|
||||||
|
func WaitDeployedHash(ctx context.Context, b DeployBackend, hash common.Hash) (common.Address, error) {
|
||||||
|
receipt, err := WaitMinedHash(ctx, b, hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package bind_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -25,7 +26,6 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
"github.com/XinFinOrg/XDPoSChain/params"
|
"github.com/XinFinOrg/XDPoSChain/params"
|
||||||
|
|
@ -53,14 +53,18 @@ var waitDeployedTests = map[string]struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWaitDeployed(t *testing.T) {
|
func TestWaitDeployed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
config := *params.TestXDPoSMockChainConfig
|
config := *params.TestXDPoSMockChainConfig
|
||||||
config.Eip1559Block = big.NewInt(0)
|
config.Eip1559Block = big.NewInt(0)
|
||||||
for name, test := range waitDeployedTests {
|
for name, test := range waitDeployedTests {
|
||||||
backend := backends.NewXDCSimulatedBackend(
|
backend := backends.NewXDCSimulatedBackend(
|
||||||
core.GenesisAlloc{
|
types.GenesisAlloc{
|
||||||
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(100000000000000000)},
|
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(100000000000000000)},
|
||||||
}, 10000000, &config,
|
},
|
||||||
|
10000000,
|
||||||
|
&config,
|
||||||
)
|
)
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
// Create the transaction
|
// Create the transaction
|
||||||
head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
||||||
|
|
@ -82,13 +86,15 @@ func TestWaitDeployed(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Send and mine the transaction.
|
// Send and mine the transaction.
|
||||||
backend.SendTransaction(ctx, tx)
|
if err := backend.SendTransaction(ctx, tx); err != nil {
|
||||||
|
t.Errorf("test %q: failed to send transaction: %v", name, err)
|
||||||
|
}
|
||||||
backend.Commit()
|
backend.Commit()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-mined:
|
case <-mined:
|
||||||
if err != test.wantErr {
|
if err != test.wantErr {
|
||||||
t.Errorf("test %q: error mismatch: got %q, want %q", name, err, test.wantErr)
|
t.Errorf("test %q: error mismatch: want %q, got %q", name, test.wantErr, err)
|
||||||
}
|
}
|
||||||
if address != test.wantAddress {
|
if address != test.wantAddress {
|
||||||
t.Errorf("test %q: unexpected contract address %s", name, address.Hex())
|
t.Errorf("test %q: unexpected contract address %s", name, address.Hex())
|
||||||
|
|
@ -98,3 +104,51 @@ func TestWaitDeployed(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWaitDeployedCornerCases(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
config := *params.TestXDPoSMockChainConfig
|
||||||
|
config.Eip1559Block = big.NewInt(0)
|
||||||
|
backend := backends.NewXDCSimulatedBackend(
|
||||||
|
types.GenesisAlloc{
|
||||||
|
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(100000000000000000)},
|
||||||
|
},
|
||||||
|
10000000,
|
||||||
|
&config,
|
||||||
|
)
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
|
head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
||||||
|
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
|
||||||
|
|
||||||
|
// Create a transaction to an account.
|
||||||
|
code := "6060604052600a8060106000396000f360606040526008565b00"
|
||||||
|
tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
|
||||||
|
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
if err := backend.SendTransaction(ctx, tx); err != nil {
|
||||||
|
t.Errorf("failed to send transaction: %q", err)
|
||||||
|
}
|
||||||
|
backend.Commit()
|
||||||
|
notContractCreation := errors.New("tx is not contract creation")
|
||||||
|
if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != notContractCreation.Error() {
|
||||||
|
t.Errorf("error mismatch: want %q, got %q, ", notContractCreation, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a transaction that is not mined.
|
||||||
|
tx = types.NewContractCreation(1, big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
|
||||||
|
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
contextCanceled := errors.New("context canceled")
|
||||||
|
if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != contextCanceled.Error() {
|
||||||
|
t.Errorf("error mismatch: want %q, got %q, ", contextCanceled, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := backend.SendTransaction(ctx, tx); err != nil {
|
||||||
|
t.Errorf("failed to send transaction: %q", err)
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
// Copyright 2021 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -17,68 +17,76 @@
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type Error struct {
|
||||||
errBadBool = errors.New("abi: improperly encoded boolean value")
|
Name string
|
||||||
)
|
Inputs Arguments
|
||||||
|
str string
|
||||||
|
|
||||||
// formatSliceString formats the reflection kind with the given slice size
|
// Sig contains the string signature according to the ABI spec.
|
||||||
// and returns a formatted string representation.
|
// e.g. error foo(uint32 a, int b) = "foo(uint32,int256)"
|
||||||
func formatSliceString(kind reflect.Kind, sliceSize int) string {
|
// Please note that "int" is substitute for its canonical representation "int256"
|
||||||
if sliceSize == -1 {
|
Sig string
|
||||||
return fmt.Sprintf("[]%v", kind)
|
|
||||||
}
|
// ID returns the canonical representation of the error's signature used by the
|
||||||
return fmt.Sprintf("[%d]%v", sliceSize, kind)
|
// abi definition to identify event names and types.
|
||||||
|
ID common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
// sliceTypeCheck checks that the given slice can by assigned to the reflection
|
func NewError(name string, inputs Arguments) Error {
|
||||||
// type in t.
|
// sanitize inputs to remove inputs without names
|
||||||
func sliceTypeCheck(t Type, val reflect.Value) error {
|
// and precompute string and sig representation.
|
||||||
if val.Kind() != reflect.Slice && val.Kind() != reflect.Array {
|
names := make([]string, len(inputs))
|
||||||
return typeErr(formatSliceString(t.Kind, t.Size), val.Type())
|
types := make([]string, len(inputs))
|
||||||
}
|
for i, input := range inputs {
|
||||||
|
if input.Name == "" {
|
||||||
if t.T == ArrayTy && val.Len() != t.Size {
|
inputs[i] = Argument{
|
||||||
return typeErr(formatSliceString(t.Elem.Kind, t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len()))
|
Name: fmt.Sprintf("arg%d", i),
|
||||||
}
|
Indexed: input.Indexed,
|
||||||
|
Type: input.Type,
|
||||||
if t.Elem.T == SliceTy {
|
}
|
||||||
if val.Len() > 0 {
|
} else {
|
||||||
return sliceTypeCheck(*t.Elem, val.Index(0))
|
inputs[i] = input
|
||||||
}
|
}
|
||||||
} else if t.Elem.T == ArrayTy {
|
// string representation
|
||||||
return sliceTypeCheck(*t.Elem, val.Index(0))
|
names[i] = fmt.Sprintf("%v %v", input.Type, inputs[i].Name)
|
||||||
|
if input.Indexed {
|
||||||
|
names[i] = fmt.Sprintf("%v indexed %v", input.Type, inputs[i].Name)
|
||||||
|
}
|
||||||
|
// sig representation
|
||||||
|
types[i] = input.Type.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
if elemKind := val.Type().Elem().Kind(); elemKind != t.Elem.Kind {
|
str := fmt.Sprintf("error %v(%v)", name, strings.Join(names, ", "))
|
||||||
return typeErr(formatSliceString(t.Elem.Kind, t.Size), val.Type())
|
sig := fmt.Sprintf("%v(%v)", name, strings.Join(types, ","))
|
||||||
|
id := common.BytesToHash(crypto.Keccak256([]byte(sig)))
|
||||||
|
|
||||||
|
return Error{
|
||||||
|
Name: name,
|
||||||
|
Inputs: inputs,
|
||||||
|
str: str,
|
||||||
|
Sig: sig,
|
||||||
|
ID: id,
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// typeCheck checks that the given reflection value can be assigned to the reflection
|
func (e Error) String() string {
|
||||||
// type in t.
|
return e.str
|
||||||
func typeCheck(t Type, value reflect.Value) error {
|
|
||||||
if t.T == SliceTy || t.T == ArrayTy {
|
|
||||||
return sliceTypeCheck(t, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check base type validity. Element types will be checked later on.
|
|
||||||
if t.Kind != value.Kind() {
|
|
||||||
return typeErr(t.Kind, value.Kind())
|
|
||||||
} else if t.T == FixedBytesTy && t.Size != value.Len() {
|
|
||||||
return typeErr(t.Type, value.Type())
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// typeErr returns a formatted type casting error.
|
func (e *Error) Unpack(data []byte) (interface{}, error) {
|
||||||
func typeErr(expected, got interface{}) error {
|
if len(data) < 4 {
|
||||||
return fmt.Errorf("abi: cannot use %v as type %v as argument", got, expected)
|
return "", fmt.Errorf("insufficient data for unpacking: have %d, want at least 4", len(data))
|
||||||
|
}
|
||||||
|
if !bytes.Equal(data[:4], e.ID[:4]) {
|
||||||
|
return "", fmt.Errorf("invalid identifier, have %#x want %#x", data[:4], e.ID[:4])
|
||||||
|
}
|
||||||
|
return e.Inputs.Unpack(data[4:])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
89
accounts/abi/error_handling.go
Normal file
89
accounts/abi/error_handling.go
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errBadBool = errors.New("abi: improperly encoded boolean value")
|
||||||
|
errBadUint8 = errors.New("abi: improperly encoded uint8 value")
|
||||||
|
errBadUint16 = errors.New("abi: improperly encoded uint16 value")
|
||||||
|
errBadUint32 = errors.New("abi: improperly encoded uint32 value")
|
||||||
|
errBadUint64 = errors.New("abi: improperly encoded uint64 value")
|
||||||
|
errBadInt8 = errors.New("abi: improperly encoded int8 value")
|
||||||
|
errBadInt16 = errors.New("abi: improperly encoded int16 value")
|
||||||
|
errBadInt32 = errors.New("abi: improperly encoded int32 value")
|
||||||
|
errBadInt64 = errors.New("abi: improperly encoded int64 value")
|
||||||
|
)
|
||||||
|
|
||||||
|
// formatSliceString formats the reflection kind with the given slice size
|
||||||
|
// and returns a formatted string representation.
|
||||||
|
func formatSliceString(kind reflect.Kind, sliceSize int) string {
|
||||||
|
if sliceSize == -1 {
|
||||||
|
return fmt.Sprintf("[]%v", kind)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("[%d]%v", sliceSize, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sliceTypeCheck checks that the given slice can by assigned to the reflection
|
||||||
|
// type in t.
|
||||||
|
func sliceTypeCheck(t Type, val reflect.Value) error {
|
||||||
|
if val.Kind() != reflect.Slice && val.Kind() != reflect.Array {
|
||||||
|
return typeErr(formatSliceString(t.GetType().Kind(), t.Size), val.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.T == ArrayTy && val.Len() != t.Size {
|
||||||
|
return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Elem.T == SliceTy || t.Elem.T == ArrayTy {
|
||||||
|
if val.Len() > 0 {
|
||||||
|
return sliceTypeCheck(*t.Elem, val.Index(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if val.Type().Elem().Kind() != t.Elem.GetType().Kind() {
|
||||||
|
return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeCheck checks that the given reflection value can be assigned to the reflection
|
||||||
|
// type in t.
|
||||||
|
func typeCheck(t Type, value reflect.Value) error {
|
||||||
|
if t.T == SliceTy || t.T == ArrayTy {
|
||||||
|
return sliceTypeCheck(t, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check base type validity. Element types will be checked later on.
|
||||||
|
if t.GetType().Kind() != value.Kind() {
|
||||||
|
return typeErr(t.GetType().Kind(), value.Kind())
|
||||||
|
} else if t.T == FixedBytesTy && t.Size != value.Len() {
|
||||||
|
return typeErr(t.GetType(), value.Type())
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeErr returns a formatted type casting error.
|
||||||
|
func typeErr(expected, got interface{}) error {
|
||||||
|
return fmt.Errorf("abi: cannot use %v as type %v as argument", got, expected)
|
||||||
|
}
|
||||||
|
|
@ -28,30 +28,76 @@ import (
|
||||||
// holds type information (inputs) about the yielded output. Anonymous events
|
// holds type information (inputs) about the yielded output. Anonymous events
|
||||||
// don't get the signature canonical representation as the first LOG topic.
|
// don't get the signature canonical representation as the first LOG topic.
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Name string
|
// Name is the event name used for internal representation. It's derived from
|
||||||
|
// the raw name and a suffix will be added in the case of event overloading.
|
||||||
|
//
|
||||||
|
// e.g.
|
||||||
|
// These are two events that have the same name:
|
||||||
|
// * foo(int,int)
|
||||||
|
// * foo(uint,uint)
|
||||||
|
// The event name of the first one will be resolved as foo while the second one
|
||||||
|
// will be resolved as foo0.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// RawName is the raw event name parsed from ABI.
|
||||||
|
RawName string
|
||||||
Anonymous bool
|
Anonymous bool
|
||||||
Inputs Arguments
|
Inputs Arguments
|
||||||
|
str string
|
||||||
|
|
||||||
|
// Sig contains the string signature according to the ABI spec.
|
||||||
|
// e.g. event foo(uint32 a, int b) = "foo(uint32,int256)"
|
||||||
|
// Please note that "int" is substitute for its canonical representation "int256"
|
||||||
|
Sig string
|
||||||
|
|
||||||
|
// ID returns the canonical representation of the event's signature used by the
|
||||||
|
// abi definition to identify event names and types.
|
||||||
|
ID common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
func (event Event) String() string {
|
// NewEvent creates a new Event.
|
||||||
inputs := make([]string, len(event.Inputs))
|
// It sanitizes the input arguments to remove unnamed arguments.
|
||||||
for i, input := range event.Inputs {
|
// It also precomputes the id, signature and string representation
|
||||||
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
|
// of the event.
|
||||||
if input.Indexed {
|
func NewEvent(name, rawName string, anonymous bool, inputs Arguments) Event {
|
||||||
inputs[i] = fmt.Sprintf("%v indexed %v", input.Name, input.Type)
|
// sanitize inputs to remove inputs without names
|
||||||
|
// and precompute string and sig representation.
|
||||||
|
names := make([]string, len(inputs))
|
||||||
|
types := make([]string, len(inputs))
|
||||||
|
for i, input := range inputs {
|
||||||
|
if input.Name == "" {
|
||||||
|
inputs[i] = Argument{
|
||||||
|
Name: fmt.Sprintf("arg%d", i),
|
||||||
|
Indexed: input.Indexed,
|
||||||
|
Type: input.Type,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inputs[i] = input
|
||||||
}
|
}
|
||||||
|
// string representation
|
||||||
|
names[i] = fmt.Sprintf("%v %v", input.Type, inputs[i].Name)
|
||||||
|
if input.Indexed {
|
||||||
|
names[i] = fmt.Sprintf("%v indexed %v", input.Type, inputs[i].Name)
|
||||||
|
}
|
||||||
|
// sig representation
|
||||||
|
types[i] = input.Type.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
str := fmt.Sprintf("event %v(%v)", rawName, strings.Join(names, ", "))
|
||||||
|
sig := fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ","))
|
||||||
|
id := common.BytesToHash(crypto.Keccak256([]byte(sig)))
|
||||||
|
|
||||||
|
return Event{
|
||||||
|
Name: name,
|
||||||
|
RawName: rawName,
|
||||||
|
Anonymous: anonymous,
|
||||||
|
Inputs: inputs,
|
||||||
|
str: str,
|
||||||
|
Sig: sig,
|
||||||
|
ID: id,
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("event %v(%v)", event.Name, strings.Join(inputs, ", "))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Id returns the canonical representation of the event's signature used by the
|
func (e Event) String() string {
|
||||||
// abi definition to identify event names and types.
|
return e.str
|
||||||
func (e Event) Id() common.Hash {
|
|
||||||
types := make([]string, len(e.Inputs))
|
|
||||||
i := 0
|
|
||||||
for _, input := range e.Inputs {
|
|
||||||
types[i] = input.Type.String()
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ",")))))
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,25 +58,42 @@ var jsonEventPledge = []byte(`{
|
||||||
"type": "event"
|
"type": "event"
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
|
var jsonEventMixedCase = []byte(`{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [{
|
||||||
|
"indexed": false, "name": "value", "type": "uint256"
|
||||||
|
}, {
|
||||||
|
"indexed": false, "name": "_value", "type": "uint256"
|
||||||
|
}, {
|
||||||
|
"indexed": false, "name": "Value", "type": "uint256"
|
||||||
|
}],
|
||||||
|
"name": "MixedCase",
|
||||||
|
"type": "event"
|
||||||
|
}`)
|
||||||
|
|
||||||
// 1000000
|
// 1000000
|
||||||
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
|
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
|
||||||
|
|
||||||
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
|
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
|
||||||
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
|
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
|
||||||
|
|
||||||
|
// 1000000,2218516807680,1000001
|
||||||
|
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
|
||||||
|
|
||||||
func TestEventId(t *testing.T) {
|
func TestEventId(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var table = []struct {
|
var table = []struct {
|
||||||
definition string
|
definition string
|
||||||
expectations map[string]common.Hash
|
expectations map[string]common.Hash
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
definition: `[
|
definition: `[
|
||||||
{ "type" : "event", "name" : "balance", "inputs": [{ "name" : "in", "type": "uint256" }] },
|
{ "type" : "event", "name" : "Balance", "inputs": [{ "name" : "in", "type": "uint256" }] },
|
||||||
{ "type" : "event", "name" : "check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] }
|
{ "type" : "event", "name" : "Check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] }
|
||||||
]`,
|
]`,
|
||||||
expectations: map[string]common.Hash{
|
expectations: map[string]common.Hash{
|
||||||
"balance": crypto.Keccak256Hash([]byte("balance(uint256)")),
|
"Balance": crypto.Keccak256Hash([]byte("Balance(uint256)")),
|
||||||
"check": crypto.Keccak256Hash([]byte("check(address,uint256)")),
|
"Check": crypto.Keccak256Hash([]byte("Check(address,uint256)")),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -88,8 +105,42 @@ func TestEventId(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, event := range abi.Events {
|
for name, event := range abi.Events {
|
||||||
if event.Id() != test.expectations[name] {
|
if event.ID != test.expectations[name] {
|
||||||
t.Errorf("expected id to be %x, got %x", test.expectations[name], event.Id())
|
t.Errorf("expected id to be %x, got %x", test.expectations[name], event.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var table = []struct {
|
||||||
|
definition string
|
||||||
|
expectations map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
definition: `[
|
||||||
|
{ "type" : "event", "name" : "Balance", "inputs": [{ "name" : "in", "type": "uint256" }] },
|
||||||
|
{ "type" : "event", "name" : "Check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] },
|
||||||
|
{ "type" : "event", "name" : "Transfer", "inputs": [{ "name": "from", "type": "address", "indexed": true }, { "name": "to", "type": "address", "indexed": true }, { "name": "value", "type": "uint256" }] }
|
||||||
|
]`,
|
||||||
|
expectations: map[string]string{
|
||||||
|
"Balance": "event Balance(uint256 in)",
|
||||||
|
"Check": "event Check(address t, uint256 b)",
|
||||||
|
"Transfer": "event Transfer(address indexed from, address indexed to, uint256 value)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
abi, err := JSON(strings.NewReader(test.definition))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, event := range abi.Events {
|
||||||
|
if event.String() != test.expectations[name] {
|
||||||
|
t.Errorf("expected string to be %s, got %s", test.expectations[name], event.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -97,11 +148,8 @@ func TestEventId(t *testing.T) {
|
||||||
|
|
||||||
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
|
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
|
||||||
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
||||||
type testStruct struct {
|
|
||||||
Value1 [2]uint8
|
|
||||||
Value2 uint8
|
|
||||||
}
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
|
|
@ -109,18 +157,39 @@ func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
||||||
for ; i <= 3; i++ {
|
for ; i <= 3; i++ {
|
||||||
b.Write(packNum(reflect.ValueOf(i)))
|
b.Write(packNum(reflect.ValueOf(i)))
|
||||||
}
|
}
|
||||||
var rst testStruct
|
unpacked, err := abi.Unpack("test", b.Bytes())
|
||||||
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
|
require.NoError(t, err)
|
||||||
require.Equal(t, [2]uint8{1, 2}, rst.Value1)
|
require.Equal(t, [2]uint8{1, 2}, unpacked[0])
|
||||||
require.Equal(t, uint8(3), rst.Value2)
|
require.Equal(t, uint8(3), unpacked[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEventTupleUnpack(t *testing.T) {
|
func TestEventTupleUnpack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
type EventTransfer struct {
|
type EventTransfer struct {
|
||||||
Value *big.Int
|
Value *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventTransferWithTag struct {
|
||||||
|
// this is valid because `value` is not exportable,
|
||||||
|
// so value is only unmarshalled into `Value1`.
|
||||||
|
value *big.Int //lint:ignore U1000 unused field is part of test
|
||||||
|
Value1 *big.Int `abi:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BadEventTransferWithSameFieldAndTag struct {
|
||||||
|
Value *big.Int
|
||||||
|
Value1 *big.Int `abi:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BadEventTransferWithDuplicatedTag struct {
|
||||||
|
Value1 *big.Int `abi:"value"`
|
||||||
|
Value2 *big.Int `abi:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BadEventTransferWithEmptyTag struct {
|
||||||
|
Value *big.Int `abi:""`
|
||||||
|
}
|
||||||
|
|
||||||
type EventPledge struct {
|
type EventPledge struct {
|
||||||
Who common.Address
|
Who common.Address
|
||||||
Wad *big.Int
|
Wad *big.Int
|
||||||
|
|
@ -133,9 +202,16 @@ func TestEventTupleUnpack(t *testing.T) {
|
||||||
Currency [3]byte
|
Currency [3]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventMixedCase struct {
|
||||||
|
Value1 *big.Int `abi:"value"`
|
||||||
|
Value2 *big.Int `abi:"_value"`
|
||||||
|
Value3 *big.Int `abi:"Value"`
|
||||||
|
}
|
||||||
|
|
||||||
bigint := new(big.Int)
|
bigint := new(big.Int)
|
||||||
bigintExpected := big.NewInt(1000000)
|
bigintExpected := big.NewInt(1000000)
|
||||||
bigintExpected2 := big.NewInt(2218516807680)
|
bigintExpected2 := big.NewInt(2218516807680)
|
||||||
|
bigintExpected3 := big.NewInt(1000001)
|
||||||
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
|
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
|
||||||
var testCases = []struct {
|
var testCases = []struct {
|
||||||
data string
|
data string
|
||||||
|
|
@ -158,6 +234,34 @@ func TestEventTupleUnpack(t *testing.T) {
|
||||||
jsonEventTransfer,
|
jsonEventTransfer,
|
||||||
"",
|
"",
|
||||||
"Can unpack ERC20 Transfer event into slice",
|
"Can unpack ERC20 Transfer event into slice",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&EventTransferWithTag{},
|
||||||
|
&EventTransferWithTag{Value1: bigintExpected},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"",
|
||||||
|
"Can unpack ERC20 Transfer event into structure with abi: tag",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&BadEventTransferWithDuplicatedTag{},
|
||||||
|
&BadEventTransferWithDuplicatedTag{},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"struct: abi tag in 'Value2' already mapped",
|
||||||
|
"Can not unpack ERC20 Transfer event with duplicated abi tag",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&BadEventTransferWithSameFieldAndTag{},
|
||||||
|
&BadEventTransferWithSameFieldAndTag{},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"abi: multiple variables maps to the same abi field 'value'",
|
||||||
|
"Can not unpack ERC20 Transfer event with a field and a tag mapping to the same abi variable",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&BadEventTransferWithEmptyTag{},
|
||||||
|
&BadEventTransferWithEmptyTag{},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"struct: abi tag in 'Value' is empty",
|
||||||
|
"Can not unpack ERC20 Transfer event with an empty tag",
|
||||||
}, {
|
}, {
|
||||||
pledgeData1,
|
pledgeData1,
|
||||||
&EventPledge{},
|
&EventPledge{},
|
||||||
|
|
@ -207,27 +311,33 @@ func TestEventTupleUnpack(t *testing.T) {
|
||||||
&[]interface{}{common.Address{}, new(big.Int)},
|
&[]interface{}{common.Address{}, new(big.Int)},
|
||||||
&[]interface{}{},
|
&[]interface{}{},
|
||||||
jsonEventPledge,
|
jsonEventPledge,
|
||||||
"abi: insufficient number of elements in the list/array for unpack, want 3, got 2",
|
"abi: insufficient number of arguments for unpack, want 3, got 2",
|
||||||
"Can not unpack Pledge event into too short slice",
|
"Can not unpack Pledge event into too short slice",
|
||||||
}, {
|
}, {
|
||||||
pledgeData1,
|
pledgeData1,
|
||||||
new(map[string]interface{}),
|
new(map[string]interface{}),
|
||||||
&[]interface{}{},
|
&[]interface{}{},
|
||||||
jsonEventPledge,
|
jsonEventPledge,
|
||||||
"abi: cannot unmarshal tuple into map[string]interface {}",
|
"abi:[2] cannot unmarshal tuple in to map[string]interface {}",
|
||||||
"Can not unpack Pledge event into map",
|
"Can not unpack Pledge event into map",
|
||||||
|
}, {
|
||||||
|
mixedCaseData1,
|
||||||
|
&EventMixedCase{},
|
||||||
|
&EventMixedCase{Value1: bigintExpected, Value2: bigintExpected2, Value3: bigintExpected3},
|
||||||
|
jsonEventMixedCase,
|
||||||
|
"",
|
||||||
|
"Can unpack abi variables with mixed case",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
tc := tc
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert)
|
err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert)
|
||||||
if tc.error == "" {
|
if tc.error == "" {
|
||||||
assert.Nil(err, "Should be able to unpack event data.")
|
assert.Nil(err, "Should be able to unpack event data.")
|
||||||
assert.Equal(tc.expected, tc.dest, tc.name)
|
assert.Equal(tc.expected, tc.dest, tc.name)
|
||||||
} else {
|
} else {
|
||||||
assert.EqualError(err, tc.error)
|
assert.EqualError(err, tc.error, tc.name)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -239,48 +349,15 @@ func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, ass
|
||||||
var e Event
|
var e Event
|
||||||
assert.NoError(json.Unmarshal(jsonEvent, &e), "Should be able to unmarshal event ABI")
|
assert.NoError(json.Unmarshal(jsonEvent, &e), "Should be able to unmarshal event ABI")
|
||||||
a := ABI{Events: map[string]Event{"e": e}}
|
a := ABI{Events: map[string]Event{"e": e}}
|
||||||
return a.Unpack(dest, "e", data)
|
return a.UnpackIntoInterface(dest, "e", data)
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Taken from
|
|
||||||
https://github.com/XinFinOrg/XDPoSChain/pull/15568
|
|
||||||
*/
|
|
||||||
|
|
||||||
type testResult struct {
|
|
||||||
Values [2]*big.Int
|
|
||||||
Value1 *big.Int
|
|
||||||
Value2 *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
type testCase struct {
|
|
||||||
definition string
|
|
||||||
want testResult
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tc testCase) encoded(intType, arrayType Type) []byte {
|
|
||||||
var b bytes.Buffer
|
|
||||||
if tc.want.Value1 != nil {
|
|
||||||
val, _ := intType.pack(reflect.ValueOf(tc.want.Value1))
|
|
||||||
b.Write(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(tc.want.Values, [2]*big.Int{nil, nil}) {
|
|
||||||
val, _ := arrayType.pack(reflect.ValueOf(tc.want.Values))
|
|
||||||
b.Write(val)
|
|
||||||
}
|
|
||||||
if tc.want.Value2 != nil {
|
|
||||||
val, _ := intType.pack(reflect.ValueOf(tc.want.Value2))
|
|
||||||
b.Write(val)
|
|
||||||
}
|
|
||||||
return b.Bytes()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder.
|
// TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder.
|
||||||
func TestEventUnpackIndexed(t *testing.T) {
|
func TestEventUnpackIndexed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
||||||
type testStruct struct {
|
type testStruct struct {
|
||||||
Value1 uint8
|
Value1 uint8 // indexed
|
||||||
Value2 uint8
|
Value2 uint8
|
||||||
}
|
}
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
|
@ -288,16 +365,17 @@ func TestEventUnpackIndexed(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
b.Write(packNum(reflect.ValueOf(uint8(8))))
|
b.Write(packNum(reflect.ValueOf(uint8(8))))
|
||||||
var rst testStruct
|
var rst testStruct
|
||||||
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
|
require.NoError(t, abi.UnpackIntoInterface(&rst, "test", b.Bytes()))
|
||||||
require.Equal(t, uint8(0), rst.Value1)
|
require.Equal(t, uint8(0), rst.Value1)
|
||||||
require.Equal(t, uint8(8), rst.Value2)
|
require.Equal(t, uint8(8), rst.Value2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestEventIndexedWithArrayUnpack verifies that decoder will not overlow when static array is indexed input.
|
// TestEventIndexedWithArrayUnpack verifies that decoder will not overflow when static array is indexed input.
|
||||||
func TestEventIndexedWithArrayUnpack(t *testing.T) {
|
func TestEventIndexedWithArrayUnpack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]`
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]`
|
||||||
type testStruct struct {
|
type testStruct struct {
|
||||||
Value1 [2]uint8
|
Value1 [2]uint8 // indexed
|
||||||
Value2 string
|
Value2 string
|
||||||
}
|
}
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
|
@ -310,7 +388,7 @@ func TestEventIndexedWithArrayUnpack(t *testing.T) {
|
||||||
b.Write(common.RightPadBytes([]byte(stringOut), 32))
|
b.Write(common.RightPadBytes([]byte(stringOut), 32))
|
||||||
|
|
||||||
var rst testStruct
|
var rst testStruct
|
||||||
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
|
require.NoError(t, abi.UnpackIntoInterface(&rst, "test", b.Bytes()))
|
||||||
require.Equal(t, [2]uint8{0, 0}, rst.Value1)
|
require.Equal(t, [2]uint8{0, 0}, rst.Value1)
|
||||||
require.Equal(t, stringOut, rst.Value2)
|
require.Equal(t, stringOut, rst.Value2)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,57 +23,144 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FunctionType represents different types of functions a contract might have.
|
||||||
|
type FunctionType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Constructor represents the constructor of the contract.
|
||||||
|
// The constructor function is called while deploying a contract.
|
||||||
|
Constructor FunctionType = iota
|
||||||
|
// Fallback represents the fallback function.
|
||||||
|
// This function is executed if no other function matches the given function
|
||||||
|
// signature and no receive function is specified.
|
||||||
|
Fallback
|
||||||
|
// Receive represents the receive function.
|
||||||
|
// This function is executed on plain Ether transfers.
|
||||||
|
Receive
|
||||||
|
// Function represents a normal function.
|
||||||
|
Function
|
||||||
|
)
|
||||||
|
|
||||||
// Method represents a callable given a `Name` and whether the method is a constant.
|
// Method represents a callable given a `Name` and whether the method is a constant.
|
||||||
// If the method is `Const` no transaction needs to be created for this
|
// 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.
|
// particular Method call. It can easily be simulated using a local VM.
|
||||||
// For example a `Balance()` method only needs to retrieve something
|
// For example a `Balance()` method only needs to retrieve something
|
||||||
// from the storage and therefor requires no Tx to be send to the
|
// from the storage and therefore requires no Tx to be sent to the
|
||||||
// network. A method such as `Transact` does require a Tx and thus will
|
// network. A method such as `Transact` does require a Tx and thus will
|
||||||
// be flagged `true`.
|
// be flagged `false`.
|
||||||
// Input specifies the required input parameters for this gives method.
|
// Input specifies the required input parameters for this gives method.
|
||||||
type Method struct {
|
type Method struct {
|
||||||
|
// Name is the method name used for internal representation. It's derived from
|
||||||
|
// the raw name and a suffix will be added in the case of a function overload.
|
||||||
|
//
|
||||||
|
// e.g.
|
||||||
|
// These are two functions that have the same name:
|
||||||
|
// * foo(int,int)
|
||||||
|
// * foo(uint,uint)
|
||||||
|
// The method name of the first one will be resolved as foo while the second one
|
||||||
|
// will be resolved as foo0.
|
||||||
Name string
|
Name string
|
||||||
Const bool
|
RawName string // RawName is the raw method name parsed from ABI
|
||||||
|
|
||||||
|
// Type indicates whether the method is a
|
||||||
|
// special fallback introduced in solidity v0.6.0
|
||||||
|
Type FunctionType
|
||||||
|
|
||||||
|
// StateMutability indicates the mutability state of method,
|
||||||
|
// the default value is nonpayable. It can be empty if the abi
|
||||||
|
// is generated by legacy compiler.
|
||||||
|
StateMutability string
|
||||||
|
|
||||||
|
// Legacy indicators generated by compiler before v0.6.0
|
||||||
|
Constant bool
|
||||||
|
Payable bool
|
||||||
|
|
||||||
Inputs Arguments
|
Inputs Arguments
|
||||||
Outputs Arguments
|
Outputs Arguments
|
||||||
|
str string
|
||||||
|
// Sig returns the methods string signature according to the ABI spec.
|
||||||
|
// e.g. function foo(uint32 a, int b) = "foo(uint32,int256)"
|
||||||
|
// Please note that "int" is substitute for its canonical representation "int256"
|
||||||
|
Sig string
|
||||||
|
// ID returns the canonical representation of the method's signature used by the
|
||||||
|
// abi definition to identify method names and types.
|
||||||
|
ID []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sig returns the methods string signature according to the ABI spec.
|
// NewMethod creates a new Method.
|
||||||
//
|
// A method should always be created using NewMethod.
|
||||||
// Example
|
// It also precomputes the sig representation and the string representation
|
||||||
//
|
// of the method.
|
||||||
// function foo(uint32 a, int b) = "foo(uint32,int256)"
|
func NewMethod(name string, rawName string, funType FunctionType, mutability string, isConst, isPayable bool, inputs Arguments, outputs Arguments) Method {
|
||||||
//
|
var (
|
||||||
// Please note that "int" is substitute for its canonical representation "int256"
|
types = make([]string, len(inputs))
|
||||||
func (method Method) Sig() string {
|
inputNames = make([]string, len(inputs))
|
||||||
types := make([]string, len(method.Inputs))
|
outputNames = make([]string, len(outputs))
|
||||||
i := 0
|
)
|
||||||
for _, input := range method.Inputs {
|
for i, input := range inputs {
|
||||||
|
inputNames[i] = fmt.Sprintf("%v %v", input.Type, input.Name)
|
||||||
types[i] = input.Type.String()
|
types[i] = input.Type.String()
|
||||||
i++
|
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%v(%v)", method.Name, strings.Join(types, ","))
|
for i, output := range outputs {
|
||||||
|
outputNames[i] = output.Type.String()
|
||||||
|
if len(output.Name) > 0 {
|
||||||
|
outputNames[i] += fmt.Sprintf(" %v", output.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// calculate the signature and method id. Note only function
|
||||||
|
// has meaningful signature and id.
|
||||||
|
var (
|
||||||
|
sig string
|
||||||
|
id []byte
|
||||||
|
)
|
||||||
|
if funType == Function {
|
||||||
|
sig = fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ","))
|
||||||
|
id = crypto.Keccak256([]byte(sig))[:4]
|
||||||
|
}
|
||||||
|
identity := fmt.Sprintf("function %v", rawName)
|
||||||
|
switch funType {
|
||||||
|
case Fallback:
|
||||||
|
identity = "fallback"
|
||||||
|
case Receive:
|
||||||
|
identity = "receive"
|
||||||
|
case Constructor:
|
||||||
|
identity = "constructor"
|
||||||
|
}
|
||||||
|
var str string
|
||||||
|
// Extract meaningful state mutability of solidity method.
|
||||||
|
// If it's empty string or default value "nonpayable", never print it.
|
||||||
|
if mutability == "" || mutability == "nonpayable" {
|
||||||
|
str = fmt.Sprintf("%v(%v) returns(%v)", identity, strings.Join(inputNames, ", "), strings.Join(outputNames, ", "))
|
||||||
|
} else {
|
||||||
|
str = fmt.Sprintf("%v(%v) %s returns(%v)", identity, strings.Join(inputNames, ", "), mutability, strings.Join(outputNames, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Method{
|
||||||
|
Name: name,
|
||||||
|
RawName: rawName,
|
||||||
|
Type: funType,
|
||||||
|
StateMutability: mutability,
|
||||||
|
Constant: isConst,
|
||||||
|
Payable: isPayable,
|
||||||
|
Inputs: inputs,
|
||||||
|
Outputs: outputs,
|
||||||
|
str: str,
|
||||||
|
Sig: sig,
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (method Method) String() string {
|
func (method Method) String() string {
|
||||||
inputs := make([]string, len(method.Inputs))
|
return method.str
|
||||||
for i, input := range method.Inputs {
|
|
||||||
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
|
|
||||||
}
|
|
||||||
outputs := make([]string, len(method.Outputs))
|
|
||||||
for i, output := range method.Outputs {
|
|
||||||
if len(output.Name) > 0 {
|
|
||||||
outputs[i] = fmt.Sprintf("%v ", output.Name)
|
|
||||||
}
|
|
||||||
outputs[i] += output.Type.String()
|
|
||||||
}
|
|
||||||
constant := ""
|
|
||||||
if method.Const {
|
|
||||||
constant = "constant "
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.Name, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (method Method) Id() []byte {
|
// IsConstant returns the indicator whether the method is read-only.
|
||||||
return crypto.Keccak256([]byte(method.Sig()))[:4]
|
func (method Method) IsConstant() bool {
|
||||||
|
return method.StateMutability == "view" || method.StateMutability == "pure" || method.Constant
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPayable returns the indicator whether the method can process
|
||||||
|
// plain ether transfers.
|
||||||
|
func (method Method) IsPayable() bool {
|
||||||
|
return method.StateMutability == "payable" || method.Payable
|
||||||
}
|
}
|
||||||
|
|
|
||||||
148
accounts/abi/method_test.go
Normal file
148
accounts/abi/method_test.go
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const methoddata = `
|
||||||
|
[
|
||||||
|
{"type": "function", "name": "balance", "stateMutability": "view"},
|
||||||
|
{"type": "function", "name": "send", "inputs": [{ "name": "amount", "type": "uint256" }]},
|
||||||
|
{"type": "function", "name": "transfer", "inputs": [{"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}], "outputs": [{"name": "success", "type": "bool"}]},
|
||||||
|
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple"}],"name":"tuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||||
|
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[]"}],"name":"tupleSlice","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||||
|
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5]"}],"name":"tupleArray","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||||
|
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5][]"}],"name":"complexTuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||||
|
{"stateMutability":"nonpayable","type":"fallback"},
|
||||||
|
{"stateMutability":"payable","type":"receive"}
|
||||||
|
]`
|
||||||
|
|
||||||
|
func TestMethodString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var table = []struct {
|
||||||
|
method string
|
||||||
|
expectation string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
method: "balance",
|
||||||
|
expectation: "function balance() view returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "send",
|
||||||
|
expectation: "function send(uint256 amount) returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "transfer",
|
||||||
|
expectation: "function transfer(address from, address to, uint256 value) returns(bool success)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tuple",
|
||||||
|
expectation: "function tuple((uint256,uint256) a) returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tupleArray",
|
||||||
|
expectation: "function tupleArray((uint256,uint256)[5] a) returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tupleSlice",
|
||||||
|
expectation: "function tupleSlice((uint256,uint256)[] a) returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "complexTuple",
|
||||||
|
expectation: "function complexTuple((uint256,uint256)[5][] a) returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "fallback",
|
||||||
|
expectation: "fallback() returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "receive",
|
||||||
|
expectation: "receive() payable returns()",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
abi, err := JSON(strings.NewReader(methoddata))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
var got string
|
||||||
|
switch test.method {
|
||||||
|
case "fallback":
|
||||||
|
got = abi.Fallback.String()
|
||||||
|
case "receive":
|
||||||
|
got = abi.Receive.String()
|
||||||
|
default:
|
||||||
|
got = abi.Methods[test.method].String()
|
||||||
|
}
|
||||||
|
if got != test.expectation {
|
||||||
|
t.Errorf("expected string to be %s, got %s", test.expectation, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMethodSig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var cases = []struct {
|
||||||
|
method string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
method: "balance",
|
||||||
|
expect: "balance()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "send",
|
||||||
|
expect: "send(uint256)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "transfer",
|
||||||
|
expect: "transfer(address,address,uint256)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tuple",
|
||||||
|
expect: "tuple((uint256,uint256))",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tupleArray",
|
||||||
|
expect: "tupleArray((uint256,uint256)[5])",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tupleSlice",
|
||||||
|
expect: "tupleSlice((uint256,uint256)[])",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "complexTuple",
|
||||||
|
expect: "complexTuple((uint256,uint256)[5][])",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
abi, err := JSON(strings.NewReader(methoddata))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range cases {
|
||||||
|
got := abi.Methods[test.method].Sig
|
||||||
|
if got != test.expect {
|
||||||
|
t.Errorf("expected string to be %s, got %s", test.expect, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package abi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
big_t = reflect.TypeOf(&big.Int{})
|
|
||||||
derefbig_t = reflect.TypeOf(big.Int{})
|
|
||||||
uint8_t = reflect.TypeOf(uint8(0))
|
|
||||||
uint16_t = reflect.TypeOf(uint16(0))
|
|
||||||
uint32_t = reflect.TypeOf(uint32(0))
|
|
||||||
uint64_t = reflect.TypeOf(uint64(0))
|
|
||||||
int_t = reflect.TypeOf(int(0))
|
|
||||||
int8_t = reflect.TypeOf(int8(0))
|
|
||||||
int16_t = reflect.TypeOf(int16(0))
|
|
||||||
int32_t = reflect.TypeOf(int32(0))
|
|
||||||
int64_t = reflect.TypeOf(int64(0))
|
|
||||||
address_t = reflect.TypeOf(common.Address{})
|
|
||||||
int_ts = reflect.TypeOf([]int(nil))
|
|
||||||
int8_ts = reflect.TypeOf([]int8(nil))
|
|
||||||
int16_ts = reflect.TypeOf([]int16(nil))
|
|
||||||
int32_ts = reflect.TypeOf([]int32(nil))
|
|
||||||
int64_ts = reflect.TypeOf([]int64(nil))
|
|
||||||
)
|
|
||||||
|
|
||||||
// checks whether the given reflect value is signed. This also works for slices with a number type
|
|
||||||
func isSigned(v reflect.Value) bool {
|
|
||||||
switch v.Type() {
|
|
||||||
case int_ts, int8_ts, int16_ts, int32_ts, int64_ts, int_t, int8_t, int16_t, int32_t, int64_t:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
|
@ -25,7 +27,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// packBytesSlice packs the given bytes as [L, V] as the canonical representation
|
// packBytesSlice packs the given bytes as [L, V] as the canonical representation
|
||||||
// bytes slice
|
// bytes slice.
|
||||||
func packBytesSlice(bytes []byte, l int) []byte {
|
func packBytesSlice(bytes []byte, l int) []byte {
|
||||||
len := packNum(reflect.ValueOf(l))
|
len := packNum(reflect.ValueOf(l))
|
||||||
return append(len, common.RightPadBytes(bytes, (l+31)/32*32)...)
|
return append(len, common.RightPadBytes(bytes, (l+31)/32*32)...)
|
||||||
|
|
@ -33,39 +35,42 @@ func packBytesSlice(bytes []byte, l int) []byte {
|
||||||
|
|
||||||
// packElement packs the given reflect value according to the abi specification in
|
// packElement packs the given reflect value according to the abi specification in
|
||||||
// t.
|
// t.
|
||||||
func packElement(t Type, reflectValue reflect.Value) []byte {
|
func packElement(t Type, reflectValue reflect.Value) ([]byte, error) {
|
||||||
switch t.T {
|
switch t.T {
|
||||||
case IntTy, UintTy:
|
case IntTy, UintTy:
|
||||||
return packNum(reflectValue)
|
return packNum(reflectValue), nil
|
||||||
case StringTy:
|
case StringTy:
|
||||||
return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len())
|
return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len()), nil
|
||||||
case AddressTy:
|
case AddressTy:
|
||||||
if reflectValue.Kind() == reflect.Array {
|
if reflectValue.Kind() == reflect.Array {
|
||||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
return common.LeftPadBytes(reflectValue.Bytes(), 32)
|
return common.LeftPadBytes(reflectValue.Bytes(), 32), nil
|
||||||
case BoolTy:
|
case BoolTy:
|
||||||
if reflectValue.Bool() {
|
if reflectValue.Bool() {
|
||||||
return math.PaddedBigBytes(common.Big1, 32)
|
return math.PaddedBigBytes(common.Big1, 32), nil
|
||||||
}
|
}
|
||||||
return math.PaddedBigBytes(common.Big0, 32)
|
return math.PaddedBigBytes(common.Big0, 32), nil
|
||||||
case BytesTy:
|
case BytesTy:
|
||||||
if reflectValue.Kind() == reflect.Array {
|
if reflectValue.Kind() == reflect.Array {
|
||||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||||
}
|
}
|
||||||
return packBytesSlice(reflectValue.Bytes(), reflectValue.Len())
|
if reflectValue.Type() != reflect.TypeOf([]byte{}) {
|
||||||
|
return []byte{}, errors.New("bytes type is neither slice nor array")
|
||||||
|
}
|
||||||
|
return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil
|
||||||
case FixedBytesTy, FunctionTy:
|
case FixedBytesTy, FunctionTy:
|
||||||
if reflectValue.Kind() == reflect.Array {
|
if reflectValue.Kind() == reflect.Array {
|
||||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||||
}
|
}
|
||||||
return common.RightPadBytes(reflectValue.Bytes(), 32)
|
return common.RightPadBytes(reflectValue.Bytes(), 32), nil
|
||||||
default:
|
default:
|
||||||
panic("abi: fatal error")
|
return []byte{}, fmt.Errorf("could not pack element, unknown type: %v", t.T)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation
|
// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation.
|
||||||
func packNum(value reflect.Value) []byte {
|
func packNum(value reflect.Value) []byte {
|
||||||
switch kind := value.Kind(); kind {
|
switch kind := value.Kind(); kind {
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
|
@ -73,9 +78,8 @@ func packNum(value reflect.Value) []byte {
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
return math.U256Bytes(big.NewInt(value.Int()))
|
return math.U256Bytes(big.NewInt(value.Int()))
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
return math.U256Bytes(value.Interface().(*big.Int))
|
return math.U256Bytes(new(big.Int).Set(value.Interface().(*big.Int)))
|
||||||
default:
|
default:
|
||||||
panic("abi: fatal error")
|
panic("abi: fatal error")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,336 +18,54 @@ package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TestPack tests the general pack/unpack tests in packing_test.go
|
||||||
func TestPack(t *testing.T) {
|
func TestPack(t *testing.T) {
|
||||||
for i, test := range []struct {
|
t.Parallel()
|
||||||
typ string
|
for i, test := range packUnpackTests {
|
||||||
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
encb, err := hex.DecodeString(test.packed)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid hex %s: %v", test.packed, err)
|
||||||
|
}
|
||||||
|
inDef := fmt.Sprintf(`[{ "name" : "method", "type": "function", "inputs": %s}]`, test.def)
|
||||||
|
inAbi, err := JSON(strings.NewReader(inDef))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid ABI definition %s, %v", inDef, err)
|
||||||
|
}
|
||||||
|
var packed []byte
|
||||||
|
packed, err = inAbi.Pack("method", test.unpacked)
|
||||||
|
|
||||||
input interface{}
|
if err != nil {
|
||||||
output []byte
|
t.Fatalf("test %d (%v) failed: %v", i, test.def, err)
|
||||||
}{
|
}
|
||||||
{
|
if !reflect.DeepEqual(packed[4:], encb) {
|
||||||
"uint8",
|
t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, encb, packed[4:])
|
||||||
uint8(2),
|
}
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
})
|
||||||
},
|
|
||||||
{
|
|
||||||
"uint8[]",
|
|
||||||
[]uint8{1, 2},
|
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uint16",
|
|
||||||
uint16(2),
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uint16[]",
|
|
||||||
[]uint16{1, 2},
|
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uint32",
|
|
||||||
uint32(2),
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uint32[]",
|
|
||||||
[]uint32{1, 2},
|
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uint64",
|
|
||||||
uint64(2),
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uint64[]",
|
|
||||||
[]uint64{1, 2},
|
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uint256",
|
|
||||||
big.NewInt(2),
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uint256[]",
|
|
||||||
[]*big.Int{big.NewInt(1), big.NewInt(2)},
|
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"int8",
|
|
||||||
int8(2),
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"int8[]",
|
|
||||||
[]int8{1, 2},
|
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"int16",
|
|
||||||
int16(2),
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"int16[]",
|
|
||||||
[]int16{1, 2},
|
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"int32",
|
|
||||||
int32(2),
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"int32[]",
|
|
||||||
[]int32{1, 2},
|
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"int64",
|
|
||||||
int64(2),
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"int64[]",
|
|
||||||
[]int64{1, 2},
|
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"int256",
|
|
||||||
big.NewInt(2),
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"int256[]",
|
|
||||||
[]*big.Int{big.NewInt(1), big.NewInt(2)},
|
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes1",
|
|
||||||
[1]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes2",
|
|
||||||
[2]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes3",
|
|
||||||
[3]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes4",
|
|
||||||
[4]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes5",
|
|
||||||
[5]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes6",
|
|
||||||
[6]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes7",
|
|
||||||
[7]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes8",
|
|
||||||
[8]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes9",
|
|
||||||
[9]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes10",
|
|
||||||
[10]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes11",
|
|
||||||
[11]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes12",
|
|
||||||
[12]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes13",
|
|
||||||
[13]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes14",
|
|
||||||
[14]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes15",
|
|
||||||
[15]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes16",
|
|
||||||
[16]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes17",
|
|
||||||
[17]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes18",
|
|
||||||
[18]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes19",
|
|
||||||
[19]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes20",
|
|
||||||
[20]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes21",
|
|
||||||
[21]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes22",
|
|
||||||
[22]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes23",
|
|
||||||
[23]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes24",
|
|
||||||
[24]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes24",
|
|
||||||
[24]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes25",
|
|
||||||
[25]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes26",
|
|
||||||
[26]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes27",
|
|
||||||
[27]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes28",
|
|
||||||
[28]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes29",
|
|
||||||
[29]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes30",
|
|
||||||
[30]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes31",
|
|
||||||
[31]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes32",
|
|
||||||
[32]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uint32[2][3][4]",
|
|
||||||
[4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}},
|
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000018"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"address[]",
|
|
||||||
[]common.Address{{1}, {2}},
|
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes32[]",
|
|
||||||
[]common.Hash{{1}, {2}},
|
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000201000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"function",
|
|
||||||
[24]byte{1},
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"string",
|
|
||||||
"foobar",
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
typ, err := NewType(test.typ)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v failed. Unexpected parse error: %v", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := typ.pack(reflect.ValueOf(test.input))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v failed. Unexpected pack error: %v", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(output, test.output) {
|
|
||||||
t.Errorf("%d failed. Expected bytes: '%x' Got: '%x'", i, test.output, output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMethodPack(t *testing.T) {
|
func TestMethodPack(t *testing.T) {
|
||||||
abi, err := JSON(strings.NewReader(jsondata2))
|
t.Parallel()
|
||||||
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sig := abi.Methods["slice"].Id()
|
sig := abi.Methods["slice"].ID
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
|
||||||
|
|
@ -361,7 +79,7 @@ func TestMethodPack(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var addrA, addrB = common.Address{1}, common.Address{2}
|
var addrA, addrB = common.Address{1}, common.Address{2}
|
||||||
sig = abi.Methods["sliceAddress"].Id()
|
sig = abi.Methods["sliceAddress"].ID
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
|
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
|
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
|
||||||
|
|
@ -376,7 +94,7 @@ func TestMethodPack(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var addrC, addrD = common.Address{3}, common.Address{4}
|
var addrC, addrD = common.Address{3}, common.Address{4}
|
||||||
sig = abi.Methods["sliceMultiAddress"].Id()
|
sig = abi.Methods["sliceMultiAddress"].ID
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...)
|
sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...)
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...)
|
sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...)
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
|
@ -394,7 +112,7 @@ func TestMethodPack(t *testing.T) {
|
||||||
t.Errorf("expected %x got %x", sig, packed)
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
}
|
}
|
||||||
|
|
||||||
sig = abi.Methods["slice256"].Id()
|
sig = abi.Methods["slice256"].ID
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
|
||||||
|
|
@ -406,9 +124,63 @@ func TestMethodPack(t *testing.T) {
|
||||||
if !bytes.Equal(packed, sig) {
|
if !bytes.Equal(packed, sig) {
|
||||||
t.Errorf("expected %x got %x", sig, packed)
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a := [2][2]*big.Int{{big.NewInt(1), big.NewInt(1)}, {big.NewInt(2), big.NewInt(0)}}
|
||||||
|
sig = abi.Methods["nestedArray"].ID
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0xa0}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrC[:], 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrD[:], 32)...)
|
||||||
|
packed, err = abi.Pack("nestedArray", a, []common.Address{addrC, addrD})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig = abi.Methods["nestedArray2"].ID
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x80}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
packed, err = abi.Pack("nestedArray2", [2][]uint8{{1}, {1}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig = abi.Methods["nestedSlice"].ID
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x02}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0xa0}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
packed, err = abi.Pack("nestedSlice", [][]uint8{{1, 2}, {1, 2}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPackNumber(t *testing.T) {
|
func TestPackNumber(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
value reflect.Value
|
value reflect.Value
|
||||||
packed []byte
|
packed []byte
|
||||||
|
|
|
||||||
990
accounts/abi/packing_test.go
Normal file
990
accounts/abi/packing_test.go
Normal file
|
|
@ -0,0 +1,990 @@
|
||||||
|
// Copyright 2020 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type packUnpackTest struct {
|
||||||
|
def string
|
||||||
|
unpacked interface{}
|
||||||
|
packed string
|
||||||
|
}
|
||||||
|
|
||||||
|
var packUnpackTests = []packUnpackTest{
|
||||||
|
// Booleans
|
||||||
|
{
|
||||||
|
def: `[{ "type": "bool" }]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
unpacked: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{ "type": "bool" }]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
unpacked: false,
|
||||||
|
},
|
||||||
|
// Integers
|
||||||
|
{
|
||||||
|
def: `[{ "type": "uint8" }]`,
|
||||||
|
unpacked: uint8(2),
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{ "type": "uint8[]" }]`,
|
||||||
|
unpacked: []uint8{1, 2},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{ "type": "uint16" }]`,
|
||||||
|
unpacked: uint16(2),
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{ "type": "uint16[]" }]`,
|
||||||
|
unpacked: []uint16{1, 2},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint17"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
unpacked: big.NewInt(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint32"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
unpacked: uint32(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint32[]"}]`,
|
||||||
|
unpacked: []uint32{1, 2},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint64"}]`,
|
||||||
|
unpacked: uint64(2),
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint64[]"}]`,
|
||||||
|
unpacked: []uint64{1, 2},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint256"}]`,
|
||||||
|
unpacked: big.NewInt(2),
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint256[]"}]`,
|
||||||
|
unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int8"}]`,
|
||||||
|
unpacked: int8(2),
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int8[]"}]`,
|
||||||
|
unpacked: []int8{1, 2},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int16"}]`,
|
||||||
|
unpacked: int16(2),
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int16[]"}]`,
|
||||||
|
unpacked: []int16{1, 2},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int17"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
unpacked: big.NewInt(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int32"}]`,
|
||||||
|
unpacked: int32(2),
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int32"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
unpacked: int32(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int32[]"}]`,
|
||||||
|
unpacked: []int32{1, 2},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int64"}]`,
|
||||||
|
unpacked: int64(2),
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int64[]"}]`,
|
||||||
|
unpacked: []int64{1, 2},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int256"}]`,
|
||||||
|
unpacked: big.NewInt(2),
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int256"}]`,
|
||||||
|
packed: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
unpacked: big.NewInt(-1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int256[]"}]`,
|
||||||
|
unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
},
|
||||||
|
// Address
|
||||||
|
{
|
||||||
|
def: `[{"type": "address"}]`,
|
||||||
|
packed: "0000000000000000000000000100000000000000000000000000000000000000",
|
||||||
|
unpacked: common.Address{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "address[]"}]`,
|
||||||
|
unpacked: []common.Address{{1}, {2}},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000100000000000000000000000000000000000000" +
|
||||||
|
"0000000000000000000000000200000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
// Bytes
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes1"}]`,
|
||||||
|
unpacked: [1]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes2"}]`,
|
||||||
|
unpacked: [2]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes3"}]`,
|
||||||
|
unpacked: [3]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes4"}]`,
|
||||||
|
unpacked: [4]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes5"}]`,
|
||||||
|
unpacked: [5]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes6"}]`,
|
||||||
|
unpacked: [6]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes7"}]`,
|
||||||
|
unpacked: [7]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes8"}]`,
|
||||||
|
unpacked: [8]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes9"}]`,
|
||||||
|
unpacked: [9]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes10"}]`,
|
||||||
|
unpacked: [10]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes11"}]`,
|
||||||
|
unpacked: [11]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes12"}]`,
|
||||||
|
unpacked: [12]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes13"}]`,
|
||||||
|
unpacked: [13]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes14"}]`,
|
||||||
|
unpacked: [14]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes15"}]`,
|
||||||
|
unpacked: [15]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes16"}]`,
|
||||||
|
unpacked: [16]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes17"}]`,
|
||||||
|
unpacked: [17]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes18"}]`,
|
||||||
|
unpacked: [18]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes19"}]`,
|
||||||
|
unpacked: [19]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes20"}]`,
|
||||||
|
unpacked: [20]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes21"}]`,
|
||||||
|
unpacked: [21]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes22"}]`,
|
||||||
|
unpacked: [22]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes23"}]`,
|
||||||
|
unpacked: [23]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes24"}]`,
|
||||||
|
unpacked: [24]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes25"}]`,
|
||||||
|
unpacked: [25]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes26"}]`,
|
||||||
|
unpacked: [26]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes27"}]`,
|
||||||
|
unpacked: [27]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes28"}]`,
|
||||||
|
unpacked: [28]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes29"}]`,
|
||||||
|
unpacked: [29]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes30"}]`,
|
||||||
|
unpacked: [30]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes31"}]`,
|
||||||
|
unpacked: [31]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes32"}]`,
|
||||||
|
unpacked: [32]byte{1},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes32"}]`,
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
unpacked: [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
unpacked: common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes32"}]`,
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
unpacked: [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
// Functions
|
||||||
|
{
|
||||||
|
def: `[{"type": "function"}]`,
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
unpacked: [24]byte{1},
|
||||||
|
},
|
||||||
|
// Slice and Array
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: []uint8{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
unpacked: []uint8{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint256[]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
unpacked: []*big.Int{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[2]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: [2]uint8{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int8[2]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: [2]int8{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int16[]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: []int16{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int16[2]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: [2]int16{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int32[]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: []int32{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int32[2]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: [2]int32{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int64[]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: []int64{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int64[2]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: [2]int64{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int256[]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int256[3]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003",
|
||||||
|
unpacked: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)},
|
||||||
|
},
|
||||||
|
// multi dimensional, if these pass, all types that don't require length prefix should pass
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[][]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
unpacked: [][]uint8{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[][]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" +
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000a0" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: [][]uint8{{1, 2}, {1, 2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[][]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" +
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000a0" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003",
|
||||||
|
unpacked: [][]uint8{{1, 2}, {1, 2, 3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[2][2]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: [2][2]uint8{{1, 2}, {1, 2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[][2]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000060" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
unpacked: [2][]uint8{{}, {}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[][2]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000080" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
unpacked: [2][]uint8{{1}, {1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[2][]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
unpacked: [][2]uint8{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[2][]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: [][2]uint8{{1, 2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[2][]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: [][2]uint8{{1, 2}, {1, 2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint16[]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: []uint16{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint16[2]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: [2]uint16{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint32[]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: []uint32{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint32[2][3][4]"}]`,
|
||||||
|
unpacked: [4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000004" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000005" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000006" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000007" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000008" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000009" +
|
||||||
|
"000000000000000000000000000000000000000000000000000000000000000a" +
|
||||||
|
"000000000000000000000000000000000000000000000000000000000000000b" +
|
||||||
|
"000000000000000000000000000000000000000000000000000000000000000c" +
|
||||||
|
"000000000000000000000000000000000000000000000000000000000000000d" +
|
||||||
|
"000000000000000000000000000000000000000000000000000000000000000e" +
|
||||||
|
"000000000000000000000000000000000000000000000000000000000000000f" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000010" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000011" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000012" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000013" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000014" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000015" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000016" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000017" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000018",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes32[]"}]`,
|
||||||
|
unpacked: [][32]byte{{1}, {2}},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000" +
|
||||||
|
"0200000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint32[2]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: [2]uint32{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint64[]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: []uint64{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint64[2]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: [2]uint64{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint256[]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint256[3]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003",
|
||||||
|
unpacked: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "string[4]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000080" +
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000c0" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000100" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000140" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000005" +
|
||||||
|
"48656c6c6f000000000000000000000000000000000000000000000000000000" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000005" +
|
||||||
|
"576f726c64000000000000000000000000000000000000000000000000000000" +
|
||||||
|
"000000000000000000000000000000000000000000000000000000000000000b" +
|
||||||
|
"476f2d657468657265756d000000000000000000000000000000000000000000" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000008" +
|
||||||
|
"457468657265756d000000000000000000000000000000000000000000000000",
|
||||||
|
unpacked: [4]string{"Hello", "World", "Go-ethereum", "Ethereum"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "string[]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000080" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000008" +
|
||||||
|
"457468657265756d000000000000000000000000000000000000000000000000" +
|
||||||
|
"000000000000000000000000000000000000000000000000000000000000000b" +
|
||||||
|
"676f2d657468657265756d000000000000000000000000000000000000000000",
|
||||||
|
unpacked: []string{"Ethereum", "go-ethereum"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes[]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000080" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" +
|
||||||
|
"f0f0f00000000000000000000000000000000000000000000000000000000000" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" +
|
||||||
|
"f0f0f00000000000000000000000000000000000000000000000000000000000",
|
||||||
|
unpacked: [][]byte{{0xf0, 0xf0, 0xf0}, {0xf0, 0xf0, 0xf0}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint256[2][][]"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" +
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000e0" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000c8" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000003e8" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000c8" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000003e8",
|
||||||
|
unpacked: [][][2]*big.Int{{{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}, {{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}},
|
||||||
|
},
|
||||||
|
// struct outputs
|
||||||
|
{
|
||||||
|
def: `[{"components": [{"name":"int1","type":"int256"},{"name":"int2","type":"int256"}], "type":"tuple"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: struct {
|
||||||
|
Int1 *big.Int
|
||||||
|
Int2 *big.Int
|
||||||
|
}{big.NewInt(1), big.NewInt(2)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"components": [{"name":"int_one","type":"int256"}], "type":"tuple"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
unpacked: struct {
|
||||||
|
IntOne *big.Int
|
||||||
|
}{big.NewInt(1)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"components": [{"name":"int__one","type":"int256"}], "type":"tuple"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
unpacked: struct {
|
||||||
|
IntOne *big.Int
|
||||||
|
}{big.NewInt(1)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"components": [{"name":"int_one_","type":"int256"}], "type":"tuple"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
unpacked: struct {
|
||||||
|
IntOne *big.Int
|
||||||
|
}{big.NewInt(1)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"components": [{"name":"int_one","type":"int256"}, {"name":"intone","type":"int256"}], "type":"tuple"}]`,
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
unpacked: struct {
|
||||||
|
IntOne *big.Int
|
||||||
|
Intone *big.Int
|
||||||
|
}{big.NewInt(1), big.NewInt(2)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "string"}]`,
|
||||||
|
unpacked: "foobar",
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000006" +
|
||||||
|
"666f6f6261720000000000000000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "string[]"}]`,
|
||||||
|
unpacked: []string{"hello", "foobar"},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000080" + // offset 128 to i = 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
|
||||||
|
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
|
||||||
|
"666f6f6261720000000000000000000000000000000000000000000000000000", // str[1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "string[2]"}]`,
|
||||||
|
unpacked: [2]string{"hello", "foobar"},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // offset to i = 0
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000080" + // offset to i = 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
|
||||||
|
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
|
||||||
|
"666f6f6261720000000000000000000000000000000000000000000000000000", // str[1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes32[][]"}]`,
|
||||||
|
unpacked: [][][32]byte{{{1}, {2}}, {{3}, {4}, {5}}},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
|
||||||
|
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
|
||||||
|
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
|
||||||
|
"0500000000000000000000000000000000000000000000000000000000000000", // array[1][2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes32[][2]"}]`,
|
||||||
|
unpacked: [2][][32]byte{{{1}, {2}}, {{3}, {4}, {5}}},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
|
||||||
|
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
|
||||||
|
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
|
||||||
|
"0500000000000000000000000000000000000000000000000000000000000000", // array[1][2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes32[3][2]"}]`,
|
||||||
|
unpacked: [2][3][32]byte{{{1}, {2}, {3}}, {{3}, {4}, {5}}},
|
||||||
|
packed: "0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
|
||||||
|
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // array[0][2]
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
|
||||||
|
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
|
||||||
|
"0500000000000000000000000000000000000000000000000000000000000000", // array[1][2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// static tuple
|
||||||
|
def: `[{"components": [{"name":"a","type":"int64"},
|
||||||
|
{"name":"b","type":"int256"},
|
||||||
|
{"name":"c","type":"int256"},
|
||||||
|
{"name":"d","type":"bool"},
|
||||||
|
{"name":"e","type":"bytes32[3][2]"}], "type":"tuple"}]`,
|
||||||
|
unpacked: struct {
|
||||||
|
A int64
|
||||||
|
B *big.Int
|
||||||
|
C *big.Int
|
||||||
|
D bool
|
||||||
|
E [2][3][32]byte
|
||||||
|
}{1, big.NewInt(1), big.NewInt(-1), true, [2][3][32]byte{{{1}, {2}, {3}}, {{3}, {4}, {5}}}},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000001" + // struct[a]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[b]
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // struct[c]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[d]
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][0]
|
||||||
|
"0200000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][1]
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][2]
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][0]
|
||||||
|
"0400000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][1]
|
||||||
|
"0500000000000000000000000000000000000000000000000000000000000000", // struct[e] array[1][2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"components": [{"name":"a","type":"string"},
|
||||||
|
{"name":"b","type":"int64"},
|
||||||
|
{"name":"c","type":"bytes"},
|
||||||
|
{"name":"d","type":"string[]"},
|
||||||
|
{"name":"e","type":"int256[]"},
|
||||||
|
{"name":"f","type":"address[]"}], "type":"tuple"}]`,
|
||||||
|
unpacked: struct {
|
||||||
|
A string
|
||||||
|
B int64
|
||||||
|
C []byte
|
||||||
|
D []string
|
||||||
|
E []*big.Int
|
||||||
|
F []common.Address
|
||||||
|
}{"foobar", 1, []byte{1}, []string{"foo", "bar"}, []*big.Int{big.NewInt(1), big.NewInt(-1)}, []common.Address{{1}, {2}}},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" + // struct a
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[b]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000100" + // struct[c] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000140" + // struct[d] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000220" + // struct[e] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000280" + // struct[f] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000006" + // struct[a] length
|
||||||
|
"666f6f6261720000000000000000000000000000000000000000000000000000" + // struct[a] "foobar"
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[c] length
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000" + // []byte{1}
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[d] length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // foo offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000080" + // bar offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // foo length
|
||||||
|
"666f6f0000000000000000000000000000000000000000000000000000000000" + // foo
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // bar offset
|
||||||
|
"6261720000000000000000000000000000000000000000000000000000000000" + // bar
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[e] length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // 1
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // -1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[f] length
|
||||||
|
"0000000000000000000000000100000000000000000000000000000000000000" + // common.Address{1}
|
||||||
|
"0000000000000000000000000200000000000000000000000000000000000000", // common.Address{2}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"components": [{ "type": "tuple","components": [{"name": "a","type": "uint256"},
|
||||||
|
{"name": "b","type": "uint256[]"}],
|
||||||
|
"name": "a","type": "tuple"},
|
||||||
|
{"name": "b","type": "uint256[]"}], "type": "tuple"}]`,
|
||||||
|
unpacked: struct {
|
||||||
|
A struct {
|
||||||
|
A *big.Int
|
||||||
|
B []*big.Int
|
||||||
|
}
|
||||||
|
B []*big.Int
|
||||||
|
}{
|
||||||
|
A: struct {
|
||||||
|
A *big.Int
|
||||||
|
B []*big.Int
|
||||||
|
}{big.NewInt(1), []*big.Int{big.NewInt(1), big.NewInt(2)}},
|
||||||
|
B: []*big.Int{big.NewInt(1), big.NewInt(2)}},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" + // struct a
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // a offset
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000e0" + // b offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // a.a value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // a.b offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // a.b length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // a.b[0] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // a.b[1] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // b length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // b[0] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002", // b[1] value
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
def: `[{"components": [{"name": "a","type": "int256"},
|
||||||
|
{"name": "b","type": "int256[]"}],
|
||||||
|
"name": "a","type": "tuple[]"}]`,
|
||||||
|
unpacked: []struct {
|
||||||
|
A *big.Int
|
||||||
|
B []*big.Int
|
||||||
|
}{
|
||||||
|
{big.NewInt(-1), []*big.Int{big.NewInt(1), big.NewInt(3)}},
|
||||||
|
{big.NewInt(1), []*big.Int{big.NewInt(2), big.NewInt(-1)}},
|
||||||
|
},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000e0" + // tuple[1] offset
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0].B offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].B length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].B[0] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // tuple[0].B[1] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[1].B offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B[0] value
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // tuple[1].B[1] value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"components": [{"name": "a","type": "int256"},
|
||||||
|
{"name": "b","type": "int256"}],
|
||||||
|
"name": "a","type": "tuple[2]"}]`,
|
||||||
|
unpacked: [2]struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}{
|
||||||
|
{big.NewInt(-1), big.NewInt(1)},
|
||||||
|
{big.NewInt(1), big.NewInt(-1)},
|
||||||
|
},
|
||||||
|
packed: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].a
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].b
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].a
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // tuple[1].b
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"components": [{"name": "a","type": "int256[]"}],
|
||||||
|
"name": "a","type": "tuple[2]"}]`,
|
||||||
|
unpacked: [2]struct {
|
||||||
|
A []*big.Int
|
||||||
|
}{
|
||||||
|
{[]*big.Int{big.NewInt(-1), big.NewInt(1)}},
|
||||||
|
{[]*big.Int{big.NewInt(1), big.NewInt(-1)}},
|
||||||
|
},
|
||||||
|
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000c0" + // tuple[1] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000020" + // tuple[0].A offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].A length
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A[0]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].A[1]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000020" + // tuple[1].A offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].A length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A[0]
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // tuple[1].A[1]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -19,47 +19,75 @@ package abi
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ConvertType converts an interface of a runtime type into an interface of the
|
||||||
|
// given type, e.g. turn this code:
|
||||||
|
//
|
||||||
|
// var fields []reflect.StructField
|
||||||
|
//
|
||||||
|
// fields = append(fields, reflect.StructField{
|
||||||
|
// Name: "X",
|
||||||
|
// Type: reflect.TypeOf(new(big.Int)),
|
||||||
|
// Tag: reflect.StructTag("json:\"" + "x" + "\""),
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// into:
|
||||||
|
//
|
||||||
|
// type TupleT struct { X *big.Int }
|
||||||
|
func ConvertType(in interface{}, proto interface{}) interface{} {
|
||||||
|
protoType := reflect.TypeOf(proto)
|
||||||
|
if reflect.TypeOf(in).ConvertibleTo(protoType) {
|
||||||
|
return reflect.ValueOf(in).Convert(protoType).Interface()
|
||||||
|
}
|
||||||
|
// Use set as a last ditch effort
|
||||||
|
if err := set(reflect.ValueOf(proto), reflect.ValueOf(in)); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return proto
|
||||||
|
}
|
||||||
|
|
||||||
// indirect recursively dereferences the value until it either gets the value
|
// indirect recursively dereferences the value until it either gets the value
|
||||||
// or finds a big.Int
|
// or finds a big.Int
|
||||||
func indirect(v reflect.Value) reflect.Value {
|
func indirect(v reflect.Value) reflect.Value {
|
||||||
if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbig_t {
|
if v.Kind() == reflect.Ptr && v.Elem().Type() != reflect.TypeOf(big.Int{}) {
|
||||||
return indirect(v.Elem())
|
return indirect(v.Elem())
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// reflectIntKind returns the reflect using the given size and
|
// reflectIntType returns the reflect using the given size and
|
||||||
// unsignedness.
|
// unsignedness.
|
||||||
func reflectIntKindAndType(unsigned bool, size int) (reflect.Kind, reflect.Type) {
|
func reflectIntType(unsigned bool, size int) reflect.Type {
|
||||||
|
if unsigned {
|
||||||
|
switch size {
|
||||||
|
case 8:
|
||||||
|
return reflect.TypeOf(uint8(0))
|
||||||
|
case 16:
|
||||||
|
return reflect.TypeOf(uint16(0))
|
||||||
|
case 32:
|
||||||
|
return reflect.TypeOf(uint32(0))
|
||||||
|
case 64:
|
||||||
|
return reflect.TypeOf(uint64(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
switch size {
|
switch size {
|
||||||
case 8:
|
case 8:
|
||||||
if unsigned {
|
return reflect.TypeOf(int8(0))
|
||||||
return reflect.Uint8, uint8_t
|
|
||||||
}
|
|
||||||
return reflect.Int8, int8_t
|
|
||||||
case 16:
|
case 16:
|
||||||
if unsigned {
|
return reflect.TypeOf(int16(0))
|
||||||
return reflect.Uint16, uint16_t
|
|
||||||
}
|
|
||||||
return reflect.Int16, int16_t
|
|
||||||
case 32:
|
case 32:
|
||||||
if unsigned {
|
return reflect.TypeOf(int32(0))
|
||||||
return reflect.Uint32, uint32_t
|
|
||||||
}
|
|
||||||
return reflect.Int32, int32_t
|
|
||||||
case 64:
|
case 64:
|
||||||
if unsigned {
|
return reflect.TypeOf(int64(0))
|
||||||
return reflect.Uint64, uint64_t
|
|
||||||
}
|
|
||||||
return reflect.Int64, int64_t
|
|
||||||
}
|
}
|
||||||
return reflect.Ptr, big_t
|
return reflect.TypeOf(&big.Int{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// mustArrayToBytesSlice creates a new byte slice with the exact same size as value
|
// mustArrayToByteSlice creates a new byte slice with the exact same size as value
|
||||||
// and copies the bytes in value to the new slice.
|
// and copies the bytes in value to the new slice.
|
||||||
func mustArrayToByteSlice(value reflect.Value) reflect.Value {
|
func mustArrayToByteSlice(value reflect.Value) reflect.Value {
|
||||||
slice := reflect.MakeSlice(reflect.TypeOf([]byte{}), value.Len(), value.Len())
|
slice := reflect.MakeSlice(reflect.TypeOf([]byte{}), value.Len(), value.Len())
|
||||||
|
|
@ -71,59 +99,166 @@ func mustArrayToByteSlice(value reflect.Value) reflect.Value {
|
||||||
//
|
//
|
||||||
// set is a bit more lenient when it comes to assignment and doesn't force an as
|
// set is a bit more lenient when it comes to assignment and doesn't force an as
|
||||||
// strict ruleset as bare `reflect` does.
|
// strict ruleset as bare `reflect` does.
|
||||||
func set(dst, src reflect.Value, output Argument) error {
|
func set(dst, src reflect.Value) error {
|
||||||
dstType := dst.Type()
|
dstType, srcType := dst.Type(), src.Type()
|
||||||
srcType := src.Type()
|
|
||||||
switch {
|
switch {
|
||||||
case dstType.AssignableTo(srcType):
|
case dstType.Kind() == reflect.Interface && dst.Elem().IsValid() && (dst.Elem().Type().Kind() == reflect.Ptr || dst.Elem().CanSet()):
|
||||||
|
return set(dst.Elem(), src)
|
||||||
|
case dstType.Kind() == reflect.Ptr && dstType.Elem() != reflect.TypeOf(big.Int{}):
|
||||||
|
return set(dst.Elem(), src)
|
||||||
|
case srcType.AssignableTo(dstType) && dst.CanSet():
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
case dstType.Kind() == reflect.Interface:
|
case dstType.Kind() == reflect.Slice && srcType.Kind() == reflect.Slice && dst.CanSet():
|
||||||
dst.Set(src)
|
return setSlice(dst, src)
|
||||||
case dstType.Kind() == reflect.Ptr:
|
case dstType.Kind() == reflect.Array:
|
||||||
return set(dst.Elem(), src, output)
|
return setArray(dst, src)
|
||||||
|
case dstType.Kind() == reflect.Struct:
|
||||||
|
return setStruct(dst, src)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type())
|
return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// requireAssignable assures that `dest` is a pointer and it's not an interface.
|
// setSlice attempts to assign src to dst when slices are not assignable by default
|
||||||
func requireAssignable(dst, src reflect.Value) error {
|
// e.g. src: [][]byte -> dst: [][15]byte
|
||||||
if dst.Kind() != reflect.Ptr && dst.Kind() != reflect.Interface {
|
// setSlice ignores if we cannot copy all of src' elements.
|
||||||
return fmt.Errorf("abi: cannot unmarshal %v into %v", src.Type(), dst.Type())
|
func setSlice(dst, src reflect.Value) error {
|
||||||
|
slice := reflect.MakeSlice(dst.Type(), src.Len(), src.Len())
|
||||||
|
for i := 0; i < src.Len(); i++ {
|
||||||
|
if err := set(slice.Index(i), src.Index(i)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dst.CanSet() {
|
||||||
|
dst.Set(slice)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("cannot set slice, destination not settable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setArray(dst, src reflect.Value) error {
|
||||||
|
if src.Kind() == reflect.Ptr {
|
||||||
|
return set(dst, indirect(src))
|
||||||
|
}
|
||||||
|
array := reflect.New(dst.Type()).Elem()
|
||||||
|
min := src.Len()
|
||||||
|
if src.Len() > dst.Len() {
|
||||||
|
min = dst.Len()
|
||||||
|
}
|
||||||
|
for i := 0; i < min; i++ {
|
||||||
|
if err := set(array.Index(i), src.Index(i)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dst.CanSet() {
|
||||||
|
dst.Set(array)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("cannot set array, destination not settable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setStruct(dst, src reflect.Value) error {
|
||||||
|
for i := 0; i < src.NumField(); i++ {
|
||||||
|
srcField := src.Field(i)
|
||||||
|
dstField := dst.Field(i)
|
||||||
|
if !dstField.IsValid() || !srcField.IsValid() {
|
||||||
|
return fmt.Errorf("could not find src field: %v value: %v in destination", srcField.Type().Name(), srcField)
|
||||||
|
}
|
||||||
|
if err := set(dstField, srcField); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// requireUnpackKind verifies preconditions for unpacking `args` into `kind`
|
// mapArgNamesToStructFields maps a slice of argument names to struct fields.
|
||||||
func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind,
|
//
|
||||||
args Arguments) error {
|
// first round: for each Exportable field that contains a `abi:""` tag and this field name
|
||||||
|
// exists in the given argument name list, pair them together.
|
||||||
|
//
|
||||||
|
// second round: for each argument name that has not been already linked, find what
|
||||||
|
// variable is expected to be mapped into, if it exists and has not been used, pair them.
|
||||||
|
//
|
||||||
|
// Note this function assumes the given value is a struct value.
|
||||||
|
func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[string]string, error) {
|
||||||
|
typ := value.Type()
|
||||||
|
|
||||||
switch k {
|
abi2struct := make(map[string]string)
|
||||||
case reflect.Struct:
|
struct2abi := make(map[string]string)
|
||||||
case reflect.Slice, reflect.Array:
|
|
||||||
if minLen := args.LengthNonIndexed(); v.Len() < minLen {
|
|
||||||
return fmt.Errorf("abi: insufficient number of elements in the list/array for unpack, want %d, got %d",
|
|
||||||
minLen, v.Len())
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("abi: cannot unmarshal tuple into %v", t)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// requireUniqueStructFieldNames makes sure field names don't collide
|
// first round ~~~
|
||||||
func requireUniqueStructFieldNames(args Arguments) error {
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
exists := make(map[string]bool)
|
structFieldName := typ.Field(i).Name
|
||||||
for _, arg := range args {
|
|
||||||
field := ToCamelCase(arg.Name)
|
// skip private struct fields.
|
||||||
if field == "" {
|
if structFieldName[:1] != strings.ToUpper(structFieldName[:1]) {
|
||||||
return errors.New("abi: purely underscored output cannot unpack to struct")
|
continue
|
||||||
}
|
}
|
||||||
if exists[field] {
|
// skip fields that have no abi:"" tag.
|
||||||
return fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", field)
|
tagName, ok := typ.Field(i).Tag.Lookup("abi")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check if tag is empty.
|
||||||
|
if tagName == "" {
|
||||||
|
return nil, fmt.Errorf("struct: abi tag in '%s' is empty", structFieldName)
|
||||||
|
}
|
||||||
|
// check which argument field matches with the abi tag.
|
||||||
|
found := false
|
||||||
|
for _, arg := range argNames {
|
||||||
|
if arg == tagName {
|
||||||
|
if abi2struct[arg] != "" {
|
||||||
|
return nil, fmt.Errorf("struct: abi tag in '%s' already mapped", structFieldName)
|
||||||
|
}
|
||||||
|
// pair them
|
||||||
|
abi2struct[arg] = structFieldName
|
||||||
|
struct2abi[structFieldName] = arg
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check if this tag has been mapped.
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("struct: abi tag '%s' defined but not found in abi", tagName)
|
||||||
}
|
}
|
||||||
exists[field] = true
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
// second round ~~~
|
||||||
|
for _, argName := range argNames {
|
||||||
|
structFieldName := ToCamelCase(argName)
|
||||||
|
|
||||||
|
if structFieldName == "" {
|
||||||
|
return nil, errors.New("abi: purely underscored output cannot unpack to struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
// this abi has already been paired, skip it... unless there exists another, yet unassigned
|
||||||
|
// struct field with the same field name. If so, raise an error:
|
||||||
|
// abi: [ { "name": "value" } ]
|
||||||
|
// struct { Value *big.Int , Value1 *big.Int `abi:"value"`}
|
||||||
|
if abi2struct[argName] != "" {
|
||||||
|
if abi2struct[argName] != structFieldName &&
|
||||||
|
struct2abi[structFieldName] == "" &&
|
||||||
|
value.FieldByName(structFieldName).IsValid() {
|
||||||
|
return nil, fmt.Errorf("abi: multiple variables maps to the same abi field '%s'", argName)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// return an error if this struct field has already been paired.
|
||||||
|
if struct2abi[structFieldName] != "" {
|
||||||
|
return nil, fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", structFieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.FieldByName(structFieldName).IsValid() {
|
||||||
|
// pair them
|
||||||
|
abi2struct[argName] = structFieldName
|
||||||
|
struct2abi[structFieldName] = argName
|
||||||
|
} else {
|
||||||
|
// not paired, but annotate as used, to detect cases like
|
||||||
|
// abi : [ { "name": "value" }, { "name": "_value" } ]
|
||||||
|
// struct { Value *big.Int }
|
||||||
|
struct2abi[structFieldName] = argName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return abi2struct, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
264
accounts/abi/reflect_test.go
Normal file
264
accounts/abi/reflect_test.go
Normal file
|
|
@ -0,0 +1,264 @@
|
||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type reflectTest struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
struc interface{}
|
||||||
|
want map[string]string
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
var reflectTests = []reflectTest{
|
||||||
|
{
|
||||||
|
name: "OneToOneCorrespondence",
|
||||||
|
args: []string{"fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MissingFieldsInStruct",
|
||||||
|
args: []string{"fieldA", "fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MoreFieldsInStructThanArgs",
|
||||||
|
args: []string{"fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
FieldB int
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MissingFieldInArgs",
|
||||||
|
args: []string{"fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
FieldB int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
err: "struct: abi tag 'fieldB' defined but not found in abi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoAbiDescriptor",
|
||||||
|
args: []string{"fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoArgs",
|
||||||
|
args: []string{},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
}{},
|
||||||
|
err: "struct: abi tag 'fieldA' defined but not found in abi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DifferentName",
|
||||||
|
args: []string{"fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldB": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DifferentName",
|
||||||
|
args: []string{"fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldB": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MultipleFields",
|
||||||
|
args: []string{"fieldA", "fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
FieldB int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
"fieldB": "FieldB",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MultipleFieldsABIMissing",
|
||||||
|
args: []string{"fieldA", "fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
FieldB int
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
"fieldB": "FieldB",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NameConflict",
|
||||||
|
args: []string{"fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldB"`
|
||||||
|
FieldB int
|
||||||
|
}{},
|
||||||
|
err: "abi: multiple variables maps to the same abi field 'fieldB'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Underscored",
|
||||||
|
args: []string{"_"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int
|
||||||
|
}{},
|
||||||
|
err: "abi: purely underscored output cannot unpack to struct",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DoubleMapping",
|
||||||
|
args: []string{"fieldB", "fieldC", "fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldC"`
|
||||||
|
FieldB int
|
||||||
|
}{},
|
||||||
|
err: "abi: multiple outputs mapping to the same struct field 'FieldA'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AlreadyMapped",
|
||||||
|
args: []string{"fieldB", "fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldB int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
err: "struct: abi tag in 'FieldB' already mapped",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReflectNameToStruct(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
for _, test := range reflectTests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc))
|
||||||
|
if len(test.err) > 0 {
|
||||||
|
if err == nil || err.Error() != test.err {
|
||||||
|
t.Fatalf("Invalid error: expected %v, got %v", test.err, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
for fname := range test.want {
|
||||||
|
if m[fname] != test.want[fname] {
|
||||||
|
t.Fatalf("Incorrect value for field %s: expected %v, got %v", fname, test.want[fname], m[fname])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertType(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
// Test Basic Struct
|
||||||
|
type T struct {
|
||||||
|
X *big.Int
|
||||||
|
Y *big.Int
|
||||||
|
}
|
||||||
|
// Create on-the-fly structure
|
||||||
|
var fields []reflect.StructField
|
||||||
|
fields = append(fields, reflect.StructField{
|
||||||
|
Name: "X",
|
||||||
|
Type: reflect.TypeOf(new(big.Int)),
|
||||||
|
Tag: "json:\"" + "x" + "\"",
|
||||||
|
})
|
||||||
|
fields = append(fields, reflect.StructField{
|
||||||
|
Name: "Y",
|
||||||
|
Type: reflect.TypeOf(new(big.Int)),
|
||||||
|
Tag: "json:\"" + "y" + "\"",
|
||||||
|
})
|
||||||
|
val := reflect.New(reflect.StructOf(fields))
|
||||||
|
val.Elem().Field(0).Set(reflect.ValueOf(big.NewInt(1)))
|
||||||
|
val.Elem().Field(1).Set(reflect.ValueOf(big.NewInt(2)))
|
||||||
|
// ConvertType
|
||||||
|
out := *ConvertType(val.Interface(), new(T)).(*T)
|
||||||
|
if out.X.Cmp(big.NewInt(1)) != 0 {
|
||||||
|
t.Errorf("ConvertType failed, got %v want %v", out.X, big.NewInt(1))
|
||||||
|
}
|
||||||
|
if out.Y.Cmp(big.NewInt(2)) != 0 {
|
||||||
|
t.Errorf("ConvertType failed, got %v want %v", out.Y, big.NewInt(2))
|
||||||
|
}
|
||||||
|
// Slice Type
|
||||||
|
val2 := reflect.MakeSlice(reflect.SliceOf(reflect.StructOf(fields)), 2, 2)
|
||||||
|
val2.Index(0).Field(0).Set(reflect.ValueOf(big.NewInt(1)))
|
||||||
|
val2.Index(0).Field(1).Set(reflect.ValueOf(big.NewInt(2)))
|
||||||
|
val2.Index(1).Field(0).Set(reflect.ValueOf(big.NewInt(3)))
|
||||||
|
val2.Index(1).Field(1).Set(reflect.ValueOf(big.NewInt(4)))
|
||||||
|
out2 := *ConvertType(val2.Interface(), new([]T)).(*[]T)
|
||||||
|
if out2[0].X.Cmp(big.NewInt(1)) != 0 {
|
||||||
|
t.Errorf("ConvertType failed, got %v want %v", out2[0].X, big.NewInt(1))
|
||||||
|
}
|
||||||
|
if out2[0].Y.Cmp(big.NewInt(2)) != 0 {
|
||||||
|
t.Errorf("ConvertType failed, got %v want %v", out2[1].Y, big.NewInt(2))
|
||||||
|
}
|
||||||
|
if out2[1].X.Cmp(big.NewInt(3)) != 0 {
|
||||||
|
t.Errorf("ConvertType failed, got %v want %v", out2[0].X, big.NewInt(1))
|
||||||
|
}
|
||||||
|
if out2[1].Y.Cmp(big.NewInt(4)) != 0 {
|
||||||
|
t.Errorf("ConvertType failed, got %v want %v", out2[1].Y, big.NewInt(2))
|
||||||
|
}
|
||||||
|
// Array Type
|
||||||
|
val3 := reflect.New(reflect.ArrayOf(2, reflect.StructOf(fields)))
|
||||||
|
val3.Elem().Index(0).Field(0).Set(reflect.ValueOf(big.NewInt(1)))
|
||||||
|
val3.Elem().Index(0).Field(1).Set(reflect.ValueOf(big.NewInt(2)))
|
||||||
|
val3.Elem().Index(1).Field(0).Set(reflect.ValueOf(big.NewInt(3)))
|
||||||
|
val3.Elem().Index(1).Field(1).Set(reflect.ValueOf(big.NewInt(4)))
|
||||||
|
out3 := *ConvertType(val3.Interface(), new([2]T)).(*[2]T)
|
||||||
|
if out3[0].X.Cmp(big.NewInt(1)) != 0 {
|
||||||
|
t.Errorf("ConvertType failed, got %v want %v", out3[0].X, big.NewInt(1))
|
||||||
|
}
|
||||||
|
if out3[0].Y.Cmp(big.NewInt(2)) != 0 {
|
||||||
|
t.Errorf("ConvertType failed, got %v want %v", out3[1].Y, big.NewInt(2))
|
||||||
|
}
|
||||||
|
if out3[1].X.Cmp(big.NewInt(3)) != 0 {
|
||||||
|
t.Errorf("ConvertType failed, got %v want %v", out3[0].X, big.NewInt(1))
|
||||||
|
}
|
||||||
|
if out3[1].Y.Cmp(big.NewInt(4)) != 0 {
|
||||||
|
t.Errorf("ConvertType failed, got %v want %v", out3[1].Y, big.NewInt(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
173
accounts/abi/topics.go
Normal file
173
accounts/abi/topics.go
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common/math"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MakeTopics converts a filter query argument list into a filter topic set.
|
||||||
|
func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) {
|
||||||
|
topics := make([][]common.Hash, len(query))
|
||||||
|
for i, filter := range query {
|
||||||
|
for _, rule := range filter {
|
||||||
|
var topic common.Hash
|
||||||
|
|
||||||
|
// Try to generate the topic based on simple types
|
||||||
|
switch rule := rule.(type) {
|
||||||
|
case common.Hash:
|
||||||
|
copy(topic[:], rule[:])
|
||||||
|
case common.Address:
|
||||||
|
copy(topic[common.HashLength-common.AddressLength:], rule[:])
|
||||||
|
case *big.Int:
|
||||||
|
copy(topic[:], math.U256Bytes(new(big.Int).Set(rule)))
|
||||||
|
case bool:
|
||||||
|
if rule {
|
||||||
|
topic[common.HashLength-1] = 1
|
||||||
|
}
|
||||||
|
case int8:
|
||||||
|
copy(topic[:], genIntType(int64(rule), 1))
|
||||||
|
case int16:
|
||||||
|
copy(topic[:], genIntType(int64(rule), 2))
|
||||||
|
case int32:
|
||||||
|
copy(topic[:], genIntType(int64(rule), 4))
|
||||||
|
case int64:
|
||||||
|
copy(topic[:], genIntType(rule, 8))
|
||||||
|
case uint8:
|
||||||
|
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
|
||||||
|
copy(topic[common.HashLength-len(blob):], blob)
|
||||||
|
case uint16:
|
||||||
|
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
|
||||||
|
copy(topic[common.HashLength-len(blob):], blob)
|
||||||
|
case uint32:
|
||||||
|
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
|
||||||
|
copy(topic[common.HashLength-len(blob):], blob)
|
||||||
|
case uint64:
|
||||||
|
blob := new(big.Int).SetUint64(rule).Bytes()
|
||||||
|
copy(topic[common.HashLength-len(blob):], blob)
|
||||||
|
case string:
|
||||||
|
hash := crypto.Keccak256Hash([]byte(rule))
|
||||||
|
copy(topic[:], hash[:])
|
||||||
|
case []byte:
|
||||||
|
hash := crypto.Keccak256Hash(rule)
|
||||||
|
copy(topic[:], hash[:])
|
||||||
|
|
||||||
|
default:
|
||||||
|
// todo(rjl493456442) according to solidity documentation, indexed event
|
||||||
|
// parameters that are not value types i.e. arrays and structs are not
|
||||||
|
// stored directly but instead a keccak256-hash of an encoding is stored.
|
||||||
|
//
|
||||||
|
// We only convert stringS and bytes to hash, still need to deal with
|
||||||
|
// array(both fixed-size and dynamic-size) and struct.
|
||||||
|
|
||||||
|
// Attempt to generate the topic from funky types
|
||||||
|
val := reflect.ValueOf(rule)
|
||||||
|
switch {
|
||||||
|
// static byte array
|
||||||
|
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
|
||||||
|
reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported indexed type: %T", rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
topics[i] = append(topics[i], topic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return topics, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func genIntType(rule int64, size uint) []byte {
|
||||||
|
var topic [common.HashLength]byte
|
||||||
|
if rule < 0 {
|
||||||
|
// if a rule is negative, we need to put it into two's complement.
|
||||||
|
// extended to common.HashLength bytes.
|
||||||
|
topic = [common.HashLength]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
|
||||||
|
}
|
||||||
|
for i := uint(0); i < size; i++ {
|
||||||
|
topic[common.HashLength-i-1] = byte(rule >> (i * 8))
|
||||||
|
}
|
||||||
|
return topic[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTopics converts the indexed topic fields into actual log field values.
|
||||||
|
func ParseTopics(out interface{}, fields Arguments, topics []common.Hash) error {
|
||||||
|
return parseTopicWithSetter(fields, topics,
|
||||||
|
func(arg Argument, reconstr interface{}) {
|
||||||
|
field := reflect.ValueOf(out).Elem().FieldByName(ToCamelCase(arg.Name))
|
||||||
|
field.Set(reflect.ValueOf(reconstr))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs.
|
||||||
|
func ParseTopicsIntoMap(out map[string]interface{}, fields Arguments, topics []common.Hash) error {
|
||||||
|
return parseTopicWithSetter(fields, topics,
|
||||||
|
func(arg Argument, reconstr interface{}) {
|
||||||
|
out[arg.Name] = reconstr
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTopicWithSetter converts the indexed topic field-value pairs and stores them using the
|
||||||
|
// provided set function.
|
||||||
|
//
|
||||||
|
// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256
|
||||||
|
// hashes as the topic value!
|
||||||
|
func parseTopicWithSetter(fields Arguments, topics []common.Hash, setter func(Argument, interface{})) error {
|
||||||
|
// Sanity check that the fields and topics match up
|
||||||
|
if len(fields) != len(topics) {
|
||||||
|
return errors.New("topic/field count mismatch")
|
||||||
|
}
|
||||||
|
// Iterate over all the fields and reconstruct them from topics
|
||||||
|
for i, arg := range fields {
|
||||||
|
if !arg.Indexed {
|
||||||
|
return errors.New("non-indexed field in topic reconstruction")
|
||||||
|
}
|
||||||
|
var reconstr interface{}
|
||||||
|
switch arg.Type.T {
|
||||||
|
case TupleTy:
|
||||||
|
return errors.New("tuple type in topic reconstruction")
|
||||||
|
case StringTy, BytesTy, SliceTy, ArrayTy:
|
||||||
|
// Array types (including strings and bytes) have their keccak256 hashes stored in the topic- not a hash
|
||||||
|
// whose bytes can be decoded to the actual value- so the best we can do is retrieve that hash
|
||||||
|
reconstr = topics[i]
|
||||||
|
case FunctionTy:
|
||||||
|
if garbage := binary.BigEndian.Uint64(topics[i][0:8]); garbage != 0 {
|
||||||
|
return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[i].Bytes())
|
||||||
|
}
|
||||||
|
var tmp [24]byte
|
||||||
|
copy(tmp[:], topics[i][8:32])
|
||||||
|
reconstr = tmp
|
||||||
|
default:
|
||||||
|
var err error
|
||||||
|
reconstr, err = toGoType(0, arg.Type, topics[i].Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Use the setter function to store the value
|
||||||
|
setter(arg, reconstr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
423
accounts/abi/topics_test.go
Normal file
423
accounts/abi/topics_test.go
Normal file
|
|
@ -0,0 +1,423 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMakeTopics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
type args struct {
|
||||||
|
query [][]interface{}
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want [][]common.Hash
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"support fixed byte types, right padded to 32 bytes",
|
||||||
|
args{[][]interface{}{{[5]byte{1, 2, 3, 4, 5}}}},
|
||||||
|
[][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"support common hash types in topics",
|
||||||
|
args{[][]interface{}{{common.Hash{1, 2, 3, 4, 5}}}},
|
||||||
|
[][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"support address types in topics",
|
||||||
|
args{[][]interface{}{{common.Address{1, 2, 3, 4, 5}}}},
|
||||||
|
[][]common.Hash{{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5}}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"support positive *big.Int types in topics",
|
||||||
|
args{[][]interface{}{
|
||||||
|
{big.NewInt(1)},
|
||||||
|
{big.NewInt(1).Lsh(big.NewInt(2), 254)},
|
||||||
|
}},
|
||||||
|
[][]common.Hash{
|
||||||
|
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001")},
|
||||||
|
{common.Hash{128}},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"support negative *big.Int types in topics",
|
||||||
|
args{[][]interface{}{
|
||||||
|
{big.NewInt(-1)},
|
||||||
|
{big.NewInt(math.MinInt64)},
|
||||||
|
}},
|
||||||
|
[][]common.Hash{
|
||||||
|
{common.MaxHash},
|
||||||
|
{common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffff8000000000000000")},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"support boolean types in topics",
|
||||||
|
args{[][]interface{}{
|
||||||
|
{true},
|
||||||
|
{false},
|
||||||
|
}},
|
||||||
|
[][]common.Hash{
|
||||||
|
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}},
|
||||||
|
{common.Hash{0}},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"support int/uint(8/16/32/64) types in topics",
|
||||||
|
args{[][]interface{}{
|
||||||
|
{int8(-2)},
|
||||||
|
{int16(-3)},
|
||||||
|
{int32(-4)},
|
||||||
|
{int64(-5)},
|
||||||
|
{int8(1)},
|
||||||
|
{int16(256)},
|
||||||
|
{int32(65536)},
|
||||||
|
{int64(4294967296)},
|
||||||
|
{uint8(1)},
|
||||||
|
{uint16(256)},
|
||||||
|
{uint32(65536)},
|
||||||
|
{uint64(4294967296)},
|
||||||
|
}},
|
||||||
|
[][]common.Hash{
|
||||||
|
{common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254}},
|
||||||
|
{common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253}},
|
||||||
|
{common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 252}},
|
||||||
|
{common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 251}},
|
||||||
|
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}},
|
||||||
|
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}},
|
||||||
|
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}},
|
||||||
|
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}},
|
||||||
|
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}},
|
||||||
|
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}},
|
||||||
|
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}},
|
||||||
|
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"support string types in topics",
|
||||||
|
args{[][]interface{}{{"hello world"}}},
|
||||||
|
[][]common.Hash{{crypto.Keccak256Hash([]byte("hello world"))}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"support byte slice types in topics",
|
||||||
|
args{[][]interface{}{{[]byte{1, 2, 3}}}},
|
||||||
|
[][]common.Hash{{crypto.Keccak256Hash([]byte{1, 2, 3})}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
got, err := MakeTopics(tt.args.query...)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("makeTopics() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("does not mutate big.Int", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
want := [][]common.Hash{{common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}}
|
||||||
|
|
||||||
|
in := big.NewInt(-1)
|
||||||
|
got, err := MakeTopics([]interface{}{in})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("makeTopics() error = %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("makeTopics() = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
if orig := big.NewInt(-1); in.Cmp(orig) != 0 {
|
||||||
|
t.Fatalf("makeTopics() mutated an input parameter from %v to %v", orig, in)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
createObj func() interface{}
|
||||||
|
resultObj func() interface{}
|
||||||
|
resultMap func() map[string]interface{}
|
||||||
|
fields Arguments
|
||||||
|
topics []common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
type bytesStruct struct {
|
||||||
|
StaticBytes [5]byte
|
||||||
|
}
|
||||||
|
type int8Struct struct {
|
||||||
|
Int8Value int8
|
||||||
|
}
|
||||||
|
type int256Struct struct {
|
||||||
|
Int256Value *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type hashStruct struct {
|
||||||
|
HashValue common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcStruct struct {
|
||||||
|
FuncValue [24]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type topicTest struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTopicsTests() []topicTest {
|
||||||
|
bytesType, _ := NewType("bytes5", "", nil)
|
||||||
|
int8Type, _ := NewType("int8", "", nil)
|
||||||
|
int256Type, _ := NewType("int256", "", nil)
|
||||||
|
tupleType, _ := NewType("tuple(int256,int8)", "", nil)
|
||||||
|
stringType, _ := NewType("string", "", nil)
|
||||||
|
funcType, _ := NewType("function", "", nil)
|
||||||
|
|
||||||
|
tests := []topicTest{
|
||||||
|
{
|
||||||
|
name: "support fixed byte types, right padded to 32 bytes",
|
||||||
|
args: args{
|
||||||
|
createObj: func() interface{} { return &bytesStruct{} },
|
||||||
|
resultObj: func() interface{} { return &bytesStruct{StaticBytes: [5]byte{1, 2, 3, 4, 5}} },
|
||||||
|
resultMap: func() map[string]interface{} {
|
||||||
|
return map[string]interface{}{"staticBytes": [5]byte{1, 2, 3, 4, 5}}
|
||||||
|
},
|
||||||
|
fields: Arguments{Argument{
|
||||||
|
Name: "staticBytes",
|
||||||
|
Type: bytesType,
|
||||||
|
Indexed: true,
|
||||||
|
}},
|
||||||
|
topics: []common.Hash{
|
||||||
|
{1, 2, 3, 4, 5},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int8 with negative value",
|
||||||
|
args: args{
|
||||||
|
createObj: func() interface{} { return &int8Struct{} },
|
||||||
|
resultObj: func() interface{} { return &int8Struct{Int8Value: -1} },
|
||||||
|
resultMap: func() map[string]interface{} {
|
||||||
|
return map[string]interface{}{"int8Value": int8(-1)}
|
||||||
|
},
|
||||||
|
fields: Arguments{Argument{
|
||||||
|
Name: "int8Value",
|
||||||
|
Type: int8Type,
|
||||||
|
Indexed: true,
|
||||||
|
}},
|
||||||
|
topics: []common.Hash{
|
||||||
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int256 with negative value",
|
||||||
|
args: args{
|
||||||
|
createObj: func() interface{} { return &int256Struct{} },
|
||||||
|
resultObj: func() interface{} { return &int256Struct{Int256Value: big.NewInt(-1)} },
|
||||||
|
resultMap: func() map[string]interface{} {
|
||||||
|
return map[string]interface{}{"int256Value": big.NewInt(-1)}
|
||||||
|
},
|
||||||
|
fields: Arguments{Argument{
|
||||||
|
Name: "int256Value",
|
||||||
|
Type: int256Type,
|
||||||
|
Indexed: true,
|
||||||
|
}},
|
||||||
|
topics: []common.Hash{
|
||||||
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hash type",
|
||||||
|
args: args{
|
||||||
|
createObj: func() interface{} { return &hashStruct{} },
|
||||||
|
resultObj: func() interface{} { return &hashStruct{crypto.Keccak256Hash([]byte("stringtopic"))} },
|
||||||
|
resultMap: func() map[string]interface{} {
|
||||||
|
return map[string]interface{}{"hashValue": crypto.Keccak256Hash([]byte("stringtopic"))}
|
||||||
|
},
|
||||||
|
fields: Arguments{Argument{
|
||||||
|
Name: "hashValue",
|
||||||
|
Type: stringType,
|
||||||
|
Indexed: true,
|
||||||
|
}},
|
||||||
|
topics: []common.Hash{
|
||||||
|
crypto.Keccak256Hash([]byte("stringtopic")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function type",
|
||||||
|
args: args{
|
||||||
|
createObj: func() interface{} { return &funcStruct{} },
|
||||||
|
resultObj: func() interface{} {
|
||||||
|
return &funcStruct{[24]byte{255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}}
|
||||||
|
},
|
||||||
|
resultMap: func() map[string]interface{} {
|
||||||
|
return map[string]interface{}{"funcValue": [24]byte{255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}}
|
||||||
|
},
|
||||||
|
fields: Arguments{Argument{
|
||||||
|
Name: "funcValue",
|
||||||
|
Type: funcType,
|
||||||
|
Indexed: true,
|
||||||
|
}},
|
||||||
|
topics: []common.Hash{
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error on topic/field count mismatch",
|
||||||
|
args: args{
|
||||||
|
createObj: func() interface{} { return nil },
|
||||||
|
resultObj: func() interface{} { return nil },
|
||||||
|
resultMap: func() map[string]interface{} { return make(map[string]interface{}) },
|
||||||
|
fields: Arguments{Argument{
|
||||||
|
Name: "tupletype",
|
||||||
|
Type: tupleType,
|
||||||
|
Indexed: true,
|
||||||
|
}},
|
||||||
|
topics: []common.Hash{},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error on unindexed arguments",
|
||||||
|
args: args{
|
||||||
|
createObj: func() interface{} { return &int256Struct{} },
|
||||||
|
resultObj: func() interface{} { return &int256Struct{} },
|
||||||
|
resultMap: func() map[string]interface{} { return make(map[string]interface{}) },
|
||||||
|
fields: Arguments{Argument{
|
||||||
|
Name: "int256Value",
|
||||||
|
Type: int256Type,
|
||||||
|
Indexed: false,
|
||||||
|
}},
|
||||||
|
topics: []common.Hash{
|
||||||
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error on tuple in topic reconstruction",
|
||||||
|
args: args{
|
||||||
|
createObj: func() interface{} { return &tupleType },
|
||||||
|
resultObj: func() interface{} { return &tupleType },
|
||||||
|
resultMap: func() map[string]interface{} { return make(map[string]interface{}) },
|
||||||
|
fields: Arguments{Argument{
|
||||||
|
Name: "tupletype",
|
||||||
|
Type: tupleType,
|
||||||
|
Indexed: true,
|
||||||
|
}},
|
||||||
|
topics: []common.Hash{{0}},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error on improper encoded function",
|
||||||
|
args: args{
|
||||||
|
createObj: func() interface{} { return &funcStruct{} },
|
||||||
|
resultObj: func() interface{} { return &funcStruct{} },
|
||||||
|
resultMap: func() map[string]interface{} {
|
||||||
|
return make(map[string]interface{})
|
||||||
|
},
|
||||||
|
fields: Arguments{Argument{
|
||||||
|
Name: "funcValue",
|
||||||
|
Type: funcType,
|
||||||
|
Indexed: true,
|
||||||
|
}},
|
||||||
|
topics: []common.Hash{
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return tests
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseTopics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := setupTopicsTests()
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
createObj := tt.args.createObj()
|
||||||
|
if err := ParseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("parseTopics() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
resultObj := tt.args.resultObj()
|
||||||
|
if !reflect.DeepEqual(createObj, resultObj) {
|
||||||
|
t.Errorf("parseTopics() = %v, want %v", createObj, resultObj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseTopicsIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := setupTopicsTests()
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
outMap := make(map[string]interface{})
|
||||||
|
if err := ParseTopicsIntoMap(outMap, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("parseTopicsIntoMap() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
resultMap := tt.args.resultMap()
|
||||||
|
if !reflect.DeepEqual(outMap, resultMap) {
|
||||||
|
t.Errorf("parseTopicsIntoMap() = %v, want %v", outMap, resultMap)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,10 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Type enumerator
|
// Type enumerator
|
||||||
|
|
@ -33,6 +37,7 @@ const (
|
||||||
StringTy
|
StringTy
|
||||||
SliceTy
|
SliceTy
|
||||||
ArrayTy
|
ArrayTy
|
||||||
|
TupleTy
|
||||||
AddressTy
|
AddressTy
|
||||||
FixedBytesTy
|
FixedBytesTy
|
||||||
BytesTy
|
BytesTy
|
||||||
|
|
@ -41,63 +46,70 @@ const (
|
||||||
FunctionTy
|
FunctionTy
|
||||||
)
|
)
|
||||||
|
|
||||||
// Type is the reflection of the supported argument type
|
// Type is the reflection of the supported argument type.
|
||||||
type Type struct {
|
type Type struct {
|
||||||
Elem *Type
|
Elem *Type
|
||||||
|
|
||||||
Kind reflect.Kind
|
|
||||||
Type reflect.Type
|
|
||||||
Size int
|
Size int
|
||||||
T byte // Our own type checking
|
T byte // Our own type checking
|
||||||
|
|
||||||
stringKind string // holds the unparsed string for deriving signatures
|
stringKind string // holds the unparsed string for deriving signatures
|
||||||
|
|
||||||
|
// Tuple relative fields
|
||||||
|
TupleRawName string // Raw struct name defined in source code, may be empty.
|
||||||
|
TupleElems []*Type // Type information of all tuple fields
|
||||||
|
TupleRawNames []string // Raw field name of all tuple fields
|
||||||
|
TupleType reflect.Type // Underlying struct of the tuple
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// typeRegex parses the abi sub types
|
// typeRegex parses the abi sub types
|
||||||
typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?")
|
typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?")
|
||||||
|
|
||||||
|
// sliceSizeRegex grab the slice size
|
||||||
|
sliceSizeRegex = regexp.MustCompile("[0-9]+")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewType creates a new reflection type of abi type given in t.
|
// NewType creates a new reflection type of abi type given in t.
|
||||||
func NewType(t string) (typ Type, err error) {
|
func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) {
|
||||||
// check that array brackets are equal if they exist
|
// check that array brackets are equal if they exist
|
||||||
if strings.Count(t, "[") != strings.Count(t, "]") {
|
if strings.Count(t, "[") != strings.Count(t, "]") {
|
||||||
return Type{}, errors.New("invalid arg type in abi")
|
return Type{}, errors.New("invalid arg type in abi")
|
||||||
}
|
}
|
||||||
|
|
||||||
typ.stringKind = t
|
typ.stringKind = t
|
||||||
|
|
||||||
// if there are brackets, get ready to go into slice/array mode and
|
// if there are brackets, get ready to go into slice/array mode and
|
||||||
// recursively create the type
|
// recursively create the type
|
||||||
if strings.Count(t, "[") != 0 {
|
if strings.Count(t, "[") != 0 {
|
||||||
i := strings.LastIndex(t, "[")
|
// Note internalType can be empty here.
|
||||||
|
subInternal := internalType
|
||||||
|
if i := strings.LastIndex(internalType, "["); i != -1 {
|
||||||
|
subInternal = subInternal[:i]
|
||||||
|
}
|
||||||
// recursively embed the type
|
// recursively embed the type
|
||||||
embeddedType, err := NewType(t[:i])
|
i := strings.LastIndex(t, "[")
|
||||||
|
embeddedType, err := NewType(t[:i], subInternal, components)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Type{}, err
|
return Type{}, err
|
||||||
}
|
}
|
||||||
// grab the last cell and create a type from there
|
// grab the last cell and create a type from there
|
||||||
sliced := t[i:]
|
sliced := t[i:]
|
||||||
// grab the slice size with regexp
|
// grab the slice size with regexp
|
||||||
re := regexp.MustCompile("[0-9]+")
|
intz := sliceSizeRegex.FindAllString(sliced, -1)
|
||||||
intz := re.FindAllString(sliced, -1)
|
|
||||||
|
|
||||||
if len(intz) == 0 {
|
if len(intz) == 0 {
|
||||||
// is a slice
|
// is a slice
|
||||||
typ.T = SliceTy
|
typ.T = SliceTy
|
||||||
typ.Kind = reflect.Slice
|
|
||||||
typ.Elem = &embeddedType
|
typ.Elem = &embeddedType
|
||||||
typ.Type = reflect.SliceOf(embeddedType.Type)
|
typ.stringKind = embeddedType.stringKind + sliced
|
||||||
} else if len(intz) == 1 {
|
} else if len(intz) == 1 {
|
||||||
// is a array
|
// is an array
|
||||||
typ.T = ArrayTy
|
typ.T = ArrayTy
|
||||||
typ.Kind = reflect.Array
|
|
||||||
typ.Elem = &embeddedType
|
typ.Elem = &embeddedType
|
||||||
typ.Size, err = strconv.Atoi(intz[0])
|
typ.Size, err = strconv.Atoi(intz[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
||||||
}
|
}
|
||||||
typ.Type = reflect.ArrayOf(typ.Size, embeddedType.Type)
|
typ.stringKind = embeddedType.stringKind + sliced
|
||||||
} else {
|
} else {
|
||||||
return Type{}, errors.New("invalid formatting of array type")
|
return Type{}, errors.New("invalid formatting of array type")
|
||||||
}
|
}
|
||||||
|
|
@ -127,50 +139,133 @@ func NewType(t string) (typ Type, err error) {
|
||||||
// varType is the parsed abi type
|
// varType is the parsed abi type
|
||||||
switch varType := parsedType[1]; varType {
|
switch varType := parsedType[1]; varType {
|
||||||
case "int":
|
case "int":
|
||||||
typ.Kind, typ.Type = reflectIntKindAndType(false, varSize)
|
|
||||||
typ.Size = varSize
|
typ.Size = varSize
|
||||||
typ.T = IntTy
|
typ.T = IntTy
|
||||||
case "uint":
|
case "uint":
|
||||||
typ.Kind, typ.Type = reflectIntKindAndType(true, varSize)
|
|
||||||
typ.Size = varSize
|
typ.Size = varSize
|
||||||
typ.T = UintTy
|
typ.T = UintTy
|
||||||
case "bool":
|
case "bool":
|
||||||
typ.Kind = reflect.Bool
|
|
||||||
typ.T = BoolTy
|
typ.T = BoolTy
|
||||||
typ.Type = reflect.TypeOf(bool(false))
|
|
||||||
case "address":
|
case "address":
|
||||||
typ.Kind = reflect.Array
|
|
||||||
typ.Type = address_t
|
|
||||||
typ.Size = 20
|
typ.Size = 20
|
||||||
typ.T = AddressTy
|
typ.T = AddressTy
|
||||||
case "string":
|
case "string":
|
||||||
typ.Kind = reflect.String
|
|
||||||
typ.Type = reflect.TypeOf("")
|
|
||||||
typ.T = StringTy
|
typ.T = StringTy
|
||||||
case "bytes":
|
case "bytes":
|
||||||
if varSize == 0 {
|
if varSize == 0 {
|
||||||
typ.T = BytesTy
|
typ.T = BytesTy
|
||||||
typ.Kind = reflect.Slice
|
|
||||||
typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0)))
|
|
||||||
} else {
|
} else {
|
||||||
|
if varSize > 32 {
|
||||||
|
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||||
|
}
|
||||||
typ.T = FixedBytesTy
|
typ.T = FixedBytesTy
|
||||||
typ.Kind = reflect.Array
|
|
||||||
typ.Size = varSize
|
typ.Size = varSize
|
||||||
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
|
|
||||||
}
|
}
|
||||||
|
case "tuple":
|
||||||
|
var (
|
||||||
|
fields []reflect.StructField
|
||||||
|
elems []*Type
|
||||||
|
names []string
|
||||||
|
expression string // canonical parameter expression
|
||||||
|
used = make(map[string]bool)
|
||||||
|
)
|
||||||
|
expression += "("
|
||||||
|
for idx, c := range components {
|
||||||
|
cType, err := NewType(c.Type, c.InternalType, c.Components)
|
||||||
|
if err != nil {
|
||||||
|
return Type{}, err
|
||||||
|
}
|
||||||
|
name := ToCamelCase(c.Name)
|
||||||
|
if name == "" {
|
||||||
|
return Type{}, errors.New("abi: purely anonymous or underscored field is not supported")
|
||||||
|
}
|
||||||
|
fieldName := ResolveNameConflict(name, func(s string) bool { return used[s] })
|
||||||
|
used[fieldName] = true
|
||||||
|
if !isValidFieldName(fieldName) {
|
||||||
|
return Type{}, fmt.Errorf("field %d has invalid name", idx)
|
||||||
|
}
|
||||||
|
fields = append(fields, reflect.StructField{
|
||||||
|
Name: fieldName, // reflect.StructOf will panic for any exported field.
|
||||||
|
Type: cType.GetType(),
|
||||||
|
Tag: reflect.StructTag("json:\"" + c.Name + "\""),
|
||||||
|
})
|
||||||
|
elems = append(elems, &cType)
|
||||||
|
names = append(names, c.Name)
|
||||||
|
expression += cType.stringKind
|
||||||
|
if idx != len(components)-1 {
|
||||||
|
expression += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expression += ")"
|
||||||
|
|
||||||
|
typ.TupleType = reflect.StructOf(fields)
|
||||||
|
typ.TupleElems = elems
|
||||||
|
typ.TupleRawNames = names
|
||||||
|
typ.T = TupleTy
|
||||||
|
typ.stringKind = expression
|
||||||
|
|
||||||
|
const structPrefix = "struct "
|
||||||
|
// After solidity 0.5.10, a new field of abi "internalType"
|
||||||
|
// is introduced. From that we can obtain the struct name
|
||||||
|
// user defined in the source code.
|
||||||
|
if internalType != "" && strings.HasPrefix(internalType, structPrefix) {
|
||||||
|
// Foo.Bar type definition is not allowed in golang,
|
||||||
|
// convert the format to FooBar
|
||||||
|
typ.TupleRawName = strings.ReplaceAll(internalType[len(structPrefix):], ".", "")
|
||||||
|
}
|
||||||
|
|
||||||
case "function":
|
case "function":
|
||||||
typ.Kind = reflect.Array
|
|
||||||
typ.T = FunctionTy
|
typ.T = FunctionTy
|
||||||
typ.Size = 24
|
typ.Size = 24
|
||||||
typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0)))
|
|
||||||
default:
|
default:
|
||||||
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
if strings.HasPrefix(internalType, "contract ") {
|
||||||
|
typ.Size = 20
|
||||||
|
typ.T = AddressTy
|
||||||
|
} else {
|
||||||
|
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements Stringer
|
// GetType returns the reflection type of the ABI type.
|
||||||
|
func (t Type) GetType() reflect.Type {
|
||||||
|
switch t.T {
|
||||||
|
case IntTy:
|
||||||
|
return reflectIntType(false, t.Size)
|
||||||
|
case UintTy:
|
||||||
|
return reflectIntType(true, t.Size)
|
||||||
|
case BoolTy:
|
||||||
|
return reflect.TypeOf(false)
|
||||||
|
case StringTy:
|
||||||
|
return reflect.TypeOf("")
|
||||||
|
case SliceTy:
|
||||||
|
return reflect.SliceOf(t.Elem.GetType())
|
||||||
|
case ArrayTy:
|
||||||
|
return reflect.ArrayOf(t.Size, t.Elem.GetType())
|
||||||
|
case TupleTy:
|
||||||
|
return t.TupleType
|
||||||
|
case AddressTy:
|
||||||
|
return reflect.TypeOf(common.Address{})
|
||||||
|
case FixedBytesTy:
|
||||||
|
return reflect.ArrayOf(t.Size, reflect.TypeOf(byte(0)))
|
||||||
|
case BytesTy:
|
||||||
|
return reflect.SliceOf(reflect.TypeOf(byte(0)))
|
||||||
|
case HashTy:
|
||||||
|
// hashtype currently not used
|
||||||
|
return reflect.ArrayOf(32, reflect.TypeOf(byte(0)))
|
||||||
|
case FixedPointTy:
|
||||||
|
// fixedpoint type currently not used
|
||||||
|
return reflect.ArrayOf(32, reflect.TypeOf(byte(0)))
|
||||||
|
case FunctionTy:
|
||||||
|
return reflect.ArrayOf(24, reflect.TypeOf(byte(0)))
|
||||||
|
default:
|
||||||
|
panic("Invalid type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements Stringer.
|
||||||
func (t Type) String() (out string) {
|
func (t Type) String() (out string) {
|
||||||
return t.stringKind
|
return t.stringKind
|
||||||
}
|
}
|
||||||
|
|
@ -178,32 +273,157 @@ func (t Type) String() (out string) {
|
||||||
func (t Type) pack(v reflect.Value) ([]byte, error) {
|
func (t Type) pack(v reflect.Value) ([]byte, error) {
|
||||||
// dereference pointer first if it's a pointer
|
// dereference pointer first if it's a pointer
|
||||||
v = indirect(v)
|
v = indirect(v)
|
||||||
|
|
||||||
if err := typeCheck(t, v); err != nil {
|
if err := typeCheck(t, v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.T == SliceTy || t.T == ArrayTy {
|
switch t.T {
|
||||||
var packed []byte
|
case SliceTy, ArrayTy:
|
||||||
|
var ret []byte
|
||||||
|
|
||||||
|
if t.requiresLengthPrefix() {
|
||||||
|
// append length
|
||||||
|
ret = append(ret, packNum(reflect.ValueOf(v.Len()))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate offset if any
|
||||||
|
offset := 0
|
||||||
|
offsetReq := isDynamicType(*t.Elem)
|
||||||
|
if offsetReq {
|
||||||
|
offset = getTypeSize(*t.Elem) * v.Len()
|
||||||
|
}
|
||||||
|
var tail []byte
|
||||||
for i := 0; i < v.Len(); i++ {
|
for i := 0; i < v.Len(); i++ {
|
||||||
val, err := t.Elem.pack(v.Index(i))
|
val, err := t.Elem.pack(v.Index(i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
packed = append(packed, val...)
|
if !offsetReq {
|
||||||
|
ret = append(ret, val...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
||||||
|
offset += len(val)
|
||||||
|
tail = append(tail, val...)
|
||||||
}
|
}
|
||||||
if t.T == SliceTy {
|
return append(ret, tail...), nil
|
||||||
return packBytesSlice(packed, v.Len()), nil
|
case TupleTy:
|
||||||
} else if t.T == ArrayTy {
|
// (T1,...,Tk) for k >= 0 and any types T1, …, Tk
|
||||||
return packed, nil
|
// enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k))
|
||||||
|
// where X = (X(1), ..., X(k)) and head and tail are defined for Ti being a static
|
||||||
|
// type as
|
||||||
|
// head(X(i)) = enc(X(i)) and tail(X(i)) = "" (the empty string)
|
||||||
|
// and as
|
||||||
|
// head(X(i)) = enc(len(head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1))))
|
||||||
|
// tail(X(i)) = enc(X(i))
|
||||||
|
// otherwise, i.e. if Ti is a dynamic type.
|
||||||
|
fieldmap, err := mapArgNamesToStructFields(t.TupleRawNames, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// Calculate prefix occupied size.
|
||||||
|
offset := 0
|
||||||
|
for _, elem := range t.TupleElems {
|
||||||
|
offset += getTypeSize(*elem)
|
||||||
|
}
|
||||||
|
var ret, tail []byte
|
||||||
|
for i, elem := range t.TupleElems {
|
||||||
|
field := v.FieldByName(fieldmap[t.TupleRawNames[i]])
|
||||||
|
if !field.IsValid() {
|
||||||
|
return nil, fmt.Errorf("field %s for tuple not found in the given struct", t.TupleRawNames[i])
|
||||||
|
}
|
||||||
|
val, err := elem.pack(field)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isDynamicType(*elem) {
|
||||||
|
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
||||||
|
tail = append(tail, val...)
|
||||||
|
offset += len(val)
|
||||||
|
} else {
|
||||||
|
ret = append(ret, val...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(ret, tail...), nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return packElement(t, v)
|
||||||
}
|
}
|
||||||
return packElement(t, v), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// requireLengthPrefix returns whether the type requires any sort of length
|
// requiresLengthPrefix returns whether the type requires any sort of length
|
||||||
// prefixing.
|
// prefixing.
|
||||||
func (t Type) requiresLengthPrefix() bool {
|
func (t Type) requiresLengthPrefix() bool {
|
||||||
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
|
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isDynamicType returns true if the type is dynamic.
|
||||||
|
// The following types are called “dynamic”:
|
||||||
|
// * bytes
|
||||||
|
// * string
|
||||||
|
// * T[] for any T
|
||||||
|
// * T[k] for any dynamic T and any k >= 0
|
||||||
|
// * (T1,...,Tk) if Ti is dynamic for some 1 <= i <= k
|
||||||
|
func isDynamicType(t Type) bool {
|
||||||
|
if t.T == TupleTy {
|
||||||
|
for _, elem := range t.TupleElems {
|
||||||
|
if isDynamicType(*elem) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy || (t.T == ArrayTy && isDynamicType(*t.Elem))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTypeSize returns the size that this type needs to occupy.
|
||||||
|
// We distinguish static and dynamic types. Static types are encoded in-place
|
||||||
|
// and dynamic types are encoded at a separately allocated location after the
|
||||||
|
// current block.
|
||||||
|
// So for a static variable, the size returned represents the size that the
|
||||||
|
// variable actually occupies.
|
||||||
|
// For a dynamic variable, the returned size is fixed 32 bytes, which is used
|
||||||
|
// to store the location reference for actual value storage.
|
||||||
|
func getTypeSize(t Type) int {
|
||||||
|
if t.T == ArrayTy && !isDynamicType(*t.Elem) {
|
||||||
|
// Recursively calculate type size if it is a nested array
|
||||||
|
if t.Elem.T == ArrayTy || t.Elem.T == TupleTy {
|
||||||
|
return t.Size * getTypeSize(*t.Elem)
|
||||||
|
}
|
||||||
|
return t.Size * 32
|
||||||
|
} else if t.T == TupleTy && !isDynamicType(t) {
|
||||||
|
total := 0
|
||||||
|
for _, elem := range t.TupleElems {
|
||||||
|
total += getTypeSize(*elem)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
return 32
|
||||||
|
}
|
||||||
|
|
||||||
|
// isLetter reports whether a given 'rune' is classified as a Letter.
|
||||||
|
// This method is copied from reflect/type.go
|
||||||
|
func isLetter(ch rune) bool {
|
||||||
|
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidFieldName checks if a string is a valid (struct) field name or not.
|
||||||
|
//
|
||||||
|
// According to the language spec, a field name should be an identifier.
|
||||||
|
//
|
||||||
|
// identifier = letter { letter | unicode_digit } .
|
||||||
|
// letter = unicode_letter | "_" .
|
||||||
|
// This method is copied from reflect/type.go
|
||||||
|
func isValidFieldName(fieldName string) bool {
|
||||||
|
for i, c := range fieldName {
|
||||||
|
if i == 0 && !isLetter(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(isLetter(c) || unicode.IsDigit(c)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(fieldName) > 0
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,79 +25,89 @@ import (
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
)
|
)
|
||||||
|
|
||||||
// typeWithoutStringer is a alias for the Type type which simply doesn't implement
|
// typeWithoutStringer is an alias for the Type type which simply doesn't implement
|
||||||
// the stringer interface to allow printing type details in the tests below.
|
// the stringer interface to allow printing type details in the tests below.
|
||||||
type typeWithoutStringer Type
|
type typeWithoutStringer Type
|
||||||
|
|
||||||
// Tests that all allowed types get recognized by the type parser.
|
// Tests that all allowed types get recognized by the type parser.
|
||||||
func TestTypeRegexp(t *testing.T) {
|
func TestTypeRegexp(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
blob string
|
blob string
|
||||||
kind Type
|
components []ArgumentMarshaling
|
||||||
|
kind Type
|
||||||
}{
|
}{
|
||||||
{"bool", Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}},
|
{"bool", nil, Type{T: BoolTy, stringKind: "bool"}},
|
||||||
{"bool[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool(nil)), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}},
|
{"bool[]", nil, Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}},
|
||||||
{"bool[2]", Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}},
|
{"bool[2]", nil, Type{Size: 2, T: ArrayTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}},
|
||||||
{"bool[2][]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}},
|
{"bool[2][]", nil, Type{T: SliceTy, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}},
|
||||||
{"bool[][]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}},
|
{"bool[][]", nil, Type{T: SliceTy, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}},
|
||||||
{"bool[][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}},
|
{"bool[][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}},
|
||||||
{"bool[2][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}},
|
{"bool[2][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}},
|
||||||
{"bool[2][][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][][2]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}, stringKind: "bool[2][][2]"}},
|
{"bool[2][][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: SliceTy, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}, stringKind: "bool[2][][2]"}},
|
||||||
{"bool[2][2][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}},
|
{"bool[2][2][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}},
|
||||||
{"bool[][][]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}},
|
{"bool[][][]", nil, Type{T: SliceTy, Elem: &Type{T: SliceTy, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}},
|
||||||
{"bool[][2][]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][2][]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}},
|
{"bool[][2][]", nil, Type{T: SliceTy, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}},
|
||||||
{"int8", Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}},
|
{"int8", nil, Type{Size: 8, T: IntTy, stringKind: "int8"}},
|
||||||
{"int16", Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}},
|
{"int16", nil, Type{Size: 16, T: IntTy, stringKind: "int16"}},
|
||||||
{"int32", Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}},
|
{"int32", nil, Type{Size: 32, T: IntTy, stringKind: "int32"}},
|
||||||
{"int64", Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}},
|
{"int64", nil, Type{Size: 64, T: IntTy, stringKind: "int64"}},
|
||||||
{"int256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}},
|
{"int256", nil, Type{Size: 256, T: IntTy, stringKind: "int256"}},
|
||||||
{"int8[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}},
|
{"int8[]", nil, Type{T: SliceTy, Elem: &Type{Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}},
|
||||||
{"int8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}},
|
{"int8[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}},
|
||||||
{"int16[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}},
|
{"int16[]", nil, Type{T: SliceTy, Elem: &Type{Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}},
|
||||||
{"int16[2]", Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}},
|
{"int16[2]", nil, Type{Size: 2, T: ArrayTy, Elem: &Type{Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}},
|
||||||
{"int32[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
|
{"int32[]", nil, Type{T: SliceTy, Elem: &Type{Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
|
||||||
{"int32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
|
{"int32[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
|
||||||
{"int64[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}},
|
{"int64[]", nil, Type{T: SliceTy, Elem: &Type{Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}},
|
||||||
{"int64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}},
|
{"int64[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}},
|
||||||
{"int256[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
|
{"int256[]", nil, Type{T: SliceTy, Elem: &Type{Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
|
||||||
{"int256[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
|
{"int256[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
|
||||||
{"uint8", Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}},
|
{"uint8", nil, Type{Size: 8, T: UintTy, stringKind: "uint8"}},
|
||||||
{"uint16", Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}},
|
{"uint16", nil, Type{Size: 16, T: UintTy, stringKind: "uint16"}},
|
||||||
{"uint32", Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}},
|
{"uint32", nil, Type{Size: 32, T: UintTy, stringKind: "uint32"}},
|
||||||
{"uint64", Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}},
|
{"uint64", nil, Type{Size: 64, T: UintTy, stringKind: "uint64"}},
|
||||||
{"uint256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}},
|
{"uint256", nil, Type{Size: 256, T: UintTy, stringKind: "uint256"}},
|
||||||
{"uint8[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}},
|
{"uint8[]", nil, Type{T: SliceTy, Elem: &Type{Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}},
|
||||||
{"uint8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}},
|
{"uint8[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}},
|
||||||
{"uint16[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}},
|
{"uint16[]", nil, Type{T: SliceTy, Elem: &Type{Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}},
|
||||||
{"uint16[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}},
|
{"uint16[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}},
|
||||||
{"uint32[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
|
{"uint32[]", nil, Type{T: SliceTy, Elem: &Type{Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
|
||||||
{"uint32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
|
{"uint32[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
|
||||||
{"uint64[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}},
|
{"uint64[]", nil, Type{T: SliceTy, Elem: &Type{Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}},
|
||||||
{"uint64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}},
|
{"uint64[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}},
|
||||||
{"uint256[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
|
{"uint256[]", nil, Type{T: SliceTy, Elem: &Type{Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
|
||||||
{"uint256[2]", Type{Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]*big.Int{}), Size: 2, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
|
{"uint256[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
|
||||||
{"bytes32", Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}},
|
{"bytes32", nil, Type{T: FixedBytesTy, Size: 32, stringKind: "bytes32"}},
|
||||||
{"bytes[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]byte{}), Elem: &Type{Kind: reflect.Slice, Type: reflect.TypeOf([]byte{}), T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}},
|
{"bytes[]", nil, Type{T: SliceTy, Elem: &Type{T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}},
|
||||||
{"bytes[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]byte{}), Elem: &Type{T: BytesTy, Type: reflect.TypeOf([]byte{}), Kind: reflect.Slice, stringKind: "bytes"}, stringKind: "bytes[2]"}},
|
{"bytes[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[2]"}},
|
||||||
{"bytes32[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][32]byte{}), Elem: &Type{Kind: reflect.Array, Type: reflect.TypeOf([32]byte{}), T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[]"}},
|
{"bytes32[]", nil, Type{T: SliceTy, Elem: &Type{T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[]"}},
|
||||||
{"bytes32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][32]byte{}), Elem: &Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}, stringKind: "bytes32[2]"}},
|
{"bytes32[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[2]"}},
|
||||||
{"string", Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}},
|
{"string", nil, Type{T: StringTy, stringKind: "string"}},
|
||||||
{"string[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]string{}), Elem: &Type{Kind: reflect.String, Type: reflect.TypeOf(""), T: StringTy, stringKind: "string"}, stringKind: "string[]"}},
|
{"string[]", nil, Type{T: SliceTy, Elem: &Type{T: StringTy, stringKind: "string"}, stringKind: "string[]"}},
|
||||||
{"string[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]string{}), Elem: &Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}, stringKind: "string[2]"}},
|
{"string[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: StringTy, stringKind: "string"}, stringKind: "string[2]"}},
|
||||||
{"address", Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}},
|
{"address", nil, Type{Size: 20, T: AddressTy, stringKind: "address"}},
|
||||||
{"address[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}},
|
{"address[]", nil, Type{T: SliceTy, Elem: &Type{Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}},
|
||||||
{"address[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}},
|
{"address[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}},
|
||||||
// TODO when fixed types are implemented properly
|
// TODO when fixed types are implemented properly
|
||||||
// {"fixed", Type{}},
|
// {"fixed", nil, Type{}},
|
||||||
// {"fixed128x128", Type{}},
|
// {"fixed128x128", nil, Type{}},
|
||||||
// {"fixed[]", Type{}},
|
// {"fixed[]", nil, Type{}},
|
||||||
// {"fixed[2]", Type{}},
|
// {"fixed[2]", nil, Type{}},
|
||||||
// {"fixed128x128[]", Type{}},
|
// {"fixed128x128[]", nil, Type{}},
|
||||||
// {"fixed128x128[2]", Type{}},
|
// {"fixed128x128[2]", nil, Type{}},
|
||||||
|
{"tuple", []ArgumentMarshaling{{Name: "a", Type: "int64"}}, Type{T: TupleTy, TupleType: reflect.TypeOf(struct {
|
||||||
|
A int64 `json:"a"`
|
||||||
|
}{}), stringKind: "(int64)",
|
||||||
|
TupleElems: []*Type{{T: IntTy, Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"a"}}},
|
||||||
|
{"tuple with long name", []ArgumentMarshaling{{Name: "aTypicalParamName", Type: "int64"}}, Type{T: TupleTy, TupleType: reflect.TypeOf(struct {
|
||||||
|
ATypicalParamName int64 `json:"aTypicalParamName"`
|
||||||
|
}{}), stringKind: "(int64)",
|
||||||
|
TupleElems: []*Type{{T: IntTy, Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"aTypicalParamName"}}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
typ, err := NewType(tt.blob)
|
typ, err := NewType(tt.blob, "", tt.components)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("type %q: failed to parse type string: %v", tt.blob, err)
|
t.Errorf("type %q: failed to parse type string: %v", tt.blob, err)
|
||||||
}
|
}
|
||||||
|
|
@ -108,152 +118,172 @@ func TestTypeRegexp(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTypeCheck(t *testing.T) {
|
func TestTypeCheck(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
for i, test := range []struct {
|
for i, test := range []struct {
|
||||||
typ string
|
typ string
|
||||||
input interface{}
|
components []ArgumentMarshaling
|
||||||
err string
|
input interface{}
|
||||||
|
err string
|
||||||
}{
|
}{
|
||||||
{"uint", big.NewInt(1), "unsupported arg type: uint"},
|
{"uint", nil, big.NewInt(1), "unsupported arg type: uint"},
|
||||||
{"int", big.NewInt(1), "unsupported arg type: int"},
|
{"int", nil, big.NewInt(1), "unsupported arg type: int"},
|
||||||
{"uint256", big.NewInt(1), ""},
|
{"uint256", nil, big.NewInt(1), ""},
|
||||||
{"uint256[][3][]", [][3][]*big.Int{{{}}}, ""},
|
{"uint256[][3][]", nil, [][3][]*big.Int{{{}}}, ""},
|
||||||
{"uint256[][][3]", [3][][]*big.Int{{{}}}, ""},
|
{"uint256[][][3]", nil, [3][][]*big.Int{{{}}}, ""},
|
||||||
{"uint256[3][][]", [][][3]*big.Int{{{}}}, ""},
|
{"uint256[3][][]", nil, [][][3]*big.Int{{{}}}, ""},
|
||||||
{"uint256[3][3][3]", [3][3][3]*big.Int{{{}}}, ""},
|
{"uint256[3][3][3]", nil, [3][3][3]*big.Int{{{}}}, ""},
|
||||||
{"uint8[][]", [][]uint8{}, ""},
|
{"uint8[][]", nil, [][]uint8{}, ""},
|
||||||
{"int256", big.NewInt(1), ""},
|
{"int256", nil, big.NewInt(1), ""},
|
||||||
{"uint8", uint8(1), ""},
|
{"uint8", nil, uint8(1), ""},
|
||||||
{"uint16", uint16(1), ""},
|
{"uint16", nil, uint16(1), ""},
|
||||||
{"uint32", uint32(1), ""},
|
{"uint32", nil, uint32(1), ""},
|
||||||
{"uint64", uint64(1), ""},
|
{"uint64", nil, uint64(1), ""},
|
||||||
{"int8", int8(1), ""},
|
{"int8", nil, int8(1), ""},
|
||||||
{"int16", int16(1), ""},
|
{"int16", nil, int16(1), ""},
|
||||||
{"int32", int32(1), ""},
|
{"int32", nil, int32(1), ""},
|
||||||
{"int64", int64(1), ""},
|
{"int64", nil, int64(1), ""},
|
||||||
{"uint24", big.NewInt(1), ""},
|
{"uint24", nil, big.NewInt(1), ""},
|
||||||
{"uint40", big.NewInt(1), ""},
|
{"uint40", nil, big.NewInt(1), ""},
|
||||||
{"uint48", big.NewInt(1), ""},
|
{"uint48", nil, big.NewInt(1), ""},
|
||||||
{"uint56", big.NewInt(1), ""},
|
{"uint56", nil, big.NewInt(1), ""},
|
||||||
{"uint72", big.NewInt(1), ""},
|
{"uint72", nil, big.NewInt(1), ""},
|
||||||
{"uint80", big.NewInt(1), ""},
|
{"uint80", nil, big.NewInt(1), ""},
|
||||||
{"uint88", big.NewInt(1), ""},
|
{"uint88", nil, big.NewInt(1), ""},
|
||||||
{"uint96", big.NewInt(1), ""},
|
{"uint96", nil, big.NewInt(1), ""},
|
||||||
{"uint104", big.NewInt(1), ""},
|
{"uint104", nil, big.NewInt(1), ""},
|
||||||
{"uint112", big.NewInt(1), ""},
|
{"uint112", nil, big.NewInt(1), ""},
|
||||||
{"uint120", big.NewInt(1), ""},
|
{"uint120", nil, big.NewInt(1), ""},
|
||||||
{"uint128", big.NewInt(1), ""},
|
{"uint128", nil, big.NewInt(1), ""},
|
||||||
{"uint136", big.NewInt(1), ""},
|
{"uint136", nil, big.NewInt(1), ""},
|
||||||
{"uint144", big.NewInt(1), ""},
|
{"uint144", nil, big.NewInt(1), ""},
|
||||||
{"uint152", big.NewInt(1), ""},
|
{"uint152", nil, big.NewInt(1), ""},
|
||||||
{"uint160", big.NewInt(1), ""},
|
{"uint160", nil, big.NewInt(1), ""},
|
||||||
{"uint168", big.NewInt(1), ""},
|
{"uint168", nil, big.NewInt(1), ""},
|
||||||
{"uint176", big.NewInt(1), ""},
|
{"uint176", nil, big.NewInt(1), ""},
|
||||||
{"uint184", big.NewInt(1), ""},
|
{"uint184", nil, big.NewInt(1), ""},
|
||||||
{"uint192", big.NewInt(1), ""},
|
{"uint192", nil, big.NewInt(1), ""},
|
||||||
{"uint200", big.NewInt(1), ""},
|
{"uint200", nil, big.NewInt(1), ""},
|
||||||
{"uint208", big.NewInt(1), ""},
|
{"uint208", nil, big.NewInt(1), ""},
|
||||||
{"uint216", big.NewInt(1), ""},
|
{"uint216", nil, big.NewInt(1), ""},
|
||||||
{"uint224", big.NewInt(1), ""},
|
{"uint224", nil, big.NewInt(1), ""},
|
||||||
{"uint232", big.NewInt(1), ""},
|
{"uint232", nil, big.NewInt(1), ""},
|
||||||
{"uint240", big.NewInt(1), ""},
|
{"uint240", nil, big.NewInt(1), ""},
|
||||||
{"uint248", big.NewInt(1), ""},
|
{"uint248", nil, big.NewInt(1), ""},
|
||||||
{"int24", big.NewInt(1), ""},
|
{"int24", nil, big.NewInt(1), ""},
|
||||||
{"int40", big.NewInt(1), ""},
|
{"int40", nil, big.NewInt(1), ""},
|
||||||
{"int48", big.NewInt(1), ""},
|
{"int48", nil, big.NewInt(1), ""},
|
||||||
{"int56", big.NewInt(1), ""},
|
{"int56", nil, big.NewInt(1), ""},
|
||||||
{"int72", big.NewInt(1), ""},
|
{"int72", nil, big.NewInt(1), ""},
|
||||||
{"int80", big.NewInt(1), ""},
|
{"int80", nil, big.NewInt(1), ""},
|
||||||
{"int88", big.NewInt(1), ""},
|
{"int88", nil, big.NewInt(1), ""},
|
||||||
{"int96", big.NewInt(1), ""},
|
{"int96", nil, big.NewInt(1), ""},
|
||||||
{"int104", big.NewInt(1), ""},
|
{"int104", nil, big.NewInt(1), ""},
|
||||||
{"int112", big.NewInt(1), ""},
|
{"int112", nil, big.NewInt(1), ""},
|
||||||
{"int120", big.NewInt(1), ""},
|
{"int120", nil, big.NewInt(1), ""},
|
||||||
{"int128", big.NewInt(1), ""},
|
{"int128", nil, big.NewInt(1), ""},
|
||||||
{"int136", big.NewInt(1), ""},
|
{"int136", nil, big.NewInt(1), ""},
|
||||||
{"int144", big.NewInt(1), ""},
|
{"int144", nil, big.NewInt(1), ""},
|
||||||
{"int152", big.NewInt(1), ""},
|
{"int152", nil, big.NewInt(1), ""},
|
||||||
{"int160", big.NewInt(1), ""},
|
{"int160", nil, big.NewInt(1), ""},
|
||||||
{"int168", big.NewInt(1), ""},
|
{"int168", nil, big.NewInt(1), ""},
|
||||||
{"int176", big.NewInt(1), ""},
|
{"int176", nil, big.NewInt(1), ""},
|
||||||
{"int184", big.NewInt(1), ""},
|
{"int184", nil, big.NewInt(1), ""},
|
||||||
{"int192", big.NewInt(1), ""},
|
{"int192", nil, big.NewInt(1), ""},
|
||||||
{"int200", big.NewInt(1), ""},
|
{"int200", nil, big.NewInt(1), ""},
|
||||||
{"int208", big.NewInt(1), ""},
|
{"int208", nil, big.NewInt(1), ""},
|
||||||
{"int216", big.NewInt(1), ""},
|
{"int216", nil, big.NewInt(1), ""},
|
||||||
{"int224", big.NewInt(1), ""},
|
{"int224", nil, big.NewInt(1), ""},
|
||||||
{"int232", big.NewInt(1), ""},
|
{"int232", nil, big.NewInt(1), ""},
|
||||||
{"int240", big.NewInt(1), ""},
|
{"int240", nil, big.NewInt(1), ""},
|
||||||
{"int248", big.NewInt(1), ""},
|
{"int248", nil, big.NewInt(1), ""},
|
||||||
{"uint30", uint8(1), "abi: cannot use uint8 as type ptr as argument"},
|
{"uint30", nil, uint8(1), "abi: cannot use uint8 as type ptr as argument"},
|
||||||
{"uint8", uint16(1), "abi: cannot use uint16 as type uint8 as argument"},
|
{"uint8", nil, uint16(1), "abi: cannot use uint16 as type uint8 as argument"},
|
||||||
{"uint8", uint32(1), "abi: cannot use uint32 as type uint8 as argument"},
|
{"uint8", nil, uint32(1), "abi: cannot use uint32 as type uint8 as argument"},
|
||||||
{"uint8", uint64(1), "abi: cannot use uint64 as type uint8 as argument"},
|
{"uint8", nil, uint64(1), "abi: cannot use uint64 as type uint8 as argument"},
|
||||||
{"uint8", int8(1), "abi: cannot use int8 as type uint8 as argument"},
|
{"uint8", nil, int8(1), "abi: cannot use int8 as type uint8 as argument"},
|
||||||
{"uint8", int16(1), "abi: cannot use int16 as type uint8 as argument"},
|
{"uint8", nil, int16(1), "abi: cannot use int16 as type uint8 as argument"},
|
||||||
{"uint8", int32(1), "abi: cannot use int32 as type uint8 as argument"},
|
{"uint8", nil, int32(1), "abi: cannot use int32 as type uint8 as argument"},
|
||||||
{"uint8", int64(1), "abi: cannot use int64 as type uint8 as argument"},
|
{"uint8", nil, int64(1), "abi: cannot use int64 as type uint8 as argument"},
|
||||||
{"uint16", uint16(1), ""},
|
{"uint16", nil, uint16(1), ""},
|
||||||
{"uint16", uint8(1), "abi: cannot use uint8 as type uint16 as argument"},
|
{"uint16", nil, uint8(1), "abi: cannot use uint8 as type uint16 as argument"},
|
||||||
{"uint16[]", []uint16{1, 2, 3}, ""},
|
{"uint16[]", nil, []uint16{1, 2, 3}, ""},
|
||||||
{"uint16[]", [3]uint16{1, 2, 3}, ""},
|
{"uint16[]", nil, [3]uint16{1, 2, 3}, ""},
|
||||||
{"uint16[]", []uint32{1, 2, 3}, "abi: cannot use []uint32 as type [0]uint16 as argument"},
|
{"uint16[]", nil, []uint32{1, 2, 3}, "abi: cannot use []uint32 as type [0]uint16 as argument"},
|
||||||
{"uint16[3]", [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"},
|
{"uint16[3]", nil, [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"},
|
||||||
{"uint16[3]", [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
{"uint16[3]", nil, [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
||||||
{"uint16[3]", []uint16{1, 2, 3}, ""},
|
{"uint16[3]", nil, []uint16{1, 2, 3}, ""},
|
||||||
{"uint16[3]", []uint16{1, 2, 3, 4}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
{"uint16[3]", nil, []uint16{1, 2, 3, 4}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
||||||
{"address[]", []common.Address{{1}}, ""},
|
{"address[]", nil, []common.Address{{1}}, ""},
|
||||||
{"address[1]", []common.Address{{1}}, ""},
|
{"address[1]", nil, []common.Address{{1}}, ""},
|
||||||
{"address[1]", [1]common.Address{{1}}, ""},
|
{"address[1]", nil, [1]common.Address{{1}}, ""},
|
||||||
{"address[2]", [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"},
|
{"address[2]", nil, [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"},
|
||||||
{"bytes32", [32]byte{}, ""},
|
{"bytes32", nil, [32]byte{}, ""},
|
||||||
{"bytes31", [31]byte{}, ""},
|
{"bytes31", nil, [31]byte{}, ""},
|
||||||
{"bytes30", [30]byte{}, ""},
|
{"bytes30", nil, [30]byte{}, ""},
|
||||||
{"bytes29", [29]byte{}, ""},
|
{"bytes29", nil, [29]byte{}, ""},
|
||||||
{"bytes28", [28]byte{}, ""},
|
{"bytes28", nil, [28]byte{}, ""},
|
||||||
{"bytes27", [27]byte{}, ""},
|
{"bytes27", nil, [27]byte{}, ""},
|
||||||
{"bytes26", [26]byte{}, ""},
|
{"bytes26", nil, [26]byte{}, ""},
|
||||||
{"bytes25", [25]byte{}, ""},
|
{"bytes25", nil, [25]byte{}, ""},
|
||||||
{"bytes24", [24]byte{}, ""},
|
{"bytes24", nil, [24]byte{}, ""},
|
||||||
{"bytes23", [23]byte{}, ""},
|
{"bytes23", nil, [23]byte{}, ""},
|
||||||
{"bytes22", [22]byte{}, ""},
|
{"bytes22", nil, [22]byte{}, ""},
|
||||||
{"bytes21", [21]byte{}, ""},
|
{"bytes21", nil, [21]byte{}, ""},
|
||||||
{"bytes20", [20]byte{}, ""},
|
{"bytes20", nil, [20]byte{}, ""},
|
||||||
{"bytes19", [19]byte{}, ""},
|
{"bytes19", nil, [19]byte{}, ""},
|
||||||
{"bytes18", [18]byte{}, ""},
|
{"bytes18", nil, [18]byte{}, ""},
|
||||||
{"bytes17", [17]byte{}, ""},
|
{"bytes17", nil, [17]byte{}, ""},
|
||||||
{"bytes16", [16]byte{}, ""},
|
{"bytes16", nil, [16]byte{}, ""},
|
||||||
{"bytes15", [15]byte{}, ""},
|
{"bytes15", nil, [15]byte{}, ""},
|
||||||
{"bytes14", [14]byte{}, ""},
|
{"bytes14", nil, [14]byte{}, ""},
|
||||||
{"bytes13", [13]byte{}, ""},
|
{"bytes13", nil, [13]byte{}, ""},
|
||||||
{"bytes12", [12]byte{}, ""},
|
{"bytes12", nil, [12]byte{}, ""},
|
||||||
{"bytes11", [11]byte{}, ""},
|
{"bytes11", nil, [11]byte{}, ""},
|
||||||
{"bytes10", [10]byte{}, ""},
|
{"bytes10", nil, [10]byte{}, ""},
|
||||||
{"bytes9", [9]byte{}, ""},
|
{"bytes9", nil, [9]byte{}, ""},
|
||||||
{"bytes8", [8]byte{}, ""},
|
{"bytes8", nil, [8]byte{}, ""},
|
||||||
{"bytes7", [7]byte{}, ""},
|
{"bytes7", nil, [7]byte{}, ""},
|
||||||
{"bytes6", [6]byte{}, ""},
|
{"bytes6", nil, [6]byte{}, ""},
|
||||||
{"bytes5", [5]byte{}, ""},
|
{"bytes5", nil, [5]byte{}, ""},
|
||||||
{"bytes4", [4]byte{}, ""},
|
{"bytes4", nil, [4]byte{}, ""},
|
||||||
{"bytes3", [3]byte{}, ""},
|
{"bytes3", nil, [3]byte{}, ""},
|
||||||
{"bytes2", [2]byte{}, ""},
|
{"bytes2", nil, [2]byte{}, ""},
|
||||||
{"bytes1", [1]byte{}, ""},
|
{"bytes1", nil, [1]byte{}, ""},
|
||||||
{"bytes32", [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"},
|
{"bytes32", nil, [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"},
|
||||||
{"bytes32", common.Hash{1}, ""},
|
{"bytes32", nil, common.Hash{1}, ""},
|
||||||
{"bytes31", common.Hash{1}, "abi: cannot use common.Hash as type [31]uint8 as argument"},
|
{"bytes31", nil, common.Hash{1}, "abi: cannot use common.Hash as type [31]uint8 as argument"},
|
||||||
{"bytes31", [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"},
|
{"bytes31", nil, [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"},
|
||||||
{"bytes", []byte{0, 1}, ""},
|
{"bytes", nil, []byte{0, 1}, ""},
|
||||||
{"bytes", [2]byte{0, 1}, "abi: cannot use array as type slice as argument"},
|
{"bytes", nil, [2]byte{0, 1}, "abi: cannot use array as type slice as argument"},
|
||||||
{"bytes", common.Hash{1}, "abi: cannot use array as type slice as argument"},
|
{"bytes", nil, common.Hash{1}, "abi: cannot use array as type slice as argument"},
|
||||||
{"string", "hello world", ""},
|
{"string", nil, "hello world", ""},
|
||||||
{"string", string(""), ""},
|
{"string", nil, "", ""},
|
||||||
{"string", []byte{}, "abi: cannot use slice as type string as argument"},
|
{"string", nil, []byte{}, "abi: cannot use slice as type string as argument"},
|
||||||
{"bytes32[]", [][32]byte{{}}, ""},
|
{"bytes32[]", nil, [][32]byte{{}}, ""},
|
||||||
{"function", [24]byte{}, ""},
|
{"function", nil, [24]byte{}, ""},
|
||||||
{"bytes20", common.Address{}, ""},
|
{"bytes20", nil, common.Address{}, ""},
|
||||||
{"address", [20]byte{}, ""},
|
{"address", nil, [20]byte{}, ""},
|
||||||
{"address", common.Address{}, ""},
|
{"address", nil, common.Address{}, ""},
|
||||||
|
{"bytes32[]]", nil, "", "invalid arg type in abi"},
|
||||||
|
{"invalidType", nil, "", "unsupported arg type: invalidType"},
|
||||||
|
{"invalidSlice[]", nil, "", "unsupported arg type: invalidSlice"},
|
||||||
|
// simple tuple
|
||||||
|
{"tuple", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}{}, ""},
|
||||||
|
// tuple slice
|
||||||
|
{"tuple[]", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, []struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}{}, ""},
|
||||||
|
// tuple array
|
||||||
|
{"tuple[2]", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, []struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}{{big.NewInt(0), big.NewInt(0)}, {big.NewInt(0), big.NewInt(0)}}, ""},
|
||||||
} {
|
} {
|
||||||
typ, err := NewType(test.typ)
|
typ, err := NewType(test.typ, "", test.components)
|
||||||
if err != nil && len(test.err) == 0 {
|
if err != nil && len(test.err) == 0 {
|
||||||
t.Fatal("unexpected parse error:", err)
|
t.Fatal("unexpected parse error:", err)
|
||||||
} else if err != nil && len(test.err) != 0 {
|
} else if err != nil && len(test.err) != 0 {
|
||||||
|
|
@ -278,3 +308,73 @@ func TestTypeCheck(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInternalType(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
components := []ArgumentMarshaling{{Name: "a", Type: "int64"}}
|
||||||
|
internalType := "struct a.b[]"
|
||||||
|
kind := Type{
|
||||||
|
T: TupleTy,
|
||||||
|
TupleType: reflect.TypeOf(struct {
|
||||||
|
A int64 `json:"a"`
|
||||||
|
}{}),
|
||||||
|
stringKind: "(int64)",
|
||||||
|
TupleRawName: "ab[]",
|
||||||
|
TupleElems: []*Type{{T: IntTy, Size: 64, stringKind: "int64"}},
|
||||||
|
TupleRawNames: []string{"a"},
|
||||||
|
}
|
||||||
|
|
||||||
|
blob := "tuple"
|
||||||
|
typ, err := NewType(blob, internalType, components)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("type %q: failed to parse type string: %v", blob, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(typ, kind) {
|
||||||
|
t.Errorf("type %q: parsed type mismatch:\nGOT %s\nWANT %s ", blob, spew.Sdump(typeWithoutStringer(typ)), spew.Sdump(typeWithoutStringer(kind)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTypeSize(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var testCases = []struct {
|
||||||
|
typ string
|
||||||
|
components []ArgumentMarshaling
|
||||||
|
typSize int
|
||||||
|
}{
|
||||||
|
// simple array
|
||||||
|
{"uint256[2]", nil, 32 * 2},
|
||||||
|
{"address[3]", nil, 32 * 3},
|
||||||
|
{"bytes32[4]", nil, 32 * 4},
|
||||||
|
// array array
|
||||||
|
{"uint256[2][3][4]", nil, 32 * (2 * 3 * 4)},
|
||||||
|
// array tuple
|
||||||
|
{"tuple[2]", []ArgumentMarshaling{{Name: "x", Type: "bytes32"}, {Name: "y", Type: "bytes32"}}, (32 * 2) * 2},
|
||||||
|
// simple tuple
|
||||||
|
{"tuple", []ArgumentMarshaling{{Name: "x", Type: "uint256"}, {Name: "y", Type: "uint256"}}, 32 * 2},
|
||||||
|
// tuple array
|
||||||
|
{"tuple", []ArgumentMarshaling{{Name: "x", Type: "bytes32[2]"}}, 32 * 2},
|
||||||
|
// tuple tuple
|
||||||
|
{"tuple", []ArgumentMarshaling{{Name: "x", Type: "tuple", Components: []ArgumentMarshaling{{Name: "x", Type: "bytes32"}}}}, 32},
|
||||||
|
{"tuple", []ArgumentMarshaling{{Name: "x", Type: "tuple", Components: []ArgumentMarshaling{{Name: "x", Type: "bytes32[2]"}, {Name: "y", Type: "uint256"}}}}, 32 * (2 + 1)},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, data := range testCases {
|
||||||
|
typ, err := NewType(data.typ, "", data.components)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("type %q: failed to parse type string: %v", data.typ, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := getTypeSize(typ)
|
||||||
|
if result != data.typSize {
|
||||||
|
t.Errorf("case %d type %q: get type size error: actual: %d expected: %d", i, data.typ, result, data.typSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewFixedBytesOver32(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_, err := NewType("bytes4096", "", nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("fixed bytes with size over 32 is not spec'd")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,37 +20,91 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// reads the integer based on its kind
|
var (
|
||||||
func readInteger(kind reflect.Kind, b []byte) interface{} {
|
// MaxUint256 is the maximum value that can be represented by a uint256.
|
||||||
switch kind {
|
MaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1)
|
||||||
case reflect.Uint8:
|
// MaxInt256 is the maximum value that can be represented by a int256.
|
||||||
return b[len(b)-1]
|
MaxInt256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 255), common.Big1)
|
||||||
case reflect.Uint16:
|
)
|
||||||
return binary.BigEndian.Uint16(b[len(b)-2:])
|
|
||||||
case reflect.Uint32:
|
// ReadInteger reads the integer based on its kind and returns the appropriate value.
|
||||||
return binary.BigEndian.Uint32(b[len(b)-4:])
|
func ReadInteger(typ Type, b []byte) (interface{}, error) {
|
||||||
case reflect.Uint64:
|
ret := new(big.Int).SetBytes(b)
|
||||||
return binary.BigEndian.Uint64(b[len(b)-8:])
|
|
||||||
case reflect.Int8:
|
if typ.T == UintTy {
|
||||||
return int8(b[len(b)-1])
|
u64, isu64 := ret.Uint64(), ret.IsUint64()
|
||||||
case reflect.Int16:
|
switch typ.Size {
|
||||||
return int16(binary.BigEndian.Uint16(b[len(b)-2:]))
|
case 8:
|
||||||
case reflect.Int32:
|
if !isu64 || u64 > math.MaxUint8 {
|
||||||
return int32(binary.BigEndian.Uint32(b[len(b)-4:]))
|
return nil, errBadUint8
|
||||||
case reflect.Int64:
|
}
|
||||||
return int64(binary.BigEndian.Uint64(b[len(b)-8:]))
|
return byte(u64), nil
|
||||||
|
case 16:
|
||||||
|
if !isu64 || u64 > math.MaxUint16 {
|
||||||
|
return nil, errBadUint16
|
||||||
|
}
|
||||||
|
return uint16(u64), nil
|
||||||
|
case 32:
|
||||||
|
if !isu64 || u64 > math.MaxUint32 {
|
||||||
|
return nil, errBadUint32
|
||||||
|
}
|
||||||
|
return uint32(u64), nil
|
||||||
|
case 64:
|
||||||
|
if !isu64 {
|
||||||
|
return nil, errBadUint64
|
||||||
|
}
|
||||||
|
return u64, nil
|
||||||
|
default:
|
||||||
|
// the only case left for unsigned integer is uint256.
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// big.SetBytes can't tell if a number is negative or positive in itself.
|
||||||
|
// On EVM, if the returned number > max int256, it is negative.
|
||||||
|
// A number is > max int256 if the bit at position 255 is set.
|
||||||
|
if ret.Bit(255) == 1 {
|
||||||
|
ret.Add(MaxUint256, new(big.Int).Neg(ret))
|
||||||
|
ret.Add(ret, common.Big1)
|
||||||
|
ret.Neg(ret)
|
||||||
|
}
|
||||||
|
i64, isi64 := ret.Int64(), ret.IsInt64()
|
||||||
|
switch typ.Size {
|
||||||
|
case 8:
|
||||||
|
if !isi64 || i64 < math.MinInt8 || i64 > math.MaxInt8 {
|
||||||
|
return nil, errBadInt8
|
||||||
|
}
|
||||||
|
return int8(i64), nil
|
||||||
|
case 16:
|
||||||
|
if !isi64 || i64 < math.MinInt16 || i64 > math.MaxInt16 {
|
||||||
|
return nil, errBadInt16
|
||||||
|
}
|
||||||
|
return int16(i64), nil
|
||||||
|
case 32:
|
||||||
|
if !isi64 || i64 < math.MinInt32 || i64 > math.MaxInt32 {
|
||||||
|
return nil, errBadInt32
|
||||||
|
}
|
||||||
|
return int32(i64), nil
|
||||||
|
case 64:
|
||||||
|
if !isi64 {
|
||||||
|
return nil, errBadInt64
|
||||||
|
}
|
||||||
|
return i64, nil
|
||||||
default:
|
default:
|
||||||
return new(big.Int).SetBytes(b)
|
// the only case left for integer is int256
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reads a bool
|
// readBool reads a bool.
|
||||||
func readBool(word []byte) (bool, error) {
|
func readBool(word []byte) (bool, error) {
|
||||||
for _, b := range word[:31] {
|
for _, b := range word[:31] {
|
||||||
if b != 0 {
|
if b != 0 {
|
||||||
|
|
@ -68,7 +122,8 @@ func readBool(word []byte) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// A function type is simply the address with the function selection signature at the end.
|
// A function type is simply the address with the function selection signature at the end.
|
||||||
// This enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes)
|
//
|
||||||
|
// readFunctionType enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes)
|
||||||
func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
||||||
if t.T != FunctionTy {
|
if t.T != FunctionTy {
|
||||||
return [24]byte{}, errors.New("abi: invalid type in call to make function type byte array")
|
return [24]byte{}, errors.New("abi: invalid type in call to make function type byte array")
|
||||||
|
|
@ -81,61 +136,46 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// through reflection, creates a fixed array to be read from
|
// ReadFixedBytes uses reflection to create a fixed array to be read from.
|
||||||
func readFixedBytes(t Type, word []byte) (interface{}, error) {
|
func ReadFixedBytes(t Type, word []byte) (interface{}, error) {
|
||||||
if t.T != FixedBytesTy {
|
if t.T != FixedBytesTy {
|
||||||
return nil, errors.New("abi: invalid type in call to make fixed byte array")
|
return nil, errors.New("abi: invalid type in call to make fixed byte array")
|
||||||
}
|
}
|
||||||
// convert
|
// convert
|
||||||
array := reflect.New(t.Type).Elem()
|
array := reflect.New(t.GetType()).Elem()
|
||||||
|
|
||||||
reflect.Copy(array, reflect.ValueOf(word[0:t.Size]))
|
reflect.Copy(array, reflect.ValueOf(word[0:t.Size]))
|
||||||
return array.Interface(), nil
|
return array.Interface(), nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFullElemSize(elem *Type) int {
|
// forEachUnpack iteratively unpack elements.
|
||||||
//all other should be counted as 32 (slices have pointers to respective elements)
|
|
||||||
size := 32
|
|
||||||
//arrays wrap it, each element being the same size
|
|
||||||
for elem.T == ArrayTy {
|
|
||||||
size *= elem.Size
|
|
||||||
elem = elem.Elem
|
|
||||||
}
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
|
|
||||||
// iteratively unpack elements
|
|
||||||
func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) {
|
func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) {
|
||||||
if size < 0 {
|
if size < 0 {
|
||||||
return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size)
|
return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size)
|
||||||
}
|
}
|
||||||
if start+32*size > len(output) {
|
if start+32*size > len(output) {
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size)
|
return nil, fmt.Errorf("abi: cannot marshal into go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// this value will become our slice or our array, depending on the type
|
// this value will become our slice or our array, depending on the type
|
||||||
var refSlice reflect.Value
|
var refSlice reflect.Value
|
||||||
|
|
||||||
if t.T == SliceTy {
|
switch t.T {
|
||||||
|
case SliceTy:
|
||||||
// declare our slice
|
// declare our slice
|
||||||
refSlice = reflect.MakeSlice(t.Type, size, size)
|
refSlice = reflect.MakeSlice(t.GetType(), size, size)
|
||||||
} else if t.T == ArrayTy {
|
case ArrayTy:
|
||||||
// declare our array
|
// declare our array
|
||||||
refSlice = reflect.New(t.Type).Elem()
|
refSlice = reflect.New(t.GetType()).Elem()
|
||||||
} else {
|
default:
|
||||||
return nil, errors.New("abi: invalid type in array/slice unpacking stage")
|
return nil, errors.New("abi: invalid type in array/slice unpacking stage")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arrays have packed elements, resulting in longer unpack steps.
|
// Arrays have packed elements, resulting in longer unpack steps.
|
||||||
// Slices have just 32 bytes per element (pointing to the contents).
|
// Slices have just 32 bytes per element (pointing to the contents).
|
||||||
elemSize := 32
|
elemSize := getTypeSize(*t.Elem)
|
||||||
if t.T == ArrayTy {
|
|
||||||
elemSize = getFullElemSize(t.Elem)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, j := start, 0; j < size; i, j = i+elemSize, j+1 {
|
for i, j := start, 0; j < size; i, j = i+elemSize, j+1 {
|
||||||
|
|
||||||
inter, err := toGoType(i, *t.Elem, output)
|
inter, err := toGoType(i, *t.Elem, output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -149,6 +189,36 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
|
||||||
return refSlice.Interface(), nil
|
return refSlice.Interface(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func forTupleUnpack(t Type, output []byte) (interface{}, error) {
|
||||||
|
retval := reflect.New(t.GetType()).Elem()
|
||||||
|
virtualArgs := 0
|
||||||
|
for index, elem := range t.TupleElems {
|
||||||
|
marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if elem.T == ArrayTy && !isDynamicType(*elem) {
|
||||||
|
// If we have a static array, like [3]uint256, these are coded as
|
||||||
|
// just like uint256,uint256,uint256.
|
||||||
|
// This means that we need to add two 'virtual' arguments when
|
||||||
|
// we count the index from now on.
|
||||||
|
//
|
||||||
|
// Array values nested multiple levels deep are also encoded inline:
|
||||||
|
// [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256
|
||||||
|
//
|
||||||
|
// Calculate the full array size to get the correct offset for the next argument.
|
||||||
|
// Decrement it by 1, as the normal index increment is still applied.
|
||||||
|
virtualArgs += getTypeSize(*elem)/32 - 1
|
||||||
|
} else if elem.T == TupleTy && !isDynamicType(*elem) {
|
||||||
|
// If we have a static tuple, like (uint256, bool, uint256), these are
|
||||||
|
// coded as just like uint256,bool,uint256
|
||||||
|
virtualArgs += getTypeSize(*elem)/32 - 1
|
||||||
|
}
|
||||||
|
retval.Field(index).Set(reflect.ValueOf(marshalledValue))
|
||||||
|
}
|
||||||
|
return retval.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// toGoType parses the output bytes and recursively assigns the value of these bytes
|
// toGoType parses the output bytes and recursively assigns the value of these bytes
|
||||||
// into a go type with accordance with the ABI spec.
|
// into a go type with accordance with the ABI spec.
|
||||||
func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
||||||
|
|
@ -157,14 +227,14 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
returnOutput []byte
|
returnOutput []byte
|
||||||
begin, end int
|
begin, length int
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
// if we require a length prefix, find the beginning word and size returned.
|
// if we require a length prefix, find the beginning word and size returned.
|
||||||
if t.requiresLengthPrefix() {
|
if t.requiresLengthPrefix() {
|
||||||
begin, end, err = lengthPrefixPointsTo(index, output)
|
begin, length, err = lengthPrefixPointsTo(index, output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -173,14 +243,30 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t.T {
|
switch t.T {
|
||||||
|
case TupleTy:
|
||||||
|
if isDynamicType(t) {
|
||||||
|
begin, err := tuplePointsTo(index, output)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return forTupleUnpack(t, output[begin:])
|
||||||
|
}
|
||||||
|
return forTupleUnpack(t, output[index:])
|
||||||
case SliceTy:
|
case SliceTy:
|
||||||
return forEachUnpack(t, output, begin, end)
|
return forEachUnpack(t, output[begin:], 0, length)
|
||||||
case ArrayTy:
|
case ArrayTy:
|
||||||
return forEachUnpack(t, output, index, t.Size)
|
if isDynamicType(*t.Elem) {
|
||||||
|
offset := binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:])
|
||||||
|
if offset > uint64(len(output)) {
|
||||||
|
return nil, fmt.Errorf("abi: toGoType offset greater than output length: offset: %d, len(output): %d", offset, len(output))
|
||||||
|
}
|
||||||
|
return forEachUnpack(t, output[offset:], 0, t.Size)
|
||||||
|
}
|
||||||
|
return forEachUnpack(t, output[index:], 0, t.Size)
|
||||||
case StringTy: // variable arrays are written at the end of the return bytes
|
case StringTy: // variable arrays are written at the end of the return bytes
|
||||||
return string(output[begin : begin+end]), nil
|
return string(output[begin : begin+length]), nil
|
||||||
case IntTy, UintTy:
|
case IntTy, UintTy:
|
||||||
return readInteger(t.Kind, returnOutput), nil
|
return ReadInteger(t, returnOutput)
|
||||||
case BoolTy:
|
case BoolTy:
|
||||||
return readBool(returnOutput)
|
return readBool(returnOutput)
|
||||||
case AddressTy:
|
case AddressTy:
|
||||||
|
|
@ -188,9 +274,9 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
||||||
case HashTy:
|
case HashTy:
|
||||||
return common.BytesToHash(returnOutput), nil
|
return common.BytesToHash(returnOutput), nil
|
||||||
case BytesTy:
|
case BytesTy:
|
||||||
return output[begin : begin+end], nil
|
return output[begin : begin+length], nil
|
||||||
case FixedBytesTy:
|
case FixedBytesTy:
|
||||||
return readFixedBytes(t, returnOutput)
|
return ReadFixedBytes(t, returnOutput)
|
||||||
case FunctionTy:
|
case FunctionTy:
|
||||||
return readFunctionType(t, returnOutput)
|
return readFunctionType(t, returnOutput)
|
||||||
default:
|
default:
|
||||||
|
|
@ -198,9 +284,9 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// interprets a 32 byte slice as an offset and then determines which indice to look to decode the type.
|
// lengthPrefixPointsTo interprets a 32 byte slice as an offset and then determines which indices to look to decode the type.
|
||||||
func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) {
|
func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) {
|
||||||
bigOffsetEnd := big.NewInt(0).SetBytes(output[index : index+32])
|
bigOffsetEnd := new(big.Int).SetBytes(output[index : index+32])
|
||||||
bigOffsetEnd.Add(bigOffsetEnd, common.Big32)
|
bigOffsetEnd.Add(bigOffsetEnd, common.Big32)
|
||||||
outputLength := big.NewInt(int64(len(output)))
|
outputLength := big.NewInt(int64(len(output)))
|
||||||
|
|
||||||
|
|
@ -213,13 +299,11 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err
|
||||||
}
|
}
|
||||||
|
|
||||||
offsetEnd := int(bigOffsetEnd.Uint64())
|
offsetEnd := int(bigOffsetEnd.Uint64())
|
||||||
lengthBig := big.NewInt(0).SetBytes(output[offsetEnd-32 : offsetEnd])
|
lengthBig := new(big.Int).SetBytes(output[offsetEnd-32 : offsetEnd])
|
||||||
|
|
||||||
totalSize := big.NewInt(0)
|
totalSize := new(big.Int).Add(bigOffsetEnd, lengthBig)
|
||||||
totalSize.Add(totalSize, bigOffsetEnd)
|
|
||||||
totalSize.Add(totalSize, lengthBig)
|
|
||||||
if totalSize.BitLen() > 63 {
|
if totalSize.BitLen() > 63 {
|
||||||
return 0, 0, fmt.Errorf("abi length larger than int64: %v", totalSize)
|
return 0, 0, fmt.Errorf("abi: length larger than int64: %v", totalSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalSize.Cmp(outputLength) > 0 {
|
if totalSize.Cmp(outputLength) > 0 {
|
||||||
|
|
@ -229,3 +313,17 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err
|
||||||
length = int(lengthBig.Uint64())
|
length = int(lengthBig.Uint64())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tuplePointsTo resolves the location reference for dynamic tuple.
|
||||||
|
func tuplePointsTo(index int, output []byte) (start int, err error) {
|
||||||
|
offset := new(big.Int).SetBytes(output[index : index+32])
|
||||||
|
outputLen := big.NewInt(int64(len(output)))
|
||||||
|
|
||||||
|
if offset.Cmp(outputLen) > 0 {
|
||||||
|
return 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", offset, outputLen)
|
||||||
|
}
|
||||||
|
if offset.BitLen() > 63 {
|
||||||
|
return 0, fmt.Errorf("abi offset larger than int64: %v", offset)
|
||||||
|
}
|
||||||
|
return int(offset.Uint64()), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
40
accounts/abi/utils.go
Normal file
40
accounts/abi/utils.go
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// ResolveNameConflict returns the next available name for a given thing.
|
||||||
|
// This helper can be used for lots of purposes:
|
||||||
|
//
|
||||||
|
// - In solidity function overloading is supported, this function can fix
|
||||||
|
// the name conflicts of overloaded functions.
|
||||||
|
// - In golang binding generation, the parameter(in function, event, error,
|
||||||
|
// and struct definition) name will be converted to camelcase style which
|
||||||
|
// may eventually lead to name conflicts.
|
||||||
|
//
|
||||||
|
// Name conflicts are mostly resolved by adding number suffix. e.g. if the abi contains
|
||||||
|
// Methods "send" and "send1", ResolveNameConflict would return "send2" for input "send".
|
||||||
|
func ResolveNameConflict(rawName string, used func(string) bool) string {
|
||||||
|
name := rawName
|
||||||
|
ok := used(name)
|
||||||
|
for idx := 0; ok; idx++ {
|
||||||
|
name = fmt.Sprintf("%s%d", rawName, idx)
|
||||||
|
ok = used(name)
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
@ -35,11 +35,18 @@ type Account struct {
|
||||||
URL URL `json:"url"` // Optional resource locator within a backend
|
URL URL `json:"url"` // Optional resource locator within a backend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
MimetypeDataWithValidator = "data/validator"
|
||||||
|
MimetypeTypedData = "data/typed"
|
||||||
|
MimetypeClique = "application/x-clique-header"
|
||||||
|
MimetypeTextPlain = "text/plain"
|
||||||
|
)
|
||||||
|
|
||||||
// Wallet represents a software or hardware wallet that might contain one or more
|
// Wallet represents a software or hardware wallet that might contain one or more
|
||||||
// accounts (derived from the same seed).
|
// accounts (derived from the same seed).
|
||||||
type Wallet interface {
|
type Wallet interface {
|
||||||
// URL retrieves the canonical path under which this wallet is reachable. It is
|
// URL retrieves the canonical path under which this wallet is reachable. It is
|
||||||
// user by upper layers to define a sorting order over all wallets from multiple
|
// used by upper layers to define a sorting order over all wallets from multiple
|
||||||
// backends.
|
// backends.
|
||||||
URL() URL
|
URL() URL
|
||||||
|
|
||||||
|
|
@ -81,13 +88,17 @@ type Wallet interface {
|
||||||
// to discover non zero accounts and automatically add them to list of tracked
|
// to discover non zero accounts and automatically add them to list of tracked
|
||||||
// accounts.
|
// accounts.
|
||||||
//
|
//
|
||||||
// Note, self derivaton will increment the last component of the specified path
|
// Note, self derivation will increment the last component of the specified path
|
||||||
// opposed to decending into a child path to allow discovering accounts starting
|
// opposed to descending into a child path to allow discovering accounts starting
|
||||||
// from non zero components.
|
// from non zero components.
|
||||||
//
|
//
|
||||||
|
// Some hardware wallets switched derivation paths through their evolution, so
|
||||||
|
// this method supports providing multiple bases to discover old user accounts
|
||||||
|
// too. Only the last base will be used to derive the next empty account.
|
||||||
|
//
|
||||||
// You can disable automatic account discovery by calling SelfDerive with a nil
|
// You can disable automatic account discovery by calling SelfDerive with a nil
|
||||||
// chain state reader.
|
// chain state reader.
|
||||||
SelfDerive(base DerivationPath, chain ethereum.ChainStateReader)
|
SelfDerive(bases []DerivationPath, chain ethereum.ChainStateReader)
|
||||||
|
|
||||||
// SignHash requests the wallet to sign the given hash.
|
// SignHash requests the wallet to sign the given hash.
|
||||||
//
|
//
|
||||||
|
|
@ -102,31 +113,56 @@ type Wallet interface {
|
||||||
// the account in a keystore).
|
// the account in a keystore).
|
||||||
SignHash(account Account, hash []byte) ([]byte, error)
|
SignHash(account Account, hash []byte) ([]byte, error)
|
||||||
|
|
||||||
|
// SignData requests the wallet to sign the hash of the given data
|
||||||
|
// It looks up the account specified either solely via its address contained within,
|
||||||
|
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||||
|
//
|
||||||
|
// If the wallet requires additional authentication to sign the request (e.g.
|
||||||
|
// a password to decrypt the account, or a PIN code to verify the transaction),
|
||||||
|
// an AuthNeededError instance will be returned, containing infos for the user
|
||||||
|
// about which fields or actions are needed. The user may retry by providing
|
||||||
|
// the needed details via SignDataWithPassphrase, or by other means (e.g. unlock
|
||||||
|
// the account in a keystore).
|
||||||
|
SignData(account Account, mimeType string, data []byte) ([]byte, error)
|
||||||
|
|
||||||
|
// SignDataWithPassphrase is identical to SignData, but also takes a password
|
||||||
|
// NOTE: there's a chance that an erroneous call might mistake the two strings, and
|
||||||
|
// supply password in the mimetype field, or vice versa. Thus, an implementation
|
||||||
|
// should never echo the mimetype or return the mimetype in the error-response
|
||||||
|
SignDataWithPassphrase(account Account, passphrase, mimeType string, data []byte) ([]byte, error)
|
||||||
|
|
||||||
|
// SignText requests the wallet to sign the hash of a given piece of data, prefixed
|
||||||
|
// by the Ethereum prefix scheme
|
||||||
|
// It looks up the account specified either solely via its address contained within,
|
||||||
|
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||||
|
//
|
||||||
|
// If the wallet requires additional authentication to sign the request (e.g.
|
||||||
|
// a password to decrypt the account, or a PIN code to verify the transaction),
|
||||||
|
// an AuthNeededError instance will be returned, containing infos for the user
|
||||||
|
// about which fields or actions are needed. The user may retry by providing
|
||||||
|
// the needed details via SignTextWithPassphrase, or by other means (e.g. unlock
|
||||||
|
// the account in a keystore).
|
||||||
|
//
|
||||||
|
// This method should return the signature in 'canonical' format, with v 0 or 1.
|
||||||
|
SignText(account Account, text []byte) ([]byte, error)
|
||||||
|
|
||||||
|
// SignTextWithPassphrase is identical to Signtext, but also takes a password
|
||||||
|
SignTextWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
|
||||||
|
|
||||||
// SignTx requests the wallet to sign the given transaction.
|
// SignTx requests the wallet to sign the given transaction.
|
||||||
//
|
//
|
||||||
// It looks up the account specified either solely via its address contained within,
|
// It looks up the account specified either solely via its address contained within,
|
||||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||||
//
|
//
|
||||||
// If the wallet requires additional authentication to sign the request (e.g.
|
// If the wallet requires additional authentication to sign the request (e.g.
|
||||||
// a password to decrypt the account, or a PIN code o verify the transaction),
|
// a password to decrypt the account, or a PIN code to verify the transaction),
|
||||||
// an AuthNeededError instance will be returned, containing infos for the user
|
// an AuthNeededError instance will be returned, containing infos for the user
|
||||||
// about which fields or actions are needed. The user may retry by providing
|
// about which fields or actions are needed. The user may retry by providing
|
||||||
// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock
|
// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock
|
||||||
// the account in a keystore).
|
// the account in a keystore).
|
||||||
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
|
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
|
||||||
|
|
||||||
// SignHashWithPassphrase requests the wallet to sign the given hash with the
|
// SignTxWithPassphrase is identical to SignTx, but also takes a password
|
||||||
// given passphrase as extra authentication information.
|
|
||||||
//
|
|
||||||
// It looks up the account specified either solely via its address contained within,
|
|
||||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
|
||||||
SignHashWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
|
|
||||||
|
|
||||||
// SignTxWithPassphrase requests the wallet to sign the given transaction, with the
|
|
||||||
// given passphrase as extra authentication information.
|
|
||||||
//
|
|
||||||
// It looks up the account specified either solely via its address contained within,
|
|
||||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
|
||||||
SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
|
SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,7 +189,7 @@ type Backend interface {
|
||||||
// TextHash is a helper function that calculates a hash for the given message that can be
|
// TextHash is a helper function that calculates a hash for the given message that can be
|
||||||
// safely used to calculate a signature from.
|
// safely used to calculate a signature from.
|
||||||
//
|
//
|
||||||
// The hash is calulcated as
|
// The hash is calculated as
|
||||||
//
|
//
|
||||||
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
|
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
|
||||||
//
|
//
|
||||||
|
|
@ -166,13 +202,13 @@ func TextHash(data []byte) []byte {
|
||||||
// TextAndHash is a helper function that calculates a hash for the given message that can be
|
// TextAndHash is a helper function that calculates a hash for the given message that can be
|
||||||
// safely used to calculate a signature from.
|
// safely used to calculate a signature from.
|
||||||
//
|
//
|
||||||
// The hash is calulcated as
|
// The hash is calculated as
|
||||||
//
|
//
|
||||||
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
|
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
|
||||||
//
|
//
|
||||||
// This gives context to the signed message and prevents signing of transactions.
|
// This gives context to the signed message and prevents signing of transactions.
|
||||||
func TextAndHash(data []byte) ([]byte, string) {
|
func TextAndHash(data []byte) ([]byte, string) {
|
||||||
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data))
|
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
hasher := sha3.NewLegacyKeccak256()
|
||||||
hasher.Write([]byte(msg))
|
hasher.Write([]byte(msg))
|
||||||
return hasher.Sum(nil), msg
|
return hasher.Sum(nil), msg
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
// Copyright 2019 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -14,29 +14,20 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package randentropy
|
package accounts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
crand "crypto/rand"
|
"bytes"
|
||||||
"io"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Reader io.Reader = &randEntropy{}
|
func TestTextHash(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
type randEntropy struct {
|
hash := TextHash([]byte("Hello Joe"))
|
||||||
}
|
want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b")
|
||||||
|
if !bytes.Equal(hash, want) {
|
||||||
func (*randEntropy) Read(bytes []byte) (n int, err error) {
|
t.Fatalf("wrong hash: %x", hash)
|
||||||
readBytes := GetEntropyCSPRNG(len(bytes))
|
|
||||||
copy(bytes, readBytes)
|
|
||||||
return len(bytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEntropyCSPRNG(n int) []byte {
|
|
||||||
mainBuff := make([]byte, n)
|
|
||||||
_, err := io.ReadFull(crand.Reader, mainBuff)
|
|
||||||
if err != nil {
|
|
||||||
panic("reading from crypto/rand failed: " + err.Error())
|
|
||||||
}
|
}
|
||||||
return mainBuff
|
|
||||||
}
|
}
|
||||||
|
|
@ -35,14 +35,13 @@ var ErrNotSupported = errors.New("not supported")
|
||||||
|
|
||||||
// ErrInvalidPassphrase is returned when a decryption operation receives a bad
|
// ErrInvalidPassphrase is returned when a decryption operation receives a bad
|
||||||
// passphrase.
|
// passphrase.
|
||||||
var ErrInvalidPassphrase = errors.New("invalid passphrase")
|
var ErrInvalidPassphrase = errors.New("invalid password")
|
||||||
|
|
||||||
// ErrWalletAlreadyOpen is returned if a wallet is attempted to be opened the
|
// ErrWalletAlreadyOpen is returned if a wallet is attempted to be opened the
|
||||||
// second time.
|
// second time.
|
||||||
var ErrWalletAlreadyOpen = errors.New("wallet already open")
|
var ErrWalletAlreadyOpen = errors.New("wallet already open")
|
||||||
|
|
||||||
// ErrWalletClosed is returned if a wallet is attempted to be opened the
|
// ErrWalletClosed is returned if a wallet is offline.
|
||||||
// secodn time.
|
|
||||||
var ErrWalletClosed = errors.New("wallet closed")
|
var ErrWalletClosed = errors.New("wallet closed")
|
||||||
|
|
||||||
// AuthNeededError is returned by backends for signing requests where the user
|
// AuthNeededError is returned by backends for signing requests where the user
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package accounts
|
package accounts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
@ -30,22 +31,22 @@ import (
|
||||||
var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
|
var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
|
||||||
|
|
||||||
// DefaultBaseDerivationPath is the base path from which custom derivation endpoints
|
// DefaultBaseDerivationPath is the base path from which custom derivation endpoints
|
||||||
// are incremented. As such, the first account will be at m/44'/60'/0'/0, the second
|
// are incremented. As such, the first account will be at m/44'/60'/0'/0/0, the second
|
||||||
// at m/44'/60'/0'/1, etc.
|
// at m/44'/60'/0'/0/1, etc.
|
||||||
var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}
|
var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}
|
||||||
|
|
||||||
// DefaultLedgerBaseDerivationPath is the base path from which custom derivation endpoints
|
// LegacyLedgerBaseDerivationPath is the legacy base path from which custom derivation
|
||||||
// are incremented. As such, the first account will be at m/44'/60'/0'/0, the second
|
// endpoints are incremented. As such, the first account will be at m/44'/60'/0'/0, the
|
||||||
// at m/44'/60'/0'/1, etc.
|
// second at m/44'/60'/0'/1, etc.
|
||||||
var DefaultLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
|
var LegacyLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
|
||||||
|
|
||||||
// DerivationPath represents the computer friendly version of a hierarchical
|
// DerivationPath represents the computer friendly version of a hierarchical
|
||||||
// deterministic wallet account derivaion path.
|
// deterministic wallet account derivation path.
|
||||||
//
|
//
|
||||||
// The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
// The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
||||||
// defines derivation paths to be of the form:
|
// defines derivation paths to be of the form:
|
||||||
//
|
//
|
||||||
// m / purpose' / coin_type' / account' / change / address_index
|
// m / purpose' / coin_type' / account' / change / address_index
|
||||||
//
|
//
|
||||||
// The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
// The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
||||||
// defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and
|
// defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and
|
||||||
|
|
@ -133,3 +134,47 @@ func (path DerivationPath) String() string {
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON turns a derivation path into its json-serialized string
|
||||||
|
func (path DerivationPath) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(path.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON a json-serialized string back into a derivation path
|
||||||
|
func (path *DerivationPath) UnmarshalJSON(b []byte) error {
|
||||||
|
var dp string
|
||||||
|
var err error
|
||||||
|
if err = json.Unmarshal(b, &dp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*path, err = ParseDerivationPath(dp)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultIterator creates a BIP-32 path iterator, which progresses by increasing the last component:
|
||||||
|
// i.e. m/44'/60'/0'/0/0, m/44'/60'/0'/0/1, m/44'/60'/0'/0/2, ... m/44'/60'/0'/0/N.
|
||||||
|
func DefaultIterator(base DerivationPath) func() DerivationPath {
|
||||||
|
path := make(DerivationPath, len(base))
|
||||||
|
copy(path[:], base[:])
|
||||||
|
// Set it back by one, so the first call gives the first result
|
||||||
|
path[len(path)-1]--
|
||||||
|
return func() DerivationPath {
|
||||||
|
path[len(path)-1]++
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LedgerLiveIterator creates a bip44 path iterator for Ledger Live.
|
||||||
|
// Ledger Live increments the third component rather than the fifth component
|
||||||
|
// i.e. m/44'/60'/0'/0/0, m/44'/60'/1'/0/0, m/44'/60'/2'/0/0, ... m/44'/60'/N'/0/0.
|
||||||
|
func LedgerLiveIterator(base DerivationPath) func() DerivationPath {
|
||||||
|
path := make(DerivationPath, len(base))
|
||||||
|
copy(path[:], base[:])
|
||||||
|
// Set it back by one, so the first call gives the first result
|
||||||
|
path[2]--
|
||||||
|
return func() DerivationPath {
|
||||||
|
// ledgerLivePathIterator iterates on the third component
|
||||||
|
path[2]++
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package accounts
|
package accounts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
@ -24,6 +25,7 @@ import (
|
||||||
// Tests that HD derivation paths can be correctly parsed into our internal binary
|
// Tests that HD derivation paths can be correctly parsed into our internal binary
|
||||||
// representation.
|
// representation.
|
||||||
func TestHDPathParsing(t *testing.T) {
|
func TestHDPathParsing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
output DerivationPath
|
output DerivationPath
|
||||||
|
|
@ -61,7 +63,7 @@ func TestHDPathParsing(t *testing.T) {
|
||||||
// Weird inputs just to ensure they work
|
// Weird inputs just to ensure they work
|
||||||
{" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
{" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
||||||
|
|
||||||
// Invaid derivation paths
|
// Invalid derivation paths
|
||||||
{"", nil}, // Empty relative derivation path
|
{"", nil}, // Empty relative derivation path
|
||||||
{"m", nil}, // Empty absolute derivation path
|
{"m", nil}, // Empty absolute derivation path
|
||||||
{"m/", nil}, // Missing last derivation component
|
{"m/", nil}, // Missing last derivation component
|
||||||
|
|
@ -77,3 +79,42 @@ func TestHDPathParsing(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testDerive(t *testing.T, next func() DerivationPath, expected []string) {
|
||||||
|
t.Helper()
|
||||||
|
for i, want := range expected {
|
||||||
|
if have := next(); fmt.Sprintf("%v", have) != want {
|
||||||
|
t.Errorf("step %d, have %v, want %v", i, have, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHdPathIteration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testDerive(t, DefaultIterator(DefaultBaseDerivationPath),
|
||||||
|
[]string{
|
||||||
|
"m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1",
|
||||||
|
"m/44'/60'/0'/0/2", "m/44'/60'/0'/0/3",
|
||||||
|
"m/44'/60'/0'/0/4", "m/44'/60'/0'/0/5",
|
||||||
|
"m/44'/60'/0'/0/6", "m/44'/60'/0'/0/7",
|
||||||
|
"m/44'/60'/0'/0/8", "m/44'/60'/0'/0/9",
|
||||||
|
})
|
||||||
|
|
||||||
|
testDerive(t, DefaultIterator(LegacyLedgerBaseDerivationPath),
|
||||||
|
[]string{
|
||||||
|
"m/44'/60'/0'/0", "m/44'/60'/0'/1",
|
||||||
|
"m/44'/60'/0'/2", "m/44'/60'/0'/3",
|
||||||
|
"m/44'/60'/0'/4", "m/44'/60'/0'/5",
|
||||||
|
"m/44'/60'/0'/6", "m/44'/60'/0'/7",
|
||||||
|
"m/44'/60'/0'/8", "m/44'/60'/0'/9",
|
||||||
|
})
|
||||||
|
|
||||||
|
testDerive(t, LedgerLiveIterator(DefaultBaseDerivationPath),
|
||||||
|
[]string{
|
||||||
|
"m/44'/60'/0'/0/0", "m/44'/60'/1'/0/0",
|
||||||
|
"m/44'/60'/2'/0/0", "m/44'/60'/3'/0/0",
|
||||||
|
"m/44'/60'/4'/0/0", "m/44'/60'/5'/0/0",
|
||||||
|
"m/44'/60'/6'/0/0", "m/44'/60'/7'/0/0",
|
||||||
|
"m/44'/60'/8'/0/0", "m/44'/60'/9'/0/0",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -30,7 +31,7 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/log"
|
"github.com/XinFinOrg/XDPoSChain/log"
|
||||||
mapset "github.com/deckarep/golang-set"
|
mapset "github.com/deckarep/golang-set/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Minimum amount of time between cache reloads. This limit applies if the platform does
|
// Minimum amount of time between cache reloads. This limit applies if the platform does
|
||||||
|
|
@ -38,11 +39,10 @@ import (
|
||||||
// exist yet, the code will attempt to create a watcher at most this often.
|
// exist yet, the code will attempt to create a watcher at most this often.
|
||||||
const minReloadInterval = 2 * time.Second
|
const minReloadInterval = 2 * time.Second
|
||||||
|
|
||||||
type accountsByURL []accounts.Account
|
// byURL defines the sorting order for accounts.
|
||||||
|
func byURL(a, b accounts.Account) int {
|
||||||
func (s accountsByURL) Len() int { return len(s) }
|
return a.URL.Cmp(b.URL)
|
||||||
func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 }
|
}
|
||||||
func (s accountsByURL) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
|
|
||||||
// AmbiguousAddrError is returned when attempting to unlock
|
// AmbiguousAddrError is returned when attempting to unlock
|
||||||
// an address for which more than one file exists.
|
// an address for which more than one file exists.
|
||||||
|
|
@ -67,7 +67,7 @@ type accountCache struct {
|
||||||
keydir string
|
keydir string
|
||||||
watcher *watcher
|
watcher *watcher
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
all accountsByURL
|
all []accounts.Account
|
||||||
byAddr map[common.Address][]accounts.Account
|
byAddr map[common.Address][]accounts.Account
|
||||||
throttle *time.Timer
|
throttle *time.Timer
|
||||||
notify chan struct{}
|
notify chan struct{}
|
||||||
|
|
@ -79,7 +79,7 @@ func newAccountCache(keydir string) (*accountCache, chan struct{}) {
|
||||||
keydir: keydir,
|
keydir: keydir,
|
||||||
byAddr: make(map[common.Address][]accounts.Account),
|
byAddr: make(map[common.Address][]accounts.Account),
|
||||||
notify: make(chan struct{}, 1),
|
notify: make(chan struct{}, 1),
|
||||||
fileC: fileCache{all: mapset.NewThreadUnsafeSet()},
|
fileC: fileCache{all: mapset.NewThreadUnsafeSet[string]()},
|
||||||
}
|
}
|
||||||
ac.watcher = newWatcher(ac)
|
ac.watcher = newWatcher(ac)
|
||||||
return ac, ac.notify
|
return ac, ac.notify
|
||||||
|
|
@ -146,6 +146,14 @@ func (ac *accountCache) deleteByFile(path string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// watcherStarted returns true if the watcher loop started running (even if it
|
||||||
|
// has since also ended).
|
||||||
|
func (ac *accountCache) watcherStarted() bool {
|
||||||
|
ac.mu.Lock()
|
||||||
|
defer ac.mu.Unlock()
|
||||||
|
return ac.watcher.running || ac.watcher.runEnded
|
||||||
|
}
|
||||||
|
|
||||||
func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account {
|
func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account {
|
||||||
for i := range slice {
|
for i := range slice {
|
||||||
if slice[i] == elem {
|
if slice[i] == elem {
|
||||||
|
|
@ -186,7 +194,7 @@ func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
|
||||||
default:
|
default:
|
||||||
err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
|
err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
|
||||||
copy(err.Matches, matches)
|
copy(err.Matches, matches)
|
||||||
sort.Sort(accountsByURL(err.Matches))
|
slices.SortFunc(err.Matches, byURL)
|
||||||
return accounts.Account{}, err
|
return accounts.Account{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -262,26 +270,28 @@ func (ac *accountCache) scanAccounts() error {
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
log.Debug("Failed to decode keystore key", "path", path, "err", err)
|
log.Debug("Failed to decode keystore key", "path", path, "err", err)
|
||||||
case (addr == common.Address{}):
|
case addr == common.Address{}:
|
||||||
log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address")
|
log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address")
|
||||||
default:
|
default:
|
||||||
return &accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}}
|
return &accounts.Account{
|
||||||
|
Address: addr,
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: path},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Process all the file diffs
|
// Process all the file diffs
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
for _, p := range creates.ToSlice() {
|
for _, path := range creates.ToSlice() {
|
||||||
if a := readAccount(p.(string)); a != nil {
|
if a := readAccount(path); a != nil {
|
||||||
ac.add(*a)
|
ac.add(*a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, p := range deletes.ToSlice() {
|
for _, path := range deletes.ToSlice() {
|
||||||
ac.deleteByFile(p.(string))
|
ac.deleteByFile(path)
|
||||||
}
|
}
|
||||||
for _, p := range updates.ToSlice() {
|
for _, path := range updates.ToSlice() {
|
||||||
path := p.(string)
|
|
||||||
ac.deleteByFile(path)
|
ac.deleteByFile(path)
|
||||||
if a := readAccount(path); a != nil {
|
if a := readAccount(path); a != nil {
|
||||||
ac.add(*a)
|
ac.add(*a)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -51,16 +51,48 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// waitWatcherStart waits up to 1s for the keystore watcher to start.
|
||||||
|
func waitWatcherStart(ks *KeyStore) bool {
|
||||||
|
// On systems where file watch is not supported, just return "ok".
|
||||||
|
if !ks.cache.watcher.enabled() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// The watcher should start, and then exit.
|
||||||
|
for t0 := time.Now(); time.Since(t0) < 1*time.Second; time.Sleep(100 * time.Millisecond) {
|
||||||
|
if ks.cache.watcherStarted() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
||||||
|
var list []accounts.Account
|
||||||
|
for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(100 * time.Millisecond) {
|
||||||
|
list = ks.Accounts()
|
||||||
|
if reflect.DeepEqual(list, wantAccounts) {
|
||||||
|
// ks should have also received change notifications
|
||||||
|
select {
|
||||||
|
case <-ks.changes:
|
||||||
|
default:
|
||||||
|
return errors.New("wasn't notified of new accounts")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
|
||||||
|
}
|
||||||
|
|
||||||
func TestWatchNewFile(t *testing.T) {
|
func TestWatchNewFile(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
dir, ks := tmpKeyStore(t, false)
|
dir, ks := tmpKeyStore(t)
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
// Ensure the watcher is started before adding any files.
|
// Ensure the watcher is started before adding any files.
|
||||||
ks.Accounts()
|
ks.Accounts()
|
||||||
time.Sleep(1000 * time.Millisecond)
|
if !waitWatcherStart(ks) {
|
||||||
|
t.Fatal("keystore watcher didn't start in time")
|
||||||
|
}
|
||||||
// Move in the files.
|
// Move in the files.
|
||||||
wantAccounts := make([]accounts.Account, len(cachetestAccounts))
|
wantAccounts := make([]accounts.Account, len(cachetestAccounts))
|
||||||
for i := range cachetestAccounts {
|
for i := range cachetestAccounts {
|
||||||
|
|
@ -74,40 +106,26 @@ func TestWatchNewFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ks should see the accounts.
|
// ks should see the accounts.
|
||||||
var list []accounts.Account
|
if err := waitForAccounts(wantAccounts, ks); err != nil {
|
||||||
for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 {
|
t.Error(err)
|
||||||
list = ks.Accounts()
|
|
||||||
if reflect.DeepEqual(list, wantAccounts) {
|
|
||||||
// ks should have also received change notifications
|
|
||||||
select {
|
|
||||||
case <-ks.changes:
|
|
||||||
default:
|
|
||||||
t.Fatalf("wasn't notified of new accounts")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
time.Sleep(d)
|
|
||||||
}
|
}
|
||||||
t.Errorf("got %s, want %s", spew.Sdump(list), spew.Sdump(wantAccounts))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWatchNoDir(t *testing.T) {
|
func TestWatchNoDir(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Create ks but not the directory that it watches.
|
// Create ks but not the directory that it watches.
|
||||||
rand.Seed(time.Now().UnixNano())
|
dir := filepath.Join(t.TempDir(), fmt.Sprintf("eth-keystore-watchnodir-test-%d-%d", os.Getpid(), rand.Int()))
|
||||||
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
|
|
||||||
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
||||||
|
|
||||||
list := ks.Accounts()
|
list := ks.Accounts()
|
||||||
if len(list) > 0 {
|
if len(list) > 0 {
|
||||||
t.Error("initial account list not empty:", list)
|
t.Error("initial account list not empty:", list)
|
||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
// The watcher should start, and then exit.
|
||||||
|
if !waitWatcherStart(ks) {
|
||||||
|
t.Fatal("keystore watcher didn't start in time")
|
||||||
|
}
|
||||||
// Create the directory and copy a key file into it.
|
// Create the directory and copy a key file into it.
|
||||||
os.MkdirAll(dir, 0700)
|
os.MkdirAll(dir, 0700)
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
file := filepath.Join(dir, "aaa")
|
file := filepath.Join(dir, "aaa")
|
||||||
if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
|
if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -133,6 +151,7 @@ func TestWatchNoDir(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheInitialReload(t *testing.T) {
|
func TestCacheInitialReload(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cache, _ := newAccountCache(cachetestDir)
|
cache, _ := newAccountCache(cachetestDir)
|
||||||
accounts := cache.accounts()
|
accounts := cache.accounts()
|
||||||
if !reflect.DeepEqual(accounts, cachetestAccounts) {
|
if !reflect.DeepEqual(accounts, cachetestAccounts) {
|
||||||
|
|
@ -141,6 +160,7 @@ func TestCacheInitialReload(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheAddDeleteOrder(t *testing.T) {
|
func TestCacheAddDeleteOrder(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cache, _ := newAccountCache("testdata/no-such-dir")
|
cache, _ := newAccountCache("testdata/no-such-dir")
|
||||||
cache.watcher.running = true // prevent unexpected reloads
|
cache.watcher.running = true // prevent unexpected reloads
|
||||||
|
|
||||||
|
|
@ -184,7 +204,7 @@ func TestCacheAddDeleteOrder(t *testing.T) {
|
||||||
// Check that the account list is sorted by filename.
|
// Check that the account list is sorted by filename.
|
||||||
wantAccounts := make([]accounts.Account, len(accs))
|
wantAccounts := make([]accounts.Account, len(accs))
|
||||||
copy(wantAccounts, accs)
|
copy(wantAccounts, accs)
|
||||||
sort.Sort(accountsByURL(wantAccounts))
|
slices.SortFunc(wantAccounts, byURL)
|
||||||
list := cache.accounts()
|
list := cache.accounts()
|
||||||
if !reflect.DeepEqual(list, wantAccounts) {
|
if !reflect.DeepEqual(list, wantAccounts) {
|
||||||
t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
|
t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
|
||||||
|
|
@ -225,6 +245,7 @@ func TestCacheAddDeleteOrder(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheFind(t *testing.T) {
|
func TestCacheFind(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := filepath.Join("testdata", "dir")
|
dir := filepath.Join("testdata", "dir")
|
||||||
cache, _ := newAccountCache(dir)
|
cache, _ := newAccountCache(dir)
|
||||||
cache.watcher.running = true // prevent unexpected reloads
|
cache.watcher.running = true // prevent unexpected reloads
|
||||||
|
|
@ -297,43 +318,24 @@ func TestCacheFind(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
|
||||||
var list []accounts.Account
|
|
||||||
for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
|
|
||||||
list = ks.Accounts()
|
|
||||||
if reflect.DeepEqual(list, wantAccounts) {
|
|
||||||
// ks should have also received change notifications
|
|
||||||
select {
|
|
||||||
case <-ks.changes:
|
|
||||||
default:
|
|
||||||
return errors.New("wasn't notified of new accounts")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
time.Sleep(d)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestUpdatedKeyfileContents tests that updating the contents of a keystore file
|
// TestUpdatedKeyfileContents tests that updating the contents of a keystore file
|
||||||
// is noticed by the watcher, and the account cache is updated accordingly
|
// is noticed by the watcher, and the account cache is updated accordingly
|
||||||
func TestUpdatedKeyfileContents(t *testing.T) {
|
func TestUpdatedKeyfileContents(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Create a temporary kesytore to test with
|
// Create a temporary keystore to test with
|
||||||
rand.Seed(time.Now().UnixNano())
|
dir := t.TempDir()
|
||||||
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
|
|
||||||
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
||||||
|
|
||||||
list := ks.Accounts()
|
list := ks.Accounts()
|
||||||
if len(list) > 0 {
|
if len(list) > 0 {
|
||||||
t.Error("initial account list not empty:", list)
|
t.Error("initial account list not empty:", list)
|
||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
if !waitWatcherStart(ks) {
|
||||||
|
t.Fatal("keystore watcher didn't start in time")
|
||||||
// Create the directory and copy a key file into it.
|
}
|
||||||
os.MkdirAll(dir, 0700)
|
// Copy a key file into it
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
file := filepath.Join(dir, "aaa")
|
file := filepath.Join(dir, "aaa")
|
||||||
|
|
||||||
// Place one of our testfiles in there
|
// Place one of our testfiles in there
|
||||||
|
|
@ -348,9 +350,8 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
||||||
time.Sleep(1000 * time.Millisecond)
|
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second))
|
||||||
|
|
||||||
// Now replace file contents
|
// Now replace file contents
|
||||||
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
|
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
|
||||||
|
|
@ -366,7 +367,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
||||||
time.Sleep(1000 * time.Millisecond)
|
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second))
|
||||||
|
|
||||||
// Now replace file contents again
|
// Now replace file contents again
|
||||||
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
|
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
|
||||||
|
|
@ -382,10 +383,10 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// needed so that modTime of `file` is different to its current value after os.WriteFile
|
// needed so that modTime of `file` is different to its current value after os.WriteFile
|
||||||
time.Sleep(1000 * time.Millisecond)
|
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second))
|
||||||
|
|
||||||
// Now replace file contents with crap
|
// Now replace file contents with crap
|
||||||
if err := os.WriteFile(file, []byte("foo"), 0644); err != nil {
|
if err := os.WriteFile(file, []byte("foo"), 0600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,22 +24,22 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/log"
|
"github.com/XinFinOrg/XDPoSChain/log"
|
||||||
mapset "github.com/deckarep/golang-set"
|
mapset "github.com/deckarep/golang-set/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// fileCache is a cache of files seen during scan of keystore.
|
// fileCache is a cache of files seen during scan of keystore.
|
||||||
type fileCache struct {
|
type fileCache struct {
|
||||||
all mapset.Set // Set of all files from the keystore folder
|
all mapset.Set[string] // Set of all files from the keystore folder
|
||||||
lastMod time.Time // Last time instance when a file was modified
|
lastMod time.Time // Last time instance when a file was modified
|
||||||
mu sync.RWMutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// scan performs a new scan on the given directory, compares against the already
|
// scan performs a new scan on the given directory, compares against the already
|
||||||
// cached filenames, and returns file sets: creates, deletes, updates.
|
// cached filenames, and returns file sets: creates, deletes, updates.
|
||||||
func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, error) {
|
func (fc *fileCache) scan(keyDir string) (mapset.Set[string], mapset.Set[string], mapset.Set[string], error) {
|
||||||
t0 := time.Now()
|
t0 := time.Now()
|
||||||
|
|
||||||
// List all the failes from the keystore folder
|
// List all the files from the keystore folder
|
||||||
files, err := os.ReadDir(keyDir)
|
files, err := os.ReadDir(keyDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
|
|
@ -50,25 +50,25 @@ func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, er
|
||||||
defer fc.mu.Unlock()
|
defer fc.mu.Unlock()
|
||||||
|
|
||||||
// Iterate all the files and gather their metadata
|
// Iterate all the files and gather their metadata
|
||||||
all := mapset.NewThreadUnsafeSet()
|
all := mapset.NewThreadUnsafeSet[string]()
|
||||||
mods := mapset.NewThreadUnsafeSet()
|
mods := mapset.NewThreadUnsafeSet[string]()
|
||||||
|
|
||||||
var newLastMod time.Time
|
var newLastMod time.Time
|
||||||
for _, fi := range files {
|
for _, fi := range files {
|
||||||
// Skip any non-key files from the folder
|
|
||||||
path := filepath.Join(keyDir, fi.Name())
|
path := filepath.Join(keyDir, fi.Name())
|
||||||
fiInfo, err := fi.Info()
|
// Skip any non-key files from the folder
|
||||||
if err != nil {
|
if nonKeyFile(fi) {
|
||||||
log.Warn("scan get FileInfo", "err", err, "path", path)
|
|
||||||
}
|
|
||||||
if fiInfo == nil || skipKeyFile(fiInfo) {
|
|
||||||
log.Trace("Ignoring file on account scan", "path", path)
|
log.Trace("Ignoring file on account scan", "path", path)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Gather the set of all and fresly modified files
|
// Gather the set of all and freshly modified files
|
||||||
all.Add(path)
|
all.Add(path)
|
||||||
|
|
||||||
modified := fiInfo.ModTime()
|
info, err := fi.Info()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
modified := info.ModTime()
|
||||||
if modified.After(fc.lastMod) {
|
if modified.After(fc.lastMod) {
|
||||||
mods.Add(path)
|
mods.Add(path)
|
||||||
}
|
}
|
||||||
|
|
@ -91,15 +91,14 @@ func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, er
|
||||||
return creates, deletes, updates, nil
|
return creates, deletes, updates, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// skipKeyFile ignores editor backups, hidden files and folders/symlinks.
|
// nonKeyFile ignores editor backups, hidden files and folders/symlinks.
|
||||||
func skipKeyFile(fi os.FileInfo) bool {
|
func nonKeyFile(fi os.DirEntry) bool {
|
||||||
// Skip editor backups and UNIX-style hidden files.
|
// Skip editor backups and UNIX-style hidden files.
|
||||||
name := fi.Name()
|
if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
|
||||||
if strings.HasSuffix(name, "~") || strings.HasPrefix(name, ".") {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// Skip misc special files, directories (yes, symlinks too).
|
// Skip misc special files, directories (yes, symlinks too).
|
||||||
if fi.IsDir() || fi.Mode()&os.ModeType != 0 {
|
if fi.IsDir() || !fi.Type().IsRegular() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
"github.com/pborman/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -64,19 +64,19 @@ type plainKeyJSON struct {
|
||||||
|
|
||||||
type encryptedKeyJSONV3 struct {
|
type encryptedKeyJSONV3 struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Crypto cryptoJSON `json:"crypto"`
|
Crypto CryptoJSON `json:"crypto"`
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type encryptedKeyJSONV1 struct {
|
type encryptedKeyJSONV1 struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Crypto cryptoJSON `json:"crypto"`
|
Crypto CryptoJSON `json:"crypto"`
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type cryptoJSON struct {
|
type CryptoJSON struct {
|
||||||
Cipher string `json:"cipher"`
|
Cipher string `json:"cipher"`
|
||||||
CipherText string `json:"ciphertext"`
|
CipherText string `json:"ciphertext"`
|
||||||
CipherParams cipherparamsJSON `json:"cipherparams"`
|
CipherParams cipherparamsJSON `json:"cipherparams"`
|
||||||
|
|
@ -108,7 +108,10 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
u := new(uuid.UUID)
|
u := new(uuid.UUID)
|
||||||
*u = uuid.Parse(keyJSON.Id)
|
*u, err = uuid.Parse(keyJSON.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
k.Id = *u
|
k.Id = *u
|
||||||
addr, err := hex.DecodeString(keyJSON.Address)
|
addr, err := hex.DecodeString(keyJSON.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -126,7 +129,10 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
|
func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
|
||||||
id := uuid.NewRandom()
|
id, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Could not create random uuid: %v", err))
|
||||||
|
}
|
||||||
key := &Key{
|
key := &Key{
|
||||||
Id: id,
|
Id: id,
|
||||||
Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey),
|
Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey),
|
||||||
|
|
@ -169,7 +175,10 @@ func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Accou
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, accounts.Account{}, err
|
return nil, accounts.Account{}, err
|
||||||
}
|
}
|
||||||
a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}}
|
a := accounts.Account{
|
||||||
|
Address: key.Address,
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))},
|
||||||
|
}
|
||||||
if err := ks.StoreKey(a.URL.Path, key, auth); err != nil {
|
if err := ks.StoreKey(a.URL.Path, key, auth); err != nil {
|
||||||
zeroKey(key.PrivateKey)
|
zeroKey(key.PrivateKey)
|
||||||
return nil, a, err
|
return nil, a, err
|
||||||
|
|
@ -177,26 +186,34 @@ func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Accou
|
||||||
return key, a, err
|
return key, a, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeKeyFile(file string, content []byte) error {
|
func writeTemporaryKeyFile(file string, content []byte) (string, error) {
|
||||||
// Create the keystore directory with appropriate permissions
|
// Create the keystore directory with appropriate permissions
|
||||||
// in case it is not present yet.
|
// in case it is not present yet.
|
||||||
const dirPerm = 0700
|
const dirPerm = 0700
|
||||||
if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil {
|
if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
// Atomic write: create a temporary hidden file first
|
// Atomic write: create a temporary hidden file first
|
||||||
// then move it into place. TempFile assigns mode 0600.
|
// then move it into place. TempFile assigns mode 0600.
|
||||||
f, err := os.CreateTemp(filepath.Dir(file), "."+filepath.Base(file)+".tmp")
|
f, err := os.CreateTemp(filepath.Dir(file), "."+filepath.Base(file)+".tmp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
if _, err := f.Write(content); err != nil {
|
if _, err := f.Write(content); err != nil {
|
||||||
f.Close()
|
f.Close()
|
||||||
os.Remove(f.Name())
|
os.Remove(f.Name())
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
f.Close()
|
f.Close()
|
||||||
return os.Rename(f.Name(), file)
|
return f.Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeKeyFile(file string, content []byte) error {
|
||||||
|
name, err := writeTemporaryKeyFile(file, content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Rename(name, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// keyFileName implements the naming convention for keyfiles:
|
// keyFileName implements the naming convention for keyfiles:
|
||||||
|
|
@ -215,5 +232,6 @@ func toISO8601(t time.Time) string {
|
||||||
} else {
|
} else {
|
||||||
tz = fmt.Sprintf("%03d00", offset/3600)
|
tz = fmt.Sprintf("%03d00", offset/3600)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz)
|
return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s",
|
||||||
|
t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,14 +42,18 @@ import (
|
||||||
var (
|
var (
|
||||||
ErrLocked = accounts.NewAuthNeededError("password or unlock")
|
ErrLocked = accounts.NewAuthNeededError("password or unlock")
|
||||||
ErrNoMatch = errors.New("no key for given address or file")
|
ErrNoMatch = errors.New("no key for given address or file")
|
||||||
ErrDecrypt = errors.New("could not decrypt key with given passphrase")
|
ErrDecrypt = errors.New("could not decrypt key with given password")
|
||||||
|
|
||||||
|
// ErrAccountAlreadyExists is returned if an account attempted to import is
|
||||||
|
// already present in the keystore.
|
||||||
|
ErrAccountAlreadyExists = errors.New("account already exists")
|
||||||
)
|
)
|
||||||
|
|
||||||
// KeyStoreType is the reflect type of a keystore backend.
|
// KeyStoreType is the reflect type of a keystore backend.
|
||||||
var KeyStoreType = reflect.TypeOf(&KeyStore{})
|
var KeyStoreType = reflect.TypeOf(&KeyStore{})
|
||||||
|
|
||||||
// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
|
// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
|
||||||
var KeyStoreScheme = "keystore"
|
const KeyStoreScheme = "keystore"
|
||||||
|
|
||||||
// Maximum time between wallet refreshes (if filesystem notifications don't work).
|
// Maximum time between wallet refreshes (if filesystem notifications don't work).
|
||||||
const walletRefreshCycle = 3 * time.Second
|
const walletRefreshCycle = 3 * time.Second
|
||||||
|
|
@ -66,7 +70,8 @@ type KeyStore struct {
|
||||||
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
|
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
|
||||||
updating bool // Whether the event notification loop is running
|
updating bool // Whether the event notification loop is running
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
importMu sync.Mutex // Import Mutex locks the import to prevent two insertions from racing
|
||||||
}
|
}
|
||||||
|
|
||||||
type unlocked struct {
|
type unlocked struct {
|
||||||
|
|
@ -77,16 +82,7 @@ type unlocked struct {
|
||||||
// NewKeyStore creates a keystore for the given directory.
|
// NewKeyStore creates a keystore for the given directory.
|
||||||
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
|
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
|
||||||
keydir, _ = filepath.Abs(keydir)
|
keydir, _ = filepath.Abs(keydir)
|
||||||
ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP}}
|
ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}}
|
||||||
ks.init(keydir)
|
|
||||||
return ks
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPlaintextKeyStore creates a keystore for the given directory.
|
|
||||||
// Deprecated: Use NewKeyStore.
|
|
||||||
func NewPlaintextKeyStore(keydir string) *KeyStore {
|
|
||||||
keydir, _ = filepath.Abs(keydir)
|
|
||||||
ks := &KeyStore{storage: &keyStorePlain{keydir}}
|
|
||||||
ks.init(keydir)
|
ks.init(keydir)
|
||||||
return ks
|
return ks
|
||||||
}
|
}
|
||||||
|
|
@ -136,8 +132,10 @@ func (ks *KeyStore) refreshWallets() {
|
||||||
accs := ks.cache.accounts()
|
accs := ks.cache.accounts()
|
||||||
|
|
||||||
// Transform the current list of wallets into the new one
|
// Transform the current list of wallets into the new one
|
||||||
wallets := make([]accounts.Wallet, 0, len(accs))
|
var (
|
||||||
events := []accounts.WalletEvent{}
|
wallets = make([]accounts.Wallet, 0, len(accs))
|
||||||
|
events []accounts.WalletEvent
|
||||||
|
)
|
||||||
|
|
||||||
for _, account := range accs {
|
for _, account := range accs {
|
||||||
// Drop wallets while they were in front of the next account
|
// Drop wallets while they were in front of the next account
|
||||||
|
|
@ -312,7 +310,6 @@ func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer zeroKey(key.PrivateKey)
|
defer zeroKey(key.PrivateKey)
|
||||||
|
|
||||||
// Depending on the presence of the chain ID, sign with or without replay protection.
|
// Depending on the presence of the chain ID, sign with or without replay protection.
|
||||||
signer := types.LatestSignerForChainID(chainID)
|
signer := types.LatestSignerForChainID(chainID)
|
||||||
return types.SignTx(tx, signer, key.PrivateKey)
|
return types.SignTx(tx, signer, key.PrivateKey)
|
||||||
|
|
@ -326,11 +323,10 @@ func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error {
|
||||||
// Lock removes the private key with the given address from memory.
|
// Lock removes the private key with the given address from memory.
|
||||||
func (ks *KeyStore) Lock(addr common.Address) error {
|
func (ks *KeyStore) Lock(addr common.Address) error {
|
||||||
ks.mu.Lock()
|
ks.mu.Lock()
|
||||||
if unl, found := ks.unlocked[addr]; found {
|
unl, found := ks.unlocked[addr]
|
||||||
ks.mu.Unlock()
|
ks.mu.Unlock()
|
||||||
|
if found {
|
||||||
ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
|
ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
|
||||||
} else {
|
|
||||||
ks.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -447,14 +443,27 @@ func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (ac
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return accounts.Account{}, err
|
return accounts.Account{}, err
|
||||||
}
|
}
|
||||||
|
ks.importMu.Lock()
|
||||||
|
defer ks.importMu.Unlock()
|
||||||
|
|
||||||
|
if ks.cache.hasAddress(key.Address) {
|
||||||
|
return accounts.Account{
|
||||||
|
Address: key.Address,
|
||||||
|
}, ErrAccountAlreadyExists
|
||||||
|
}
|
||||||
return ks.importKey(key, newPassphrase)
|
return ks.importKey(key, newPassphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
|
// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
|
||||||
func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) {
|
func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) {
|
||||||
|
ks.importMu.Lock()
|
||||||
|
defer ks.importMu.Unlock()
|
||||||
|
|
||||||
key := newKeyFromECDSA(priv)
|
key := newKeyFromECDSA(priv)
|
||||||
if ks.cache.hasAddress(key.Address) {
|
if ks.cache.hasAddress(key.Address) {
|
||||||
return accounts.Account{}, errors.New("account already exists")
|
return accounts.Account{
|
||||||
|
Address: key.Address,
|
||||||
|
}, ErrAccountAlreadyExists
|
||||||
}
|
}
|
||||||
return ks.importKey(key, passphrase)
|
return ks.importKey(key, passphrase)
|
||||||
}
|
}
|
||||||
|
|
@ -490,10 +499,16 @@ func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (account
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isUpdating returns whether the event notification loop is running.
|
||||||
|
// This method is mainly meant for tests.
|
||||||
|
func (ks *KeyStore) isUpdating() bool {
|
||||||
|
ks.mu.RLock()
|
||||||
|
defer ks.mu.RUnlock()
|
||||||
|
return ks.updating
|
||||||
|
}
|
||||||
|
|
||||||
// zeroKey zeroes a private key in memory.
|
// zeroKey zeroes a private key in memory.
|
||||||
func zeroKey(k *ecdsa.PrivateKey) {
|
func zeroKey(k *ecdsa.PrivateKey) {
|
||||||
b := k.D.Bits()
|
b := k.D.Bits()
|
||||||
for i := range b {
|
clear(b)
|
||||||
b[i] = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
accounts/keystore/keystore_fuzzing_test.go
Normal file
34
accounts/keystore/keystore_fuzzing_test.go
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package keystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FuzzPassword(f *testing.F) {
|
||||||
|
f.Fuzz(func(t *testing.T, password string) {
|
||||||
|
ks := NewKeyStore(t.TempDir(), LightScryptN, LightScryptP)
|
||||||
|
a, err := ks.NewAccount(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := ks.Unlock(a, password); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -20,21 +20,24 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
"github.com/XinFinOrg/XDPoSChain/event"
|
"github.com/XinFinOrg/XDPoSChain/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testSigData = make([]byte, 32)
|
var testSigData = make([]byte, 32)
|
||||||
|
|
||||||
func TestKeyStore(t *testing.T) {
|
func TestKeyStore(t *testing.T) {
|
||||||
dir, ks := tmpKeyStore(t, true)
|
t.Parallel()
|
||||||
defer os.RemoveAll(dir)
|
dir, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
a, err := ks.NewAccount("foo")
|
a, err := ks.NewAccount("foo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -68,8 +71,8 @@ func TestKeyStore(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSign(t *testing.T) {
|
func TestSign(t *testing.T) {
|
||||||
dir, ks := tmpKeyStore(t, true)
|
t.Parallel()
|
||||||
defer os.RemoveAll(dir)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
pass := "" // not used but required by API
|
pass := "" // not used but required by API
|
||||||
a1, err := ks.NewAccount(pass)
|
a1, err := ks.NewAccount(pass)
|
||||||
|
|
@ -85,8 +88,8 @@ func TestSign(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSignWithPassphrase(t *testing.T) {
|
func TestSignWithPassphrase(t *testing.T) {
|
||||||
dir, ks := tmpKeyStore(t, true)
|
t.Parallel()
|
||||||
defer os.RemoveAll(dir)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
pass := "passwd"
|
pass := "passwd"
|
||||||
acc, err := ks.NewAccount(pass)
|
acc, err := ks.NewAccount(pass)
|
||||||
|
|
@ -113,8 +116,8 @@ func TestSignWithPassphrase(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimedUnlock(t *testing.T) {
|
func TestTimedUnlock(t *testing.T) {
|
||||||
dir, ks := tmpKeyStore(t, true)
|
t.Parallel()
|
||||||
defer os.RemoveAll(dir)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
pass := "foo"
|
pass := "foo"
|
||||||
a1, err := ks.NewAccount(pass)
|
a1, err := ks.NewAccount(pass)
|
||||||
|
|
@ -148,8 +151,8 @@ func TestTimedUnlock(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOverrideUnlock(t *testing.T) {
|
func TestOverrideUnlock(t *testing.T) {
|
||||||
dir, ks := tmpKeyStore(t, false)
|
t.Parallel()
|
||||||
defer os.RemoveAll(dir)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
pass := "foo"
|
pass := "foo"
|
||||||
a1, err := ks.NewAccount(pass)
|
a1, err := ks.NewAccount(pass)
|
||||||
|
|
@ -189,8 +192,8 @@ func TestOverrideUnlock(t *testing.T) {
|
||||||
|
|
||||||
// This test should fail under -race if signing races the expiration goroutine.
|
// This test should fail under -race if signing races the expiration goroutine.
|
||||||
func TestSignRace(t *testing.T) {
|
func TestSignRace(t *testing.T) {
|
||||||
dir, ks := tmpKeyStore(t, false)
|
t.Parallel()
|
||||||
defer os.RemoveAll(dir)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
// Create a test account.
|
// Create a test account.
|
||||||
a1, err := ks.NewAccount("")
|
a1, err := ks.NewAccount("")
|
||||||
|
|
@ -214,20 +217,33 @@ func TestSignRace(t *testing.T) {
|
||||||
t.Errorf("Account did not lock within the timeout")
|
t.Errorf("Account did not lock within the timeout")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// waitForKsUpdating waits until the updating-status of the ks reaches the
|
||||||
|
// desired wantStatus.
|
||||||
|
// It waits for a maximum time of maxTime, and returns false if it does not
|
||||||
|
// finish in time
|
||||||
|
func waitForKsUpdating(t *testing.T, ks *KeyStore, wantStatus bool, maxTime time.Duration) bool {
|
||||||
|
t.Helper()
|
||||||
|
// Wait max 250 ms, then return false
|
||||||
|
for t0 := time.Now(); time.Since(t0) < maxTime; {
|
||||||
|
if ks.isUpdating() == wantStatus {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
time.Sleep(25 * time.Millisecond)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Tests that the wallet notifier loop starts and stops correctly based on the
|
// Tests that the wallet notifier loop starts and stops correctly based on the
|
||||||
// addition and removal of wallet event subscriptions.
|
// addition and removal of wallet event subscriptions.
|
||||||
func TestWalletNotifierLifecycle(t *testing.T) {
|
func TestWalletNotifierLifecycle(t *testing.T) {
|
||||||
// Create a temporary kesytore to test with
|
t.Parallel()
|
||||||
dir, ks := tmpKeyStore(t, false)
|
// Create a temporary keystore to test with
|
||||||
defer os.RemoveAll(dir)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
// Ensure that the notification updater is not running yet
|
// Ensure that the notification updater is not running yet
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(250 * time.Millisecond)
|
||||||
ks.mu.RLock()
|
|
||||||
updating := ks.updating
|
|
||||||
ks.mu.RUnlock()
|
|
||||||
|
|
||||||
if updating {
|
if ks.isUpdating() {
|
||||||
t.Errorf("wallet notifier running without subscribers")
|
t.Errorf("wallet notifier running without subscribers")
|
||||||
}
|
}
|
||||||
// Subscribe to the wallet feed and ensure the updater boots up
|
// Subscribe to the wallet feed and ensure the updater boots up
|
||||||
|
|
@ -237,38 +253,26 @@ func TestWalletNotifierLifecycle(t *testing.T) {
|
||||||
for i := 0; i < len(subs); i++ {
|
for i := 0; i < len(subs); i++ {
|
||||||
// Create a new subscription
|
// Create a new subscription
|
||||||
subs[i] = ks.Subscribe(updates)
|
subs[i] = ks.Subscribe(updates)
|
||||||
|
if !waitForKsUpdating(t, ks, true, 250*time.Millisecond) {
|
||||||
// Ensure the notifier comes online
|
|
||||||
time.Sleep(250 * time.Millisecond)
|
|
||||||
ks.mu.RLock()
|
|
||||||
updating = ks.updating
|
|
||||||
ks.mu.RUnlock()
|
|
||||||
|
|
||||||
if !updating {
|
|
||||||
t.Errorf("sub %d: wallet notifier not running after subscription", i)
|
t.Errorf("sub %d: wallet notifier not running after subscription", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Unsubscribe and ensure the updater terminates eventually
|
// Close all but one sub
|
||||||
for i := 0; i < len(subs); i++ {
|
for i := 0; i < len(subs)-1; i++ {
|
||||||
// Close an existing subscription
|
// Close an existing subscription
|
||||||
subs[i].Unsubscribe()
|
subs[i].Unsubscribe()
|
||||||
|
|
||||||
// Ensure the notifier shuts down at and only at the last close
|
|
||||||
for k := 0; k < int(walletRefreshCycle/(250*time.Millisecond))+2; k++ {
|
|
||||||
ks.mu.RLock()
|
|
||||||
updating = ks.updating
|
|
||||||
ks.mu.RUnlock()
|
|
||||||
|
|
||||||
if i < len(subs)-1 && !updating {
|
|
||||||
t.Fatalf("sub %d: event notifier stopped prematurely", i)
|
|
||||||
}
|
|
||||||
if i == len(subs)-1 && !updating {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
time.Sleep(250 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
t.Errorf("wallet notifier didn't terminate after unsubscribe")
|
// Check that it is still running
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
|
||||||
|
if !ks.isUpdating() {
|
||||||
|
t.Fatal("event notifier stopped prematurely")
|
||||||
|
}
|
||||||
|
// Unsubscribe the last one and ensure the updater terminates eventually.
|
||||||
|
subs[len(subs)-1].Unsubscribe()
|
||||||
|
if !waitForKsUpdating(t, ks, false, 4*time.Second) {
|
||||||
|
t.Errorf("wallet notifier didn't terminate after unsubscribe")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type walletEvent struct {
|
type walletEvent struct {
|
||||||
|
|
@ -279,8 +283,8 @@ type walletEvent struct {
|
||||||
// Tests that wallet notifications and correctly fired when accounts are added
|
// Tests that wallet notifications and correctly fired when accounts are added
|
||||||
// or deleted from the keystore.
|
// or deleted from the keystore.
|
||||||
func TestWalletNotifications(t *testing.T) {
|
func TestWalletNotifications(t *testing.T) {
|
||||||
dir, ks := tmpKeyStore(t, false)
|
t.Parallel()
|
||||||
defer os.RemoveAll(dir)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
// Subscribe to the wallet feed and collect events.
|
// Subscribe to the wallet feed and collect events.
|
||||||
var (
|
var (
|
||||||
|
|
@ -332,11 +336,91 @@ func TestWalletNotifications(t *testing.T) {
|
||||||
|
|
||||||
// Shut down the event collector and check events.
|
// Shut down the event collector and check events.
|
||||||
sub.Unsubscribe()
|
sub.Unsubscribe()
|
||||||
<-updates
|
for ev := range updates {
|
||||||
|
events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]})
|
||||||
|
}
|
||||||
checkAccounts(t, live, ks.Wallets())
|
checkAccounts(t, live, ks.Wallets())
|
||||||
checkEvents(t, wantEvents, events)
|
checkEvents(t, wantEvents, events)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestImportECDSA tests the import functionality of a keystore.
|
||||||
|
func TestImportECDSA(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_, ks := tmpKeyStore(t)
|
||||||
|
key, err := crypto.GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to generate key: %v", key)
|
||||||
|
}
|
||||||
|
if _, err = ks.ImportECDSA(key, "old"); err != nil {
|
||||||
|
t.Errorf("importing failed: %v", err)
|
||||||
|
}
|
||||||
|
if _, err = ks.ImportECDSA(key, "old"); err == nil {
|
||||||
|
t.Errorf("importing same key twice succeeded")
|
||||||
|
}
|
||||||
|
if _, err = ks.ImportECDSA(key, "new"); err == nil {
|
||||||
|
t.Errorf("importing same key twice succeeded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestImportExport tests the import and export functionality of a keystore.
|
||||||
|
func TestImportExport(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_, ks := tmpKeyStore(t)
|
||||||
|
acc, err := ks.NewAccount("old")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create account: %v", acc)
|
||||||
|
}
|
||||||
|
json, err := ks.Export(acc, "old", "new")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to export account: %v", acc)
|
||||||
|
}
|
||||||
|
_, ks2 := tmpKeyStore(t)
|
||||||
|
if _, err = ks2.Import(json, "old", "old"); err == nil {
|
||||||
|
t.Errorf("importing with invalid password succeeded")
|
||||||
|
}
|
||||||
|
acc2, err := ks2.Import(json, "new", "new")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("importing failed: %v", err)
|
||||||
|
}
|
||||||
|
if acc.Address != acc2.Address {
|
||||||
|
t.Error("imported account does not match exported account")
|
||||||
|
}
|
||||||
|
if _, err = ks2.Import(json, "new", "new"); err == nil {
|
||||||
|
t.Errorf("importing a key twice succeeded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestImportRace tests the keystore on races.
|
||||||
|
// This test should fail under -race if importing races.
|
||||||
|
func TestImportRace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_, ks := tmpKeyStore(t)
|
||||||
|
acc, err := ks.NewAccount("old")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create account: %v", acc)
|
||||||
|
}
|
||||||
|
json, err := ks.Export(acc, "old", "new")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to export account: %v", acc)
|
||||||
|
}
|
||||||
|
_, ks2 := tmpKeyStore(t)
|
||||||
|
var atom atomic.Uint32
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if _, err := ks2.Import(json, "new", "new"); err != nil {
|
||||||
|
atom.Add(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
if atom.Load() != 1 {
|
||||||
|
t.Errorf("Import is racy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// checkAccounts checks that all known live accounts are present in the wallet list.
|
// checkAccounts checks that all known live accounts are present in the wallet list.
|
||||||
func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, wallets []accounts.Wallet) {
|
func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, wallets []accounts.Wallet) {
|
||||||
if len(live) != len(wallets) {
|
if len(live) != len(wallets) {
|
||||||
|
|
@ -347,7 +431,7 @@ func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, walle
|
||||||
for _, account := range live {
|
for _, account := range live {
|
||||||
liveList = append(liveList, account)
|
liveList = append(liveList, account)
|
||||||
}
|
}
|
||||||
sort.Sort(accountsByURL(liveList))
|
slices.SortFunc(liveList, byURL)
|
||||||
for j, wallet := range wallets {
|
for j, wallet := range wallets {
|
||||||
if accs := wallet.Accounts(); len(accs) != 1 {
|
if accs := wallet.Accounts(); len(accs) != 1 {
|
||||||
t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))
|
t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))
|
||||||
|
|
@ -373,14 +457,7 @@ func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) {
|
func tmpKeyStore(t *testing.T) (string, *KeyStore) {
|
||||||
d, err := os.MkdirTemp("", "eth-keystore-test")
|
d := t.TempDir()
|
||||||
if err != nil {
|
return d, NewKeyStore(d, veryLightScryptN, veryLightScryptP)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
new := NewPlaintextKeyStore
|
|
||||||
if encrypted {
|
|
||||||
new = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) }
|
|
||||||
}
|
|
||||||
return d, new(d)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,19 +28,20 @@ package keystore
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
crand "crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common/math"
|
"github.com/XinFinOrg/XDPoSChain/common/math"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto/randentropy"
|
"github.com/google/uuid"
|
||||||
"github.com/pborman/uuid"
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
"golang.org/x/crypto/scrypt"
|
"golang.org/x/crypto/scrypt"
|
||||||
)
|
)
|
||||||
|
|
@ -72,6 +73,10 @@ type keyStorePassphrase struct {
|
||||||
keysDirPath string
|
keysDirPath string
|
||||||
scryptN int
|
scryptN int
|
||||||
scryptP int
|
scryptP int
|
||||||
|
// skipKeyFileVerification disables the security-feature which does
|
||||||
|
// reads and decrypts any newly created keyfiles. This should be 'false' in all
|
||||||
|
// cases except tests -- setting this to 'true' is not recommended.
|
||||||
|
skipKeyFileVerification bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) {
|
func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) {
|
||||||
|
|
@ -92,9 +97,9 @@ func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StoreKey generates a key, encrypts with 'auth' and stores in the given directory
|
// StoreKey generates a key, encrypts with 'auth' and stores in the given directory
|
||||||
func StoreKey(dir, auth string, scryptN, scryptP int) (common.Address, error) {
|
func StoreKey(dir, auth string, scryptN, scryptP int) (accounts.Account, error) {
|
||||||
_, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP}, crand.Reader, auth)
|
_, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP, false}, rand.Reader, auth)
|
||||||
return a.Address, err
|
return a, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error {
|
func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error {
|
||||||
|
|
@ -102,33 +107,54 @@ func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return writeKeyFile(filename, keyjson)
|
// Write into temporary file
|
||||||
|
tmpName, err := writeTemporaryKeyFile(filename, keyjson)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ks.skipKeyFileVerification {
|
||||||
|
// Verify that we can decrypt the file with the given password.
|
||||||
|
_, err = ks.GetKey(key.Address, tmpName, auth)
|
||||||
|
if err != nil {
|
||||||
|
msg := "an error was encountered when saving and verifying the keystore file. \n" +
|
||||||
|
"This indicates that the keystore is corrupted. \n" +
|
||||||
|
"The corrupted file is stored at \n%v\n" +
|
||||||
|
"Please file a ticket at:\n\n" +
|
||||||
|
"https://github.com/ethereum/go-ethereum/issues." +
|
||||||
|
"The error was : %s"
|
||||||
|
//lint:ignore ST1005 This is a message for the user
|
||||||
|
return fmt.Errorf(msg, tmpName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return os.Rename(tmpName, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePassphrase) JoinPath(filename string) string {
|
func (ks keyStorePassphrase) JoinPath(filename string) string {
|
||||||
if filepath.IsAbs(filename) {
|
if filepath.IsAbs(filename) {
|
||||||
return filename
|
return filename
|
||||||
} else {
|
|
||||||
return filepath.Join(ks.keysDirPath, filename)
|
|
||||||
}
|
}
|
||||||
|
return filepath.Join(ks.keysDirPath, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptKey encrypts a key using the specified scrypt parameters into a json
|
// EncryptDataV3 encrypts the data given as 'data' with the password 'auth'.
|
||||||
// blob that can be decrypted later on.
|
func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
|
||||||
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
salt := make([]byte, 32)
|
||||||
authArray := []byte(auth)
|
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||||
salt := randentropy.GetEntropyCSPRNG(32)
|
panic("reading from crypto/rand failed: " + err.Error())
|
||||||
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)
|
}
|
||||||
|
derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return CryptoJSON{}, err
|
||||||
}
|
}
|
||||||
encryptKey := derivedKey[:16]
|
encryptKey := derivedKey[:16]
|
||||||
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
|
|
||||||
|
|
||||||
iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16
|
iv := make([]byte, aes.BlockSize) // 16
|
||||||
cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
panic("reading from crypto/rand failed: " + err.Error())
|
||||||
|
}
|
||||||
|
cipherText, err := aesCTRXOR(encryptKey, data, iv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return CryptoJSON{}, err
|
||||||
}
|
}
|
||||||
mac := crypto.Keccak256(derivedKey[16:32], cipherText)
|
mac := crypto.Keccak256(derivedKey[16:32], cipherText)
|
||||||
|
|
||||||
|
|
@ -138,12 +164,11 @@ func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
||||||
scryptParamsJSON["p"] = scryptP
|
scryptParamsJSON["p"] = scryptP
|
||||||
scryptParamsJSON["dklen"] = scryptDKLen
|
scryptParamsJSON["dklen"] = scryptDKLen
|
||||||
scryptParamsJSON["salt"] = hex.EncodeToString(salt)
|
scryptParamsJSON["salt"] = hex.EncodeToString(salt)
|
||||||
|
|
||||||
cipherParamsJSON := cipherparamsJSON{
|
cipherParamsJSON := cipherparamsJSON{
|
||||||
IV: hex.EncodeToString(iv),
|
IV: hex.EncodeToString(iv),
|
||||||
}
|
}
|
||||||
|
|
||||||
cryptoStruct := cryptoJSON{
|
cryptoStruct := CryptoJSON{
|
||||||
Cipher: "aes-128-ctr",
|
Cipher: "aes-128-ctr",
|
||||||
CipherText: hex.EncodeToString(cipherText),
|
CipherText: hex.EncodeToString(cipherText),
|
||||||
CipherParams: cipherParamsJSON,
|
CipherParams: cipherParamsJSON,
|
||||||
|
|
@ -151,9 +176,19 @@ func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
||||||
KDFParams: scryptParamsJSON,
|
KDFParams: scryptParamsJSON,
|
||||||
MAC: hex.EncodeToString(mac),
|
MAC: hex.EncodeToString(mac),
|
||||||
}
|
}
|
||||||
|
return cryptoStruct, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptKey encrypts a key using the specified scrypt parameters into a json
|
||||||
|
// blob that can be decrypted later on.
|
||||||
|
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
||||||
|
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
|
||||||
|
cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
encryptedKeyJSONV3 := encryptedKeyJSONV3{
|
encryptedKeyJSONV3 := encryptedKeyJSONV3{
|
||||||
// hex.EncodeToString(key.Address[:]),
|
hex.EncodeToString(key.Address[:]),
|
||||||
key.Address.String(),
|
|
||||||
cryptoStruct,
|
cryptoStruct,
|
||||||
key.Id.String(),
|
key.Id.String(),
|
||||||
version,
|
version,
|
||||||
|
|
@ -190,51 +225,67 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
key := crypto.ToECDSAUnsafe(keyBytes)
|
key, err := crypto.ToECDSA(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid key: %w", err)
|
||||||
|
}
|
||||||
|
id, err := uuid.FromBytes(keyId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid UUID: %w", err)
|
||||||
|
}
|
||||||
return &Key{
|
return &Key{
|
||||||
Id: uuid.UUID(keyId),
|
Id: id,
|
||||||
Address: crypto.PubkeyToAddress(key.PublicKey),
|
Address: crypto.PubkeyToAddress(key.PublicKey),
|
||||||
PrivateKey: key,
|
PrivateKey: key,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
|
func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
|
||||||
if keyProtected.Version != version {
|
if cryptoJson.Cipher != "aes-128-ctr" {
|
||||||
return nil, nil, fmt.Errorf("not supported Version: %v", keyProtected.Version)
|
return nil, fmt.Errorf("cipher not supported: %v", cryptoJson.Cipher)
|
||||||
}
|
}
|
||||||
|
mac, err := hex.DecodeString(cryptoJson.MAC)
|
||||||
if keyProtected.Crypto.Cipher != "aes-128-ctr" {
|
|
||||||
return nil, nil, fmt.Errorf("not supported Cipher: %v", keyProtected.Crypto.Cipher)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyId = uuid.Parse(keyProtected.Id)
|
|
||||||
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
|
iv, err := hex.DecodeString(cryptoJson.CipherParams.IV)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
|
cipherText, err := hex.DecodeString(cryptoJson.CipherText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
|
derivedKey, err := getKDFKey(cryptoJson, auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
||||||
if !bytes.Equal(calculatedMAC, mac) {
|
if !bytes.Equal(calculatedMAC, mac) {
|
||||||
return nil, nil, ErrDecrypt
|
return nil, ErrDecrypt
|
||||||
}
|
}
|
||||||
|
|
||||||
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
|
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return plainText, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
|
||||||
|
if keyProtected.Version != version {
|
||||||
|
return nil, nil, fmt.Errorf("version not supported: %v", keyProtected.Version)
|
||||||
|
}
|
||||||
|
keyUUID, err := uuid.Parse(keyProtected.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
keyId = keyUUID[:]
|
||||||
|
plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +293,11 @@ func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byt
|
||||||
}
|
}
|
||||||
|
|
||||||
func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) {
|
func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) {
|
||||||
keyId = uuid.Parse(keyProtected.Id)
|
keyUUID, err := uuid.Parse(keyProtected.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
keyId = keyUUID[:]
|
||||||
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
|
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|
@ -275,7 +330,7 @@ func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byt
|
||||||
return plainText, keyId, err
|
return plainText, keyId, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKDFKey(cryptoJSON cryptoJSON, auth string) ([]byte, error) {
|
func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
|
||||||
authArray := []byte(auth)
|
authArray := []byte(auth)
|
||||||
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
|
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -288,7 +343,6 @@ func getKDFKey(cryptoJSON cryptoJSON, auth string) ([]byte, error) {
|
||||||
r := ensureInt(cryptoJSON.KDFParams["r"])
|
r := ensureInt(cryptoJSON.KDFParams["r"])
|
||||||
p := ensureInt(cryptoJSON.KDFParams["p"])
|
p := ensureInt(cryptoJSON.KDFParams["p"])
|
||||||
return scrypt.Key(authArray, salt, n, r, p, dkLen)
|
return scrypt.Key(authArray, salt, n, r, p, dkLen)
|
||||||
|
|
||||||
} else if cryptoJSON.KDF == "pbkdf2" {
|
} else if cryptoJSON.KDF == "pbkdf2" {
|
||||||
c := ensureInt(cryptoJSON.KDFParams["c"])
|
c := ensureInt(cryptoJSON.KDFParams["c"])
|
||||||
prf := cryptoJSON.KDFParams["prf"].(string)
|
prf := cryptoJSON.KDFParams["prf"].(string)
|
||||||
|
|
@ -30,6 +30,7 @@ const (
|
||||||
|
|
||||||
// Tests that a json key file can be decrypted and encrypted in multiple rounds.
|
// Tests that a json key file can be decrypted and encrypted in multiple rounds.
|
||||||
func TestKeyEncryptDecrypt(t *testing.T) {
|
func TestKeyEncryptDecrypt(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
keyjson, err := os.ReadFile("testdata/very-light-scrypt.json")
|
keyjson, err := os.ReadFile("testdata/very-light-scrypt.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -52,9 +53,9 @@ func TestKeyEncryptDecrypt(t *testing.T) {
|
||||||
t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address)
|
t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address)
|
||||||
}
|
}
|
||||||
// Recrypt with a new password and start over
|
// Recrypt with a new password and start over
|
||||||
password += "new data appended"
|
password += "new data appended" // nolint: gosec
|
||||||
if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil {
|
if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil {
|
||||||
t.Errorf("test %d: failed to recrypt key %v", i, err)
|
t.Errorf("test %d: failed to re-encrypt key %v", i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,7 +56,6 @@ func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error {
|
||||||
func (ks keyStorePlain) JoinPath(filename string) string {
|
func (ks keyStorePlain) JoinPath(filename string) string {
|
||||||
if filepath.IsAbs(filename) {
|
if filepath.IsAbs(filename) {
|
||||||
return filename
|
return filename
|
||||||
} else {
|
|
||||||
return filepath.Join(ks.keysDirPath, filename)
|
|
||||||
}
|
}
|
||||||
|
return filepath.Join(ks.keysDirPath, filename)
|
||||||
}
|
}
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -31,12 +30,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
|
func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
|
||||||
d, err := os.MkdirTemp("", "geth-keystore-test")
|
d := t.TempDir()
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if encrypted {
|
if encrypted {
|
||||||
ks = &keyStorePassphrase{d, veryLightScryptN, veryLightScryptP}
|
ks = &keyStorePassphrase{d, veryLightScryptN, veryLightScryptP, true}
|
||||||
} else {
|
} else {
|
||||||
ks = &keyStorePlain{d}
|
ks = &keyStorePlain{d}
|
||||||
}
|
}
|
||||||
|
|
@ -44,8 +40,8 @@ func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyStorePlain(t *testing.T) {
|
func TestKeyStorePlain(t *testing.T) {
|
||||||
dir, ks := tmpKeyStoreIface(t, false)
|
t.Parallel()
|
||||||
defer os.RemoveAll(dir)
|
_, ks := tmpKeyStoreIface(t, false)
|
||||||
|
|
||||||
pass := "" // not used but required by API
|
pass := "" // not used but required by API
|
||||||
k1, account, err := storeNewKey(ks, rand.Reader, pass)
|
k1, account, err := storeNewKey(ks, rand.Reader, pass)
|
||||||
|
|
@ -65,8 +61,8 @@ func TestKeyStorePlain(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyStorePassphrase(t *testing.T) {
|
func TestKeyStorePassphrase(t *testing.T) {
|
||||||
dir, ks := tmpKeyStoreIface(t, true)
|
t.Parallel()
|
||||||
defer os.RemoveAll(dir)
|
_, ks := tmpKeyStoreIface(t, true)
|
||||||
|
|
||||||
pass := "foo"
|
pass := "foo"
|
||||||
k1, account, err := storeNewKey(ks, rand.Reader, pass)
|
k1, account, err := storeNewKey(ks, rand.Reader, pass)
|
||||||
|
|
@ -86,8 +82,8 @@ func TestKeyStorePassphrase(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
|
func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
|
||||||
dir, ks := tmpKeyStoreIface(t, true)
|
t.Parallel()
|
||||||
defer os.RemoveAll(dir)
|
_, ks := tmpKeyStoreIface(t, true)
|
||||||
|
|
||||||
pass := "foo"
|
pass := "foo"
|
||||||
k1, account, err := storeNewKey(ks, rand.Reader, pass)
|
k1, account, err := storeNewKey(ks, rand.Reader, pass)
|
||||||
|
|
@ -95,13 +91,13 @@ func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if _, err = ks.GetKey(k1.Address, account.URL.Path, "bar"); err != ErrDecrypt {
|
if _, err = ks.GetKey(k1.Address, account.URL.Path, "bar"); err != ErrDecrypt {
|
||||||
t.Fatalf("wrong error for invalid passphrase\ngot %q\nwant %q", err, ErrDecrypt)
|
t.Fatalf("wrong error for invalid password\ngot %q\nwant %q", err, ErrDecrypt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImportPreSaleKey(t *testing.T) {
|
func TestImportPreSaleKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir, ks := tmpKeyStoreIface(t, true)
|
dir, ks := tmpKeyStoreIface(t, true)
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
// file content of a presale key file generated with:
|
// file content of a presale key file generated with:
|
||||||
// python pyethsaletool.py genwallet
|
// python pyethsaletool.py genwallet
|
||||||
|
|
@ -190,7 +186,7 @@ func TestV1_1(t *testing.T) {
|
||||||
|
|
||||||
func TestV1_2(t *testing.T) {
|
func TestV1_2(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ks := &keyStorePassphrase{"testdata/v1", LightScryptN, LightScryptP}
|
ks := &keyStorePassphrase{"testdata/v1", LightScryptN, LightScryptP, true}
|
||||||
addr := common.HexToAddress("cb61d5a9c4896fb9658090b597ef0e7be6f7b67e")
|
addr := common.HexToAddress("cb61d5a9c4896fb9658090b597ef0e7be6f7b67e")
|
||||||
file := "testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e"
|
file := "testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e"
|
||||||
k, err := ks.GetKey(addr, file, "g")
|
k, err := ks.GetKey(addr, file, "g")
|
||||||
|
|
@ -27,7 +27,7 @@ import (
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
"github.com/pborman/uuid"
|
"github.com/google/uuid"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -37,8 +37,17 @@ func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accou
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return accounts.Account{}, nil, err
|
return accounts.Account{}, nil, err
|
||||||
}
|
}
|
||||||
key.Id = uuid.NewRandom()
|
key.Id, err = uuid.NewRandom()
|
||||||
a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: keyStore.JoinPath(keyFileName(key.Address))}}
|
if err != nil {
|
||||||
|
return accounts.Account{}, nil, err
|
||||||
|
}
|
||||||
|
a := accounts.Account{
|
||||||
|
Address: key.Address,
|
||||||
|
URL: accounts.URL{
|
||||||
|
Scheme: KeyStoreScheme,
|
||||||
|
Path: keyStore.JoinPath(keyFileName(key.Address)),
|
||||||
|
},
|
||||||
|
}
|
||||||
err = keyStore.StoreKey(a.URL.Path, key, password)
|
err = keyStore.StoreKey(a.URL.Path, key, password)
|
||||||
return a, key, err
|
return a, key, err
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +89,7 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error
|
||||||
ecKey := crypto.ToECDSAUnsafe(ethPriv)
|
ecKey := crypto.ToECDSAUnsafe(ethPriv)
|
||||||
|
|
||||||
key = &Key{
|
key = &Key{
|
||||||
Id: nil,
|
Id: uuid.UUID{},
|
||||||
Address: crypto.PubkeyToAddress(ecKey.PublicKey),
|
Address: crypto.PubkeyToAddress(ecKey.PublicKey),
|
||||||
PrivateKey: ecKey,
|
PrivateKey: ecKey,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
accounts/keystore/testdata/keystore/README
vendored
2
accounts/keystore/testdata/keystore/README
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
This directory contains accounts for testing.
|
This directory contains accounts for testing.
|
||||||
The passphrase that unlocks them is "foobar".
|
The password that unlocks them is "foobar".
|
||||||
|
|
||||||
The "good" key files which are supposed to be loadable are:
|
The "good" key files which are supposed to be loadable are:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
ethereum "github.com/XinFinOrg/XDPoSChain"
|
ethereum "github.com/XinFinOrg/XDPoSChain"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// keystoreWallet implements the accounts.Wallet interface for the original
|
// keystoreWallet implements the accounts.Wallet interface for the original
|
||||||
|
|
@ -52,12 +53,12 @@ func (w *keystoreWallet) Status() (string, error) {
|
||||||
// is no connection or decryption step necessary to access the list of accounts.
|
// is no connection or decryption step necessary to access the list of accounts.
|
||||||
func (w *keystoreWallet) Open(passphrase string) error { return nil }
|
func (w *keystoreWallet) Open(passphrase string) error { return nil }
|
||||||
|
|
||||||
// Close implements accounts.Wallet, but is a noop for plain wallets since is no
|
// Close implements accounts.Wallet, but is a noop for plain wallets since there
|
||||||
// meaningful open operation.
|
// is no meaningful open operation.
|
||||||
func (w *keystoreWallet) Close() error { return nil }
|
func (w *keystoreWallet) Close() error { return nil }
|
||||||
|
|
||||||
// Accounts implements accounts.Wallet, returning an account list consisting of
|
// Accounts implements accounts.Wallet, returning an account list consisting of
|
||||||
// a single account that the plain kestore wallet contains.
|
// a single account that the plain keystore wallet contains.
|
||||||
func (w *keystoreWallet) Accounts() []accounts.Account {
|
func (w *keystoreWallet) Accounts() []accounts.Account {
|
||||||
return []accounts.Account{w.account}
|
return []accounts.Account{w.account}
|
||||||
}
|
}
|
||||||
|
|
@ -76,62 +77,72 @@ func (w *keystoreWallet) Derive(path accounts.DerivationPath, pin bool) (account
|
||||||
|
|
||||||
// SelfDerive implements accounts.Wallet, but is a noop for plain wallets since
|
// SelfDerive implements accounts.Wallet, but is a noop for plain wallets since
|
||||||
// there is no notion of hierarchical account derivation for plain keystore accounts.
|
// there is no notion of hierarchical account derivation for plain keystore accounts.
|
||||||
func (w *keystoreWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {}
|
func (w *keystoreWallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
||||||
|
}
|
||||||
|
|
||||||
// SignHash implements accounts.Wallet, attempting to sign the given hash with
|
// SignHash attempts to sign the given hash with
|
||||||
// the given account. If the wallet does not wrap this particular account, an
|
// the given account. If the wallet does not wrap this particular account, an
|
||||||
// error is returned to avoid account leakage (even though in theory we may be
|
// error is returned to avoid account leakage (even though in theory we may be
|
||||||
// able to sign via our shared keystore backend).
|
// able to sign via our shared keystore backend).
|
||||||
func (w *keystoreWallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
|
func (w *keystoreWallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
|
||||||
// Make sure the requested account is contained within
|
// Make sure the requested account is contained within
|
||||||
if account.Address != w.account.Address {
|
if !w.Contains(account) {
|
||||||
return nil, accounts.ErrUnknownAccount
|
|
||||||
}
|
|
||||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
|
|
||||||
return nil, accounts.ErrUnknownAccount
|
return nil, accounts.ErrUnknownAccount
|
||||||
}
|
}
|
||||||
// Account seems valid, request the keystore to sign
|
// Account seems valid, request the keystore to sign
|
||||||
return w.keystore.SignHash(account, hash)
|
return w.keystore.SignHash(account, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed.
|
||||||
|
func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
return w.SignHash(account, crypto.Keccak256(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed.
|
||||||
|
func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
// Make sure the requested account is contained within
|
||||||
|
if !w.Contains(account) {
|
||||||
|
return nil, accounts.ErrUnknownAccount
|
||||||
|
}
|
||||||
|
// Account seems valid, request the keystore to sign
|
||||||
|
return w.keystore.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignText implements accounts.Wallet, attempting to sign the hash of
|
||||||
|
// the given text with the given account.
|
||||||
|
func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
|
||||||
|
return w.SignHash(account, accounts.TextHash(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTextWithPassphrase implements accounts.Wallet, attempting to sign the
|
||||||
|
// hash of the given text with the given account using passphrase as extra authentication.
|
||||||
|
func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||||
|
// Make sure the requested account is contained within
|
||||||
|
if !w.Contains(account) {
|
||||||
|
return nil, accounts.ErrUnknownAccount
|
||||||
|
}
|
||||||
|
// Account seems valid, request the keystore to sign
|
||||||
|
return w.keystore.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text))
|
||||||
|
}
|
||||||
|
|
||||||
// SignTx implements accounts.Wallet, attempting to sign the given transaction
|
// SignTx implements accounts.Wallet, attempting to sign the given transaction
|
||||||
// with the given account. If the wallet does not wrap this particular account,
|
// with the given account. If the wallet does not wrap this particular account,
|
||||||
// an error is returned to avoid account leakage (even though in theory we may
|
// an error is returned to avoid account leakage (even though in theory we may
|
||||||
// be able to sign via our shared keystore backend).
|
// be able to sign via our shared keystore backend).
|
||||||
func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
// Make sure the requested account is contained within
|
// Make sure the requested account is contained within
|
||||||
if account.Address != w.account.Address {
|
if !w.Contains(account) {
|
||||||
return nil, accounts.ErrUnknownAccount
|
|
||||||
}
|
|
||||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
|
|
||||||
return nil, accounts.ErrUnknownAccount
|
return nil, accounts.ErrUnknownAccount
|
||||||
}
|
}
|
||||||
// Account seems valid, request the keystore to sign
|
// Account seems valid, request the keystore to sign
|
||||||
return w.keystore.SignTx(account, tx, chainID)
|
return w.keystore.SignTx(account, tx, chainID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignHashWithPassphrase implements accounts.Wallet, attempting to sign the
|
|
||||||
// given hash with the given account using passphrase as extra authentication.
|
|
||||||
func (w *keystoreWallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) {
|
|
||||||
// Make sure the requested account is contained within
|
|
||||||
if account.Address != w.account.Address {
|
|
||||||
return nil, accounts.ErrUnknownAccount
|
|
||||||
}
|
|
||||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
|
|
||||||
return nil, accounts.ErrUnknownAccount
|
|
||||||
}
|
|
||||||
// Account seems valid, request the keystore to sign
|
|
||||||
return w.keystore.SignHashWithPassphrase(account, passphrase, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||||
// transaction with the given account using passphrase as extra authentication.
|
// transaction with the given account using passphrase as extra authentication.
|
||||||
func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
// Make sure the requested account is contained within
|
// Make sure the requested account is contained within
|
||||||
if account.Address != w.account.Address {
|
if !w.Contains(account) {
|
||||||
return nil, accounts.ErrUnknownAccount
|
|
||||||
}
|
|
||||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
|
|
||||||
return nil, accounts.ErrUnknownAccount
|
return nil, accounts.ErrUnknownAccount
|
||||||
}
|
}
|
||||||
// Account seems valid, request the keystore to sign
|
// Account seems valid, request the keystore to sign
|
||||||
|
|
@ -14,33 +14,37 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// +build darwin,!ios freebsd linux,!arm64 netbsd solaris
|
//go:build (darwin && !ios && cgo) || freebsd || (linux && !arm64) || netbsd || solaris
|
||||||
|
// +build darwin,!ios,cgo freebsd linux,!arm64 netbsd solaris
|
||||||
|
|
||||||
package keystore
|
package keystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/log"
|
"github.com/XinFinOrg/XDPoSChain/log"
|
||||||
"github.com/rjeczalik/notify"
|
"github.com/fsnotify/fsnotify"
|
||||||
)
|
)
|
||||||
|
|
||||||
type watcher struct {
|
type watcher struct {
|
||||||
ac *accountCache
|
ac *accountCache
|
||||||
starting bool
|
running bool // set to true when runloop begins
|
||||||
running bool
|
runEnded bool // set to true when runloop ends
|
||||||
ev chan notify.EventInfo
|
starting bool // set to true prior to runloop starting
|
||||||
quit chan struct{}
|
quit chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWatcher(ac *accountCache) *watcher {
|
func newWatcher(ac *accountCache) *watcher {
|
||||||
return &watcher{
|
return &watcher{
|
||||||
ac: ac,
|
ac: ac,
|
||||||
ev: make(chan notify.EventInfo, 10),
|
|
||||||
quit: make(chan struct{}),
|
quit: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enabled returns false on systems not supported.
|
||||||
|
func (*watcher) enabled() bool { return true }
|
||||||
|
|
||||||
// starts the watcher loop in the background.
|
// starts the watcher loop in the background.
|
||||||
// Start a watcher in the background if that's not already in progress.
|
// Start a watcher in the background if that's not already in progress.
|
||||||
// The caller must hold w.ac.mu.
|
// The caller must hold w.ac.mu.
|
||||||
|
|
@ -61,16 +65,26 @@ func (w *watcher) loop() {
|
||||||
w.ac.mu.Lock()
|
w.ac.mu.Lock()
|
||||||
w.running = false
|
w.running = false
|
||||||
w.starting = false
|
w.starting = false
|
||||||
|
w.runEnded = true
|
||||||
w.ac.mu.Unlock()
|
w.ac.mu.Unlock()
|
||||||
}()
|
}()
|
||||||
logger := log.New("path", w.ac.keydir)
|
logger := log.New("path", w.ac.keydir)
|
||||||
|
|
||||||
if err := notify.Watch(w.ac.keydir, w.ev, notify.All); err != nil {
|
// Create new watcher.
|
||||||
logger.Trace("Failed to watch keystore folder", "err", err)
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to start filesystem watcher", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer notify.Stop(w.ev)
|
defer watcher.Close()
|
||||||
logger.Trace("Started watching keystore folder")
|
if err := watcher.Add(w.ac.keydir); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
logger.Warn("Failed to watch keystore folder", "err", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Trace("Started watching keystore folder", "folder", w.ac.keydir)
|
||||||
defer logger.Trace("Stopped watching keystore folder")
|
defer logger.Trace("Stopped watching keystore folder")
|
||||||
|
|
||||||
w.ac.mu.Lock()
|
w.ac.mu.Lock()
|
||||||
|
|
@ -94,12 +108,24 @@ func (w *watcher) loop() {
|
||||||
select {
|
select {
|
||||||
case <-w.quit:
|
case <-w.quit:
|
||||||
return
|
return
|
||||||
case <-w.ev:
|
case _, ok := <-watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
// Trigger the scan (with delay), if not already triggered
|
// Trigger the scan (with delay), if not already triggered
|
||||||
if !rescanTriggered {
|
if !rescanTriggered {
|
||||||
debounce.Reset(debounceDuration)
|
debounce.Reset(debounceDuration)
|
||||||
rescanTriggered = true
|
rescanTriggered = true
|
||||||
}
|
}
|
||||||
|
// The fsnotify library does provide more granular event-info, it
|
||||||
|
// would be possible to refresh individual affected files instead
|
||||||
|
// of scheduling a full rescan. For most cases though, the
|
||||||
|
// full rescan is quick and obviously simplest.
|
||||||
|
case err, ok := <-watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("Filesystem watcher error", "err", err)
|
||||||
case <-debounce.C:
|
case <-debounce.C:
|
||||||
w.ac.scanAccounts()
|
w.ac.scanAccounts()
|
||||||
rescanTriggered = false
|
rescanTriggered = false
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,22 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// +build ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris
|
//go:build (darwin && !cgo) || ios || (linux && arm64) || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris)
|
||||||
|
// +build darwin,!cgo ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris
|
||||||
|
|
||||||
// This is the fallback implementation of directory watching.
|
// This is the fallback implementation of directory watching.
|
||||||
// It is used on unsupported platforms.
|
// It is used on unsupported platforms.
|
||||||
|
|
||||||
package keystore
|
package keystore
|
||||||
|
|
||||||
type watcher struct{ running bool }
|
type watcher struct {
|
||||||
|
running bool
|
||||||
|
runEnded bool
|
||||||
|
}
|
||||||
|
|
||||||
func newWatcher(*accountCache) *watcher { return new(watcher) }
|
func newWatcher(*accountCache) *watcher { return new(watcher) }
|
||||||
func (*watcher) start() {}
|
func (*watcher) start() {}
|
||||||
func (*watcher) close() {}
|
func (*watcher) close() {}
|
||||||
|
|
||||||
|
// enabled returns false on systems not supported.
|
||||||
|
func (*watcher) enabled() bool { return false }
|
||||||
|
|
|
||||||
|
|
@ -21,33 +21,56 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/event"
|
"github.com/XinFinOrg/XDPoSChain/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// managerSubBufferSize determines how many incoming wallet events
|
||||||
|
// the manager will buffer in its channel.
|
||||||
|
const managerSubBufferSize = 50
|
||||||
|
|
||||||
|
// Config contains the settings of the global account manager.
|
||||||
|
//
|
||||||
|
// TODO(rjl493456442, karalabe, holiman): Get rid of this when account management
|
||||||
|
// is removed in favor of Clef.
|
||||||
|
type Config struct {
|
||||||
|
InsecureUnlockAllowed bool // Whether account unlocking in insecure environment is allowed
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBackendEvent lets the manager know it should
|
||||||
|
// track the given backend for wallet updates.
|
||||||
|
type newBackendEvent struct {
|
||||||
|
backend Backend
|
||||||
|
processed chan struct{} // Informs event emitter that backend has been integrated
|
||||||
|
}
|
||||||
|
|
||||||
// Manager is an overarching account manager that can communicate with various
|
// Manager is an overarching account manager that can communicate with various
|
||||||
// backends for signing transactions.
|
// backends for signing transactions.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
backends map[reflect.Type][]Backend // Index of backends currently registered
|
config *Config // Global account manager configurations
|
||||||
updaters []event.Subscription // Wallet update subscriptions for all backends
|
backends map[reflect.Type][]Backend // Index of backends currently registered
|
||||||
updates chan WalletEvent // Subscription sink for backend wallet changes
|
updaters []event.Subscription // Wallet update subscriptions for all backends
|
||||||
wallets []Wallet // Cache of all wallets from all registered backends
|
updates chan WalletEvent // Subscription sink for backend wallet changes
|
||||||
|
newBackends chan newBackendEvent // Incoming backends to be tracked by the manager
|
||||||
|
wallets []Wallet // Cache of all wallets from all registered backends
|
||||||
|
|
||||||
feed event.Feed // Wallet feed notifying of arrivals/departures
|
feed event.Feed // Wallet feed notifying of arrivals/departures
|
||||||
|
|
||||||
quit chan chan error
|
quit chan chan error
|
||||||
|
term chan struct{} // Channel is closed upon termination of the update loop
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager creates a generic account manager to sign transaction via various
|
// NewManager creates a generic account manager to sign transaction via various
|
||||||
// supported backends.
|
// supported backends.
|
||||||
func NewManager(backends ...Backend) *Manager {
|
func NewManager(config *Config, backends ...Backend) *Manager {
|
||||||
// Retrieve the initial list of wallets from the backends and sort by URL
|
// Retrieve the initial list of wallets from the backends and sort by URL
|
||||||
var wallets []Wallet
|
var wallets []Wallet
|
||||||
for _, backend := range backends {
|
for _, backend := range backends {
|
||||||
wallets = merge(wallets, backend.Wallets()...)
|
wallets = merge(wallets, backend.Wallets()...)
|
||||||
}
|
}
|
||||||
// Subscribe to wallet notifications from all backends
|
// Subscribe to wallet notifications from all backends
|
||||||
updates := make(chan WalletEvent, 4*len(backends))
|
updates := make(chan WalletEvent, managerSubBufferSize)
|
||||||
|
|
||||||
subs := make([]event.Subscription, len(backends))
|
subs := make([]event.Subscription, len(backends))
|
||||||
for i, backend := range backends {
|
for i, backend := range backends {
|
||||||
|
|
@ -55,11 +78,14 @@ func NewManager(backends ...Backend) *Manager {
|
||||||
}
|
}
|
||||||
// Assemble the account manager and return
|
// Assemble the account manager and return
|
||||||
am := &Manager{
|
am := &Manager{
|
||||||
backends: make(map[reflect.Type][]Backend),
|
config: config,
|
||||||
updaters: subs,
|
backends: make(map[reflect.Type][]Backend),
|
||||||
updates: updates,
|
updaters: subs,
|
||||||
wallets: wallets,
|
updates: updates,
|
||||||
quit: make(chan chan error),
|
newBackends: make(chan newBackendEvent),
|
||||||
|
wallets: wallets,
|
||||||
|
quit: make(chan chan error),
|
||||||
|
term: make(chan struct{}),
|
||||||
}
|
}
|
||||||
for _, backend := range backends {
|
for _, backend := range backends {
|
||||||
kind := reflect.TypeOf(backend)
|
kind := reflect.TypeOf(backend)
|
||||||
|
|
@ -72,11 +98,27 @@ func NewManager(backends ...Backend) *Manager {
|
||||||
|
|
||||||
// Close terminates the account manager's internal notification processes.
|
// Close terminates the account manager's internal notification processes.
|
||||||
func (am *Manager) Close() error {
|
func (am *Manager) Close() error {
|
||||||
|
for _, w := range am.wallets {
|
||||||
|
w.Close()
|
||||||
|
}
|
||||||
errc := make(chan error)
|
errc := make(chan error)
|
||||||
am.quit <- errc
|
am.quit <- errc
|
||||||
return <-errc
|
return <-errc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Config returns the configuration of account manager.
|
||||||
|
func (am *Manager) Config() *Config {
|
||||||
|
return am.config
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBackend starts the tracking of an additional backend for wallet updates.
|
||||||
|
// cmd/geth assumes once this func returns the backends have been already integrated.
|
||||||
|
func (am *Manager) AddBackend(backend Backend) {
|
||||||
|
done := make(chan struct{})
|
||||||
|
am.newBackends <- newBackendEvent{backend, done}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
// update is the wallet event loop listening for notifications from the backends
|
// update is the wallet event loop listening for notifications from the backends
|
||||||
// and updating the cache of wallets.
|
// and updating the cache of wallets.
|
||||||
func (am *Manager) update() {
|
func (am *Manager) update() {
|
||||||
|
|
@ -106,10 +148,22 @@ func (am *Manager) update() {
|
||||||
|
|
||||||
// Notify any listeners of the event
|
// Notify any listeners of the event
|
||||||
am.feed.Send(event)
|
am.feed.Send(event)
|
||||||
|
case event := <-am.newBackends:
|
||||||
|
am.lock.Lock()
|
||||||
|
// Update caches
|
||||||
|
backend := event.backend
|
||||||
|
am.wallets = merge(am.wallets, backend.Wallets()...)
|
||||||
|
am.updaters = append(am.updaters, backend.Subscribe(am.updates))
|
||||||
|
kind := reflect.TypeOf(backend)
|
||||||
|
am.backends[kind] = append(am.backends[kind], backend)
|
||||||
|
am.lock.Unlock()
|
||||||
|
close(event.processed)
|
||||||
case errc := <-am.quit:
|
case errc := <-am.quit:
|
||||||
// Manager terminating, return
|
// Manager terminating, return
|
||||||
errc <- nil
|
errc <- nil
|
||||||
|
// Signals event emitters the loop is not receiving values
|
||||||
|
// to prevent them from getting stuck.
|
||||||
|
close(am.term)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,6 +171,9 @@ func (am *Manager) update() {
|
||||||
|
|
||||||
// Backends retrieves the backend(s) with the given type from the account manager.
|
// Backends retrieves the backend(s) with the given type from the account manager.
|
||||||
func (am *Manager) Backends(kind reflect.Type) []Backend {
|
func (am *Manager) Backends(kind reflect.Type) []Backend {
|
||||||
|
am.lock.RLock()
|
||||||
|
defer am.lock.RUnlock()
|
||||||
|
|
||||||
return am.backends[kind]
|
return am.backends[kind]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,6 +182,11 @@ func (am *Manager) Wallets() []Wallet {
|
||||||
am.lock.RLock()
|
am.lock.RLock()
|
||||||
defer am.lock.RUnlock()
|
defer am.lock.RUnlock()
|
||||||
|
|
||||||
|
return am.walletsNoLock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// walletsNoLock returns all registered wallets. Callers must hold am.lock.
|
||||||
|
func (am *Manager) walletsNoLock() []Wallet {
|
||||||
cpy := make([]Wallet, len(am.wallets))
|
cpy := make([]Wallet, len(am.wallets))
|
||||||
copy(cpy, am.wallets)
|
copy(cpy, am.wallets)
|
||||||
return cpy
|
return cpy
|
||||||
|
|
@ -139,7 +201,7 @@ func (am *Manager) Wallet(url string) (Wallet, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, wallet := range am.Wallets() {
|
for _, wallet := range am.walletsNoLock() {
|
||||||
if wallet.URL() == parsed {
|
if wallet.URL() == parsed {
|
||||||
return wallet, nil
|
return wallet, nil
|
||||||
}
|
}
|
||||||
|
|
@ -147,6 +209,20 @@ func (am *Manager) Wallet(url string) (Wallet, error) {
|
||||||
return nil, ErrUnknownWallet
|
return nil, ErrUnknownWallet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accounts returns all account addresses of all wallets within the account manager
|
||||||
|
func (am *Manager) Accounts() []common.Address {
|
||||||
|
am.lock.RLock()
|
||||||
|
defer am.lock.RUnlock()
|
||||||
|
|
||||||
|
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
||||||
|
for _, wallet := range am.wallets {
|
||||||
|
for _, account := range wallet.Accounts() {
|
||||||
|
addresses = append(addresses, account.Address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addresses
|
||||||
|
}
|
||||||
|
|
||||||
// Find attempts to locate the wallet corresponding to a specific account. Since
|
// Find attempts to locate the wallet corresponding to a specific account. Since
|
||||||
// accounts can be dynamically added to and removed from wallets, this method has
|
// accounts can be dynamically added to and removed from wallets, this method has
|
||||||
// a linear runtime in the number of wallets.
|
// a linear runtime in the number of wallets.
|
||||||
|
|
@ -184,7 +260,7 @@ func merge(slice []Wallet, wallets ...Wallet) []Wallet {
|
||||||
return slice
|
return slice
|
||||||
}
|
}
|
||||||
|
|
||||||
// drop is the couterpart of merge, which looks up wallets from within the sorted
|
// drop is the counterpart of merge, which looks up wallets from within the sorted
|
||||||
// cache and removes the ones specified.
|
// cache and removes the ones specified.
|
||||||
func drop(slice []Wallet, wallets ...Wallet) []Wallet {
|
func drop(slice []Wallet, wallets ...Wallet) []Wallet {
|
||||||
for _, wallet := range wallets {
|
for _, wallet := range wallets {
|
||||||
|
|
|
||||||
106
accounts/scwallet/README.md
Normal file
106
accounts/scwallet/README.md
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
# Using the smartcard wallet
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
* A USB smartcard reader
|
||||||
|
* A keycard that supports the status app
|
||||||
|
* PCSCD version 4.3 running on your system **Only version 4.3 is currently supported**
|
||||||
|
|
||||||
|
## Preparing the smartcard
|
||||||
|
|
||||||
|
**WARNING: FOLLOWING THESE INSTRUCTIONS WILL DESTROY THE MASTER KEY ON YOUR CARD. ONLY PROCEED IF NO FUNDS ARE ASSOCIATED WITH THESE ACCOUNTS**
|
||||||
|
|
||||||
|
You can use status' [keycard-cli](https://github.com/status-im/keycard-cli) and you should get _at least_ version 2.1.1 of their [smartcard application](https://github.com/status-im/status-keycard/releases/download/2.2.1/keycard_v2.2.1.cap)
|
||||||
|
|
||||||
|
You also need to make sure that the PCSC daemon is running on your system.
|
||||||
|
|
||||||
|
Then, you can install the application to the card by typing:
|
||||||
|
|
||||||
|
```
|
||||||
|
keycard install -a keycard_v2.2.1.cap && keycard init
|
||||||
|
```
|
||||||
|
|
||||||
|
At the end of this process, you will be provided with a PIN, a PUK and a pairing password. Write them down, you'll need them shortly.
|
||||||
|
|
||||||
|
Start `geth` with the `console` command. You will notice the following warning:
|
||||||
|
|
||||||
|
```
|
||||||
|
WARN [04-09|16:58:38.898] Failed to open wallet url=keycard://044def09 err="smartcard: pairing password needed"
|
||||||
|
```
|
||||||
|
|
||||||
|
Write down the URL (`keycard://044def09` in this example). Then ask `geth` to open the wallet:
|
||||||
|
|
||||||
|
```
|
||||||
|
> personal.openWallet("keycard://044def09", "pairing password")
|
||||||
|
```
|
||||||
|
|
||||||
|
The pairing password has been generated during the card initialization process.
|
||||||
|
|
||||||
|
The process needs to be repeated once more with the PIN:
|
||||||
|
|
||||||
|
```
|
||||||
|
> personal.openWallet("keycard://044def09", "PIN number")
|
||||||
|
```
|
||||||
|
|
||||||
|
If everything goes well, you should see your new account when typing `personal` on the console:
|
||||||
|
|
||||||
|
```
|
||||||
|
> personal
|
||||||
|
WARN [04-09|17:02:07.330] Smartcard wallet account derivation failed url=keycard://044def09 err="Unexpected response status Cla=0x80, Ins=0xd1, Sw=0x6985"
|
||||||
|
{
|
||||||
|
listAccounts: [],
|
||||||
|
listWallets: [{
|
||||||
|
status: "Empty, waiting for initialization",
|
||||||
|
url: "keycard://044def09"
|
||||||
|
}],
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
So the communication with the card is working, but there is no key associated with this wallet. Let's create it:
|
||||||
|
|
||||||
|
```
|
||||||
|
> personal.initializeWallet("keycard://044def09")
|
||||||
|
"tilt ... impact"
|
||||||
|
```
|
||||||
|
|
||||||
|
You should get a list of words, this is your seed so write them down. Your wallet should now be initialized:
|
||||||
|
|
||||||
|
```
|
||||||
|
> personal.listWallets
|
||||||
|
[{
|
||||||
|
accounts: [{
|
||||||
|
address: "0x678b7cd55c61917defb23546a41803c5bfefbc7a",
|
||||||
|
url: "keycard://044d/m/44'/60'/0'/0/0"
|
||||||
|
}],
|
||||||
|
status: "Online",
|
||||||
|
url: "keycard://044def09"
|
||||||
|
}]
|
||||||
|
```
|
||||||
|
|
||||||
|
You're all set!
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Start `geth` with the `console` command
|
||||||
|
2. Check the card's URL by checking `personal.listWallets`:
|
||||||
|
|
||||||
|
```
|
||||||
|
listWallets: [{
|
||||||
|
status: "Online, can derive public keys",
|
||||||
|
url: "keycard://a4d73015"
|
||||||
|
}]
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Open the wallet, you will be prompted for your pairing password, then PIN:
|
||||||
|
|
||||||
|
```
|
||||||
|
personal.openWallet("keycard://a4d73015")
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Check that creation was successful by typing e.g. `personal`. Then use it like a regular wallet.
|
||||||
|
|
||||||
|
## Known issues
|
||||||
|
|
||||||
|
* Starting geth with a valid card seems to make firefox crash.
|
||||||
|
* PCSC version 4.4 should work, but is currently untested
|
||||||
87
accounts/scwallet/apdu.go
Normal file
87
accounts/scwallet/apdu.go
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package scwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// commandAPDU represents an application data unit sent to a smartcard.
|
||||||
|
type commandAPDU struct {
|
||||||
|
Cla, Ins, P1, P2 uint8 // Class, Instruction, Parameter 1, Parameter 2
|
||||||
|
Data []byte // Command data
|
||||||
|
Le uint8 // Command data length
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize serializes a command APDU.
|
||||||
|
func (ca commandAPDU) serialize() ([]byte, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
if err := binary.Write(buf, binary.BigEndian, ca.Cla); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(buf, binary.BigEndian, ca.Ins); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(buf, binary.BigEndian, ca.P1); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(buf, binary.BigEndian, ca.P2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(ca.Data) > 0 {
|
||||||
|
if err := binary.Write(buf, binary.BigEndian, uint8(len(ca.Data))); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(buf, binary.BigEndian, ca.Data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := binary.Write(buf, binary.BigEndian, ca.Le); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// responseAPDU represents an application data unit received from a smart card.
|
||||||
|
type responseAPDU struct {
|
||||||
|
Data []byte // response data
|
||||||
|
Sw1, Sw2 uint8 // status words 1 and 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// deserialize deserializes a response APDU.
|
||||||
|
func (ra *responseAPDU) deserialize(data []byte) error {
|
||||||
|
if len(data) < 2 {
|
||||||
|
return fmt.Errorf("can not deserialize data: payload too short (%d < 2)", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
ra.Data = make([]byte, len(data)-2)
|
||||||
|
|
||||||
|
buf := bytes.NewReader(data)
|
||||||
|
if err := binary.Read(buf, binary.BigEndian, &ra.Data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Read(buf, binary.BigEndian, &ra.Sw1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Read(buf, binary.BigEndian, &ra.Sw2); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
303
accounts/scwallet/hub.go
Normal file
303
accounts/scwallet/hub.go
Normal file
|
|
@ -0,0 +1,303 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// This package implements support for smartcard-based hardware wallets such as
|
||||||
|
// the one written by Status: https://github.com/status-im/hardware-wallet
|
||||||
|
//
|
||||||
|
// This implementation of smartcard wallets have a different interaction process
|
||||||
|
// to other types of hardware wallet. The process works like this:
|
||||||
|
//
|
||||||
|
// 1. (First use with a given client) Establish a pairing between hardware
|
||||||
|
// wallet and client. This requires a secret value called a 'pairing password'.
|
||||||
|
// You can pair with an unpaired wallet with `personal.openWallet(URI, pairing password)`.
|
||||||
|
// 2. (First use only) Initialize the wallet, which generates a keypair, stores
|
||||||
|
// it on the wallet, and returns it so the user can back it up. You can
|
||||||
|
// initialize a wallet with `personal.initializeWallet(URI)`.
|
||||||
|
// 3. Connect to the wallet using the pairing information established in step 1.
|
||||||
|
// You can connect to a paired wallet with `personal.openWallet(URI, PIN)`.
|
||||||
|
// 4. Interact with the wallet as normal.
|
||||||
|
|
||||||
|
package scwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/event"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/log"
|
||||||
|
pcsc "github.com/gballet/go-libpcsclite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scheme is the URI prefix for smartcard wallets.
|
||||||
|
const Scheme = "keycard"
|
||||||
|
|
||||||
|
// refreshCycle is the maximum time between wallet refreshes (if USB hotplug
|
||||||
|
// notifications don't work).
|
||||||
|
const refreshCycle = time.Second
|
||||||
|
|
||||||
|
// refreshThrottling is the minimum time between wallet refreshes to avoid thrashing.
|
||||||
|
const refreshThrottling = 500 * time.Millisecond
|
||||||
|
|
||||||
|
// smartcardPairing contains information about a smart card we have paired with
|
||||||
|
// or might pair with the hub.
|
||||||
|
type smartcardPairing struct {
|
||||||
|
PublicKey []byte `json:"publicKey"`
|
||||||
|
PairingIndex uint8 `json:"pairingIndex"`
|
||||||
|
PairingKey []byte `json:"pairingKey"`
|
||||||
|
Accounts map[common.Address]accounts.DerivationPath `json:"accounts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hub is a accounts.Backend that can find and handle generic PC/SC hardware wallets.
|
||||||
|
type Hub struct {
|
||||||
|
scheme string // Protocol scheme prefixing account and wallet URLs.
|
||||||
|
|
||||||
|
context *pcsc.Client
|
||||||
|
datadir string
|
||||||
|
pairings map[string]smartcardPairing
|
||||||
|
|
||||||
|
refreshed time.Time // Time instance when the list of wallets was last refreshed
|
||||||
|
wallets map[string]*Wallet // Mapping from reader names to wallet instances
|
||||||
|
updateFeed event.Feed // Event feed to notify wallet additions/removals
|
||||||
|
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
|
||||||
|
updating bool // Whether the event notification loop is running
|
||||||
|
|
||||||
|
quit chan chan error
|
||||||
|
|
||||||
|
stateLock sync.RWMutex // Protects the internals of the hub from racey access
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hub *Hub) readPairings() error {
|
||||||
|
hub.pairings = make(map[string]smartcardPairing)
|
||||||
|
pairingFile, err := os.Open(filepath.Join(hub.datadir, "smartcards.json"))
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer pairingFile.Close()
|
||||||
|
|
||||||
|
pairingData, err := io.ReadAll(pairingFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var pairings []smartcardPairing
|
||||||
|
if err := json.Unmarshal(pairingData, &pairings); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pairing := range pairings {
|
||||||
|
hub.pairings[string(pairing.PublicKey)] = pairing
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hub *Hub) writePairings() error {
|
||||||
|
pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer pairingFile.Close()
|
||||||
|
|
||||||
|
pairings := make([]smartcardPairing, 0, len(hub.pairings))
|
||||||
|
for _, pairing := range hub.pairings {
|
||||||
|
pairings = append(pairings, pairing)
|
||||||
|
}
|
||||||
|
|
||||||
|
pairingData, err := json.Marshal(pairings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := pairingFile.Write(pairingData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hub *Hub) pairing(wallet *Wallet) *smartcardPairing {
|
||||||
|
if pairing, ok := hub.pairings[string(wallet.PublicKey)]; ok {
|
||||||
|
return &pairing
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hub *Hub) setPairing(wallet *Wallet, pairing *smartcardPairing) error {
|
||||||
|
if pairing == nil {
|
||||||
|
delete(hub.pairings, string(wallet.PublicKey))
|
||||||
|
} else {
|
||||||
|
hub.pairings[string(wallet.PublicKey)] = *pairing
|
||||||
|
}
|
||||||
|
return hub.writePairings()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHub creates a new hardware wallet manager for smartcards.
|
||||||
|
func NewHub(daemonPath string, scheme string, datadir string) (*Hub, error) {
|
||||||
|
context, err := pcsc.EstablishContext(daemonPath, pcsc.ScopeSystem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hub := &Hub{
|
||||||
|
scheme: scheme,
|
||||||
|
context: context,
|
||||||
|
datadir: datadir,
|
||||||
|
wallets: make(map[string]*Wallet),
|
||||||
|
quit: make(chan chan error),
|
||||||
|
}
|
||||||
|
if err := hub.readPairings(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hub.refreshWallets()
|
||||||
|
return hub, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wallets implements accounts.Backend, returning all the currently tracked smart
|
||||||
|
// cards that appear to be hardware wallets.
|
||||||
|
func (hub *Hub) Wallets() []accounts.Wallet {
|
||||||
|
// Make sure the list of wallets is up to date
|
||||||
|
hub.refreshWallets()
|
||||||
|
|
||||||
|
hub.stateLock.RLock()
|
||||||
|
defer hub.stateLock.RUnlock()
|
||||||
|
|
||||||
|
cpy := make([]accounts.Wallet, 0, len(hub.wallets))
|
||||||
|
for _, wallet := range hub.wallets {
|
||||||
|
cpy = append(cpy, wallet)
|
||||||
|
}
|
||||||
|
sort.Sort(accounts.WalletsByURL(cpy))
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// refreshWallets scans the devices attached to the machine and updates the
|
||||||
|
// list of wallets based on the found devices.
|
||||||
|
func (hub *Hub) refreshWallets() {
|
||||||
|
// Don't scan the USB like crazy it the user fetches wallets in a loop
|
||||||
|
hub.stateLock.RLock()
|
||||||
|
elapsed := time.Since(hub.refreshed)
|
||||||
|
hub.stateLock.RUnlock()
|
||||||
|
|
||||||
|
if elapsed < refreshThrottling {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Retrieve all the smart card reader to check for cards
|
||||||
|
readers, err := hub.context.ListReaders()
|
||||||
|
if err != nil {
|
||||||
|
// This is a perverted hack, the scard library returns an error if no card
|
||||||
|
// readers are present instead of simply returning an empty list. We don't
|
||||||
|
// want to fill the user's log with errors, so filter those out.
|
||||||
|
if err.Error() != "scard: Cannot find a smart card reader." {
|
||||||
|
log.Error("Failed to enumerate smart card readers", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Transform the current list of wallets into the new one
|
||||||
|
hub.stateLock.Lock()
|
||||||
|
|
||||||
|
events := []accounts.WalletEvent{}
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
|
||||||
|
for _, reader := range readers {
|
||||||
|
// Mark the reader as present
|
||||||
|
seen[reader] = struct{}{}
|
||||||
|
|
||||||
|
// If we already know about this card, skip to the next reader, otherwise clean up
|
||||||
|
if wallet, ok := hub.wallets[reader]; ok {
|
||||||
|
if err := wallet.ping(); err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wallet.Close()
|
||||||
|
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
|
||||||
|
delete(hub.wallets, reader)
|
||||||
|
}
|
||||||
|
// New card detected, try to connect to it
|
||||||
|
card, err := hub.context.Connect(reader, pcsc.ShareShared, pcsc.ProtocolAny)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to open smart card", "reader", reader, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wallet := NewWallet(hub, card)
|
||||||
|
if err = wallet.connect(); err != nil {
|
||||||
|
log.Debug("Failed to connect to smart card", "reader", reader, "err", err)
|
||||||
|
card.Disconnect(pcsc.LeaveCard)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Card connected, start tracking among the wallets
|
||||||
|
hub.wallets[reader] = wallet
|
||||||
|
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
|
||||||
|
}
|
||||||
|
// Remove any wallets no longer present
|
||||||
|
for reader, wallet := range hub.wallets {
|
||||||
|
if _, ok := seen[reader]; !ok {
|
||||||
|
wallet.Close()
|
||||||
|
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
|
||||||
|
delete(hub.wallets, reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hub.refreshed = time.Now()
|
||||||
|
hub.stateLock.Unlock()
|
||||||
|
|
||||||
|
for _, event := range events {
|
||||||
|
hub.updateFeed.Send(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe implements accounts.Backend, creating an async subscription to
|
||||||
|
// receive notifications on the addition or removal of smart card wallets.
|
||||||
|
func (hub *Hub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
|
||||||
|
// We need the mutex to reliably start/stop the update loop
|
||||||
|
hub.stateLock.Lock()
|
||||||
|
defer hub.stateLock.Unlock()
|
||||||
|
|
||||||
|
// Subscribe the caller and track the subscriber count
|
||||||
|
sub := hub.updateScope.Track(hub.updateFeed.Subscribe(sink))
|
||||||
|
|
||||||
|
// Subscribers require an active notification loop, start it
|
||||||
|
if !hub.updating {
|
||||||
|
hub.updating = true
|
||||||
|
go hub.updater()
|
||||||
|
}
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
|
// updater is responsible for maintaining an up-to-date list of wallets managed
|
||||||
|
// by the smart card hub, and for firing wallet addition/removal events.
|
||||||
|
func (hub *Hub) updater() {
|
||||||
|
for {
|
||||||
|
// TODO: Wait for a USB hotplug event (not supported yet) or a refresh timeout
|
||||||
|
// <-hub.changes
|
||||||
|
time.Sleep(refreshCycle)
|
||||||
|
|
||||||
|
// Run the wallet refresher
|
||||||
|
hub.refreshWallets()
|
||||||
|
|
||||||
|
// If all our subscribers left, stop the updater
|
||||||
|
hub.stateLock.Lock()
|
||||||
|
if hub.updateScope.Count() == 0 {
|
||||||
|
hub.updating = false
|
||||||
|
hub.stateLock.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hub.stateLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
339
accounts/scwallet/securechannel.go
Normal file
339
accounts/scwallet/securechannel.go
Normal file
|
|
@ -0,0 +1,339 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package scwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
|
pcsc "github.com/gballet/go-libpcsclite"
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxPayloadSize = 223
|
||||||
|
pairP1FirstStep = 0
|
||||||
|
pairP1LastStep = 1
|
||||||
|
|
||||||
|
scSecretLength = 32
|
||||||
|
scBlockSize = 16
|
||||||
|
|
||||||
|
insOpenSecureChannel = 0x10
|
||||||
|
insMutuallyAuthenticate = 0x11
|
||||||
|
insPair = 0x12
|
||||||
|
insUnpair = 0x13
|
||||||
|
|
||||||
|
pairingSalt = "Keycard Pairing Password Salt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SecureChannelSession enables secure communication with a hardware wallet.
|
||||||
|
type SecureChannelSession struct {
|
||||||
|
card *pcsc.Card // A handle to the smartcard for communication
|
||||||
|
secret []byte // A shared secret generated from our ECDSA keys
|
||||||
|
publicKey []byte // Our own ephemeral public key
|
||||||
|
PairingKey []byte // A permanent shared secret for a pairing, if present
|
||||||
|
sessionEncKey []byte // The current session encryption key
|
||||||
|
sessionMacKey []byte // The current session MAC key
|
||||||
|
iv []byte // The current IV
|
||||||
|
PairingIndex uint8 // The pairing index
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSecureChannelSession creates a new secure channel for the given card and public key.
|
||||||
|
func NewSecureChannelSession(card *pcsc.Card, keyData []byte) (*SecureChannelSession, error) {
|
||||||
|
// Generate an ECDSA keypair for ourselves
|
||||||
|
key, err := crypto.GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cardPublic, err := crypto.UnmarshalPubkey(keyData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not unmarshal public key from card: %v", err)
|
||||||
|
}
|
||||||
|
secret, _ := crypto.S256().ScalarMult(cardPublic.X, cardPublic.Y, key.D.Bytes())
|
||||||
|
return &SecureChannelSession{
|
||||||
|
card: card,
|
||||||
|
secret: secret.Bytes(),
|
||||||
|
publicKey: crypto.FromECDSAPub(&key.PublicKey),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pair establishes a new pairing with the smartcard.
|
||||||
|
func (s *SecureChannelSession) Pair(pairingPassword []byte) error {
|
||||||
|
secretHash := pbkdf2.Key(norm.NFKD.Bytes(pairingPassword), norm.NFKD.Bytes([]byte(pairingSalt)), 50000, 32, sha256.New)
|
||||||
|
|
||||||
|
challenge := make([]byte, 32)
|
||||||
|
if _, err := rand.Read(challenge); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := s.pair(pairP1FirstStep, challenge)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
md := sha256.New()
|
||||||
|
md.Write(secretHash[:])
|
||||||
|
md.Write(challenge)
|
||||||
|
|
||||||
|
expectedCryptogram := md.Sum(nil)
|
||||||
|
cardCryptogram := response.Data[:32]
|
||||||
|
cardChallenge := response.Data[32:64]
|
||||||
|
|
||||||
|
if !bytes.Equal(expectedCryptogram, cardCryptogram) {
|
||||||
|
return fmt.Errorf("invalid card cryptogram %v != %v", expectedCryptogram, cardCryptogram)
|
||||||
|
}
|
||||||
|
|
||||||
|
md.Reset()
|
||||||
|
md.Write(secretHash[:])
|
||||||
|
md.Write(cardChallenge)
|
||||||
|
response, err = s.pair(pairP1LastStep, md.Sum(nil))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
md.Reset()
|
||||||
|
md.Write(secretHash[:])
|
||||||
|
md.Write(response.Data[1:])
|
||||||
|
s.PairingKey = md.Sum(nil)
|
||||||
|
s.PairingIndex = response.Data[0]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpair disestablishes an existing pairing.
|
||||||
|
func (s *SecureChannelSession) Unpair() error {
|
||||||
|
if s.PairingKey == nil {
|
||||||
|
return errors.New("cannot unpair: not paired")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := s.transmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.PairingKey = nil
|
||||||
|
// Close channel
|
||||||
|
s.iv = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open initializes the secure channel.
|
||||||
|
func (s *SecureChannelSession) Open() error {
|
||||||
|
if s.iv != nil {
|
||||||
|
return errors.New("session already opened")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := s.open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the encryption/mac key by hashing our shared secret,
|
||||||
|
// pairing key, and the first bytes returned from the Open APDU.
|
||||||
|
md := sha512.New()
|
||||||
|
md.Write(s.secret)
|
||||||
|
md.Write(s.PairingKey)
|
||||||
|
md.Write(response.Data[:scSecretLength])
|
||||||
|
keyData := md.Sum(nil)
|
||||||
|
s.sessionEncKey = keyData[:scSecretLength]
|
||||||
|
s.sessionMacKey = keyData[scSecretLength : scSecretLength*2]
|
||||||
|
|
||||||
|
// The IV is the last bytes returned from the Open APDU.
|
||||||
|
s.iv = response.Data[scSecretLength:]
|
||||||
|
|
||||||
|
return s.mutuallyAuthenticate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutuallyAuthenticate is an internal method to authenticate both ends of the
|
||||||
|
// connection.
|
||||||
|
func (s *SecureChannelSession) mutuallyAuthenticate() error {
|
||||||
|
data := make([]byte, scSecretLength)
|
||||||
|
if _, err := rand.Read(data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := s.transmitEncrypted(claSCWallet, insMutuallyAuthenticate, 0, 0, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.Sw1 != 0x90 || response.Sw2 != 0x00 {
|
||||||
|
return fmt.Errorf("got unexpected response from MUTUALLY_AUTHENTICATE: %#x%x", response.Sw1, response.Sw2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(response.Data) != scSecretLength {
|
||||||
|
return fmt.Errorf("response from MUTUALLY_AUTHENTICATE was %d bytes, expected %d", len(response.Data), scSecretLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// open is an internal method that sends an open APDU.
|
||||||
|
func (s *SecureChannelSession) open() (*responseAPDU, error) {
|
||||||
|
return transmit(s.card, &commandAPDU{
|
||||||
|
Cla: claSCWallet,
|
||||||
|
Ins: insOpenSecureChannel,
|
||||||
|
P1: s.PairingIndex,
|
||||||
|
P2: 0,
|
||||||
|
Data: s.publicKey,
|
||||||
|
Le: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// pair is an internal method that sends a pair APDU.
|
||||||
|
func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*responseAPDU, error) {
|
||||||
|
return transmit(s.card, &commandAPDU{
|
||||||
|
Cla: claSCWallet,
|
||||||
|
Ins: insPair,
|
||||||
|
P1: p1,
|
||||||
|
P2: 0,
|
||||||
|
Data: data,
|
||||||
|
Le: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// transmitEncrypted sends an encrypted message, and decrypts and returns the response.
|
||||||
|
func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) {
|
||||||
|
if s.iv == nil {
|
||||||
|
return nil, errors.New("channel not open")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := s.encryptAPDU(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
meta := [16]byte{cla, ins, p1, p2, byte(len(data) + scBlockSize)}
|
||||||
|
if err = s.updateIV(meta[:], data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fulldata := make([]byte, len(s.iv)+len(data))
|
||||||
|
copy(fulldata, s.iv)
|
||||||
|
copy(fulldata[len(s.iv):], data)
|
||||||
|
|
||||||
|
response, err := transmit(s.card, &commandAPDU{
|
||||||
|
Cla: cla,
|
||||||
|
Ins: ins,
|
||||||
|
P1: p1,
|
||||||
|
P2: p2,
|
||||||
|
Data: fulldata,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rmeta := [16]byte{byte(len(response.Data))}
|
||||||
|
rmac := response.Data[:len(s.iv)]
|
||||||
|
rdata := response.Data[len(s.iv):]
|
||||||
|
plainData, err := s.decryptAPDU(rdata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s.updateIV(rmeta[:], rdata); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !bytes.Equal(s.iv, rmac) {
|
||||||
|
return nil, errors.New("invalid MAC in response")
|
||||||
|
}
|
||||||
|
|
||||||
|
rapdu := &responseAPDU{}
|
||||||
|
rapdu.deserialize(plainData)
|
||||||
|
|
||||||
|
if rapdu.Sw1 != sw1Ok {
|
||||||
|
return nil, fmt.Errorf("unexpected response status Cla=%#x, Ins=%#x, Sw=%#x%x", cla, ins, rapdu.Sw1, rapdu.Sw2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rapdu, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encryptAPDU is an internal method that serializes and encrypts an APDU.
|
||||||
|
func (s *SecureChannelSession) encryptAPDU(data []byte) ([]byte, error) {
|
||||||
|
if len(data) > maxPayloadSize {
|
||||||
|
return nil, fmt.Errorf("payload of %d bytes exceeds maximum of %d", len(data), maxPayloadSize)
|
||||||
|
}
|
||||||
|
data = pad(data, 0x80)
|
||||||
|
|
||||||
|
ret := make([]byte, len(data))
|
||||||
|
|
||||||
|
a, err := aes.NewCipher(s.sessionEncKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
crypter := cipher.NewCBCEncrypter(a, s.iv)
|
||||||
|
crypter.CryptBlocks(ret, data)
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pad applies message padding to a 16 byte boundary.
|
||||||
|
func pad(data []byte, terminator byte) []byte {
|
||||||
|
padded := make([]byte, (len(data)/16+1)*16)
|
||||||
|
copy(padded, data)
|
||||||
|
padded[len(data)] = terminator
|
||||||
|
return padded
|
||||||
|
}
|
||||||
|
|
||||||
|
// decryptAPDU is an internal method that decrypts and deserializes an APDU.
|
||||||
|
func (s *SecureChannelSession) decryptAPDU(data []byte) ([]byte, error) {
|
||||||
|
a, err := aes.NewCipher(s.sessionEncKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make([]byte, len(data))
|
||||||
|
|
||||||
|
crypter := cipher.NewCBCDecrypter(a, s.iv)
|
||||||
|
crypter.CryptBlocks(ret, data)
|
||||||
|
return unpad(ret, 0x80)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpad strips padding from a message.
|
||||||
|
func unpad(data []byte, terminator byte) ([]byte, error) {
|
||||||
|
for i := 1; i <= 16; i++ {
|
||||||
|
switch data[len(data)-i] {
|
||||||
|
case 0:
|
||||||
|
continue
|
||||||
|
case terminator:
|
||||||
|
return data[:len(data)-i], nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("expected end of padding, got %d", data[len(data)-i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("expected end of padding, got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateIV is an internal method that updates the initialization vector after
|
||||||
|
// each message exchanged.
|
||||||
|
func (s *SecureChannelSession) updateIV(meta, data []byte) error {
|
||||||
|
data = pad(data, 0)
|
||||||
|
a, err := aes.NewCipher(s.sessionMacKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
crypter := cipher.NewCBCEncrypter(a, make([]byte, 16))
|
||||||
|
crypter.CryptBlocks(meta, meta)
|
||||||
|
crypter.CryptBlocks(data, data)
|
||||||
|
// The first 16 bytes of the last block is the MAC
|
||||||
|
s.iv = data[len(data)-32 : len(data)-16]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
1096
accounts/scwallet/wallet.go
Normal file
1096
accounts/scwallet/wallet.go
Normal file
File diff suppressed because it is too large
Load diff
31
accounts/sort.go
Normal file
31
accounts/sort.go
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package accounts
|
||||||
|
|
||||||
|
// AccountsByURL implements sort.Interface for []Account based on the URL field.
|
||||||
|
type AccountsByURL []Account
|
||||||
|
|
||||||
|
func (a AccountsByURL) Len() int { return len(a) }
|
||||||
|
func (a AccountsByURL) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a AccountsByURL) Less(i, j int) bool { return a[i].URL.Cmp(a[j].URL) < 0 }
|
||||||
|
|
||||||
|
// WalletsByURL implements sort.Interface for []Wallet based on the URL field.
|
||||||
|
type WalletsByURL []Wallet
|
||||||
|
|
||||||
|
func (w WalletsByURL) Len() int { return len(w) }
|
||||||
|
func (w WalletsByURL) Swap(i, j int) { w[i], w[j] = w[j], w[i] }
|
||||||
|
func (w WalletsByURL) Less(i, j int) bool { return w[i].URL().Cmp(w[j].URL()) < 0 }
|
||||||
|
|
@ -74,6 +74,22 @@ func (u URL) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(u.String())
|
return json.Marshal(u.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON parses url.
|
||||||
|
func (u *URL) UnmarshalJSON(input []byte) error {
|
||||||
|
var textURL string
|
||||||
|
err := json.Unmarshal(input, &textURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
url, err := parseURL(textURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.Scheme = url.Scheme
|
||||||
|
u.Path = url.Path
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Cmp compares x and y and returns:
|
// Cmp compares x and y and returns:
|
||||||
//
|
//
|
||||||
// -1 if x < y
|
// -1 if x < y
|
||||||
|
|
|
||||||
102
accounts/url_test.go
Normal file
102
accounts/url_test.go
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package accounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestURLParsing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
url, err := parseURL("https://ethereum.org")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if url.Scheme != "https" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
|
||||||
|
}
|
||||||
|
if url.Path != "ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "ethereum.org", url.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, u := range []string{"ethereum.org", ""} {
|
||||||
|
if _, err = parseURL(u); err == nil {
|
||||||
|
t.Errorf("input %v, expected err, got: nil", u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
url := URL{Scheme: "https", Path: "ethereum.org"}
|
||||||
|
if url.String() != "https://ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https://ethereum.org", url.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
url = URL{Scheme: "", Path: "ethereum.org"}
|
||||||
|
if url.String() != "ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "ethereum.org", url.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLMarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
url := URL{Scheme: "https", Path: "ethereum.org"}
|
||||||
|
json, err := url.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if string(json) != "\"https://ethereum.org\"" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLUnmarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
url := &URL{}
|
||||||
|
err := url.UnmarshalJSON([]byte("\"https://ethereum.org\""))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if url.Scheme != "https" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
|
||||||
|
}
|
||||||
|
if url.Path != "ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https", url.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLComparison(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []struct {
|
||||||
|
urlA URL
|
||||||
|
urlB URL
|
||||||
|
expect int
|
||||||
|
}{
|
||||||
|
{URL{"https", "ethereum.org"}, URL{"https", "ethereum.org"}, 0},
|
||||||
|
{URL{"http", "ethereum.org"}, URL{"https", "ethereum.org"}, -1},
|
||||||
|
{URL{"https", "ethereum.org/a"}, URL{"https", "ethereum.org"}, 1},
|
||||||
|
{URL{"https", "abc.org"}, URL{"https", "ethereum.org"}, -1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
result := tt.urlA.Cmp(tt.urlB)
|
||||||
|
if result != tt.expect {
|
||||||
|
t.Errorf("test %d: cmp mismatch: expected: %d, got: %d", i, tt.expect, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
|
|
@ -62,18 +63,48 @@ type Hub struct {
|
||||||
stateLock sync.RWMutex // Protects the internals of the hub from racey access
|
stateLock sync.RWMutex // Protects the internals of the hub from racey access
|
||||||
|
|
||||||
// TODO(karalabe): remove if hotplug lands on Windows
|
// TODO(karalabe): remove if hotplug lands on Windows
|
||||||
commsPend int // Number of operations blocking enumeration
|
commsPend int // Number of operations blocking enumeration
|
||||||
commsLock sync.Mutex // Lock protecting the pending counter and enumeration
|
commsLock sync.Mutex // Lock protecting the pending counter and enumeration
|
||||||
|
enumFails atomic.Uint32 // Number of times enumeration has failed
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLedgerHub creates a new hardware wallet manager for Ledger devices.
|
// NewLedgerHub creates a new hardware wallet manager for Ledger devices.
|
||||||
func NewLedgerHub() (*Hub, error) {
|
func NewLedgerHub() (*Hub, error) {
|
||||||
return newHub(LedgerScheme, 0x2c97, []uint16{0x0000 /* Ledger Blue */, 0x0001 /* Ledger Nano S */}, 0xffa0, 0, newLedgerDriver)
|
return newHub(LedgerScheme, 0x2c97, []uint16{
|
||||||
|
|
||||||
|
// Device definitions taken from
|
||||||
|
// https://github.com/LedgerHQ/ledger-live/blob/38012bc8899e0f07149ea9cfe7e64b2c146bc92b/libs/ledgerjs/packages/devices/src/index.ts
|
||||||
|
|
||||||
|
// Original product IDs
|
||||||
|
0x0000, /* Ledger Blue */
|
||||||
|
0x0001, /* Ledger Nano S */
|
||||||
|
0x0004, /* Ledger Nano X */
|
||||||
|
0x0005, /* Ledger Nano S Plus */
|
||||||
|
0x0006, /* Ledger Nano FTS */
|
||||||
|
|
||||||
|
0x0015, /* HID + U2F + WebUSB Ledger Blue */
|
||||||
|
0x1015, /* HID + U2F + WebUSB Ledger Nano S */
|
||||||
|
0x4015, /* HID + U2F + WebUSB Ledger Nano X */
|
||||||
|
0x5015, /* HID + U2F + WebUSB Ledger Nano S Plus */
|
||||||
|
0x6015, /* HID + U2F + WebUSB Ledger Nano FTS */
|
||||||
|
|
||||||
|
0x0011, /* HID + WebUSB Ledger Blue */
|
||||||
|
0x1011, /* HID + WebUSB Ledger Nano S */
|
||||||
|
0x4011, /* HID + WebUSB Ledger Nano X */
|
||||||
|
0x5011, /* HID + WebUSB Ledger Nano S Plus */
|
||||||
|
0x6011, /* HID + WebUSB Ledger Nano FTS */
|
||||||
|
}, 0xffa0, 0, newLedgerDriver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTrezorHub creates a new hardware wallet manager for Trezor devices.
|
// NewTrezorHubWithHID creates a new hardware wallet manager for Trezor devices.
|
||||||
func NewTrezorHub() (*Hub, error) {
|
func NewTrezorHubWithHID() (*Hub, error) {
|
||||||
return newHub(TrezorScheme, 0x534c, []uint16{0x0001 /* Trezor 1 */}, 0xff00, 0, newTrezorDriver)
|
return newHub(TrezorScheme, 0x534c, []uint16{0x0001 /* Trezor HID */}, 0xff00, 0, newTrezorDriver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTrezorHubWithWebUSB creates a new hardware wallet manager for Trezor devices with
|
||||||
|
// firmware version > 1.8.0
|
||||||
|
func NewTrezorHubWithWebUSB() (*Hub, error) {
|
||||||
|
return newHub(TrezorScheme, 0x1209, []uint16{0x53c1 /* Trezor WebUSB */}, 0xffff /* No usage id on webusb, don't match unset (0) */, 0, newTrezorDriver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newHub creates a new hardware wallet manager for generic USB devices.
|
// newHub creates a new hardware wallet manager for generic USB devices.
|
||||||
|
|
@ -119,6 +150,10 @@ func (hub *Hub) refreshWallets() {
|
||||||
if elapsed < refreshThrottling {
|
if elapsed < refreshThrottling {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// If USB enumeration is continually failing, don't keep trying indefinitely
|
||||||
|
if hub.enumFails.Load() > 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
// Retrieve the current list of USB wallet devices
|
// Retrieve the current list of USB wallet devices
|
||||||
var devices []hid.DeviceInfo
|
var devices []hid.DeviceInfo
|
||||||
|
|
||||||
|
|
@ -127,7 +162,7 @@ func (hub *Hub) refreshWallets() {
|
||||||
// breaking the Ledger protocol if that is waiting for user confirmation. This
|
// breaking the Ledger protocol if that is waiting for user confirmation. This
|
||||||
// is a bug acknowledged at Ledger, but it won't be fixed on old devices so we
|
// is a bug acknowledged at Ledger, but it won't be fixed on old devices so we
|
||||||
// need to prevent concurrent comms ourselves. The more elegant solution would
|
// need to prevent concurrent comms ourselves. The more elegant solution would
|
||||||
// be to ditch enumeration in favor of hutplug events, but that don't work yet
|
// be to ditch enumeration in favor of hotplug events, but that don't work yet
|
||||||
// on Windows so if we need to hack it anyway, this is more elegant for now.
|
// on Windows so if we need to hack it anyway, this is more elegant for now.
|
||||||
hub.commsLock.Lock()
|
hub.commsLock.Lock()
|
||||||
if hub.commsPend > 0 { // A confirmation is pending, don't refresh
|
if hub.commsPend > 0 { // A confirmation is pending, don't refresh
|
||||||
|
|
@ -135,8 +170,22 @@ func (hub *Hub) refreshWallets() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, info := range hid.Enumerate(hub.vendorID, 0) {
|
infos, err := hid.Enumerate(hub.vendorID, 0)
|
||||||
|
if err != nil {
|
||||||
|
failcount := hub.enumFails.Add(1)
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
// See rationale before the enumeration why this is needed and only on Linux.
|
||||||
|
hub.commsLock.Unlock()
|
||||||
|
}
|
||||||
|
log.Error("Failed to enumerate USB devices", "hub", hub.scheme,
|
||||||
|
"vendor", hub.vendorID, "failcount", failcount, "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hub.enumFails.Store(0)
|
||||||
|
|
||||||
|
for _, info := range infos {
|
||||||
for _, id := range hub.productIDs {
|
for _, id := range hub.productIDs {
|
||||||
|
// Windows and Macos use UsageID matching, Linux uses Interface matching
|
||||||
if info.ProductID == id && (info.UsagePage == hub.usageID || info.Interface == hub.endpointID) {
|
if info.ProductID == id && (info.UsagePage == hub.usageID || info.Interface == hub.endpointID) {
|
||||||
devices = append(devices, info)
|
devices = append(devices, info)
|
||||||
break
|
break
|
||||||
|
|
@ -150,8 +199,10 @@ func (hub *Hub) refreshWallets() {
|
||||||
// Transform the current list of wallets into the new one
|
// Transform the current list of wallets into the new one
|
||||||
hub.stateLock.Lock()
|
hub.stateLock.Lock()
|
||||||
|
|
||||||
wallets := make([]accounts.Wallet, 0, len(devices))
|
var (
|
||||||
events := []accounts.WalletEvent{}
|
wallets = make([]accounts.Wallet, 0, len(devices))
|
||||||
|
events []accounts.WalletEvent
|
||||||
|
)
|
||||||
|
|
||||||
for _, device := range devices {
|
for _, device := range devices {
|
||||||
url := accounts.URL{Scheme: hub.scheme, Path: device.Path}
|
url := accounts.URL{Scheme: hub.scheme, Path: device.Path}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,905 +0,0 @@
|
||||||
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
|
||||||
// https://github.com/trezor/trezor-common/blob/master/protob/messages.proto
|
|
||||||
// dated 28.07.2017, commit dd8ec3231fb5f7992360aff9bdfe30bb58130f4b.
|
|
||||||
|
|
||||||
syntax = "proto2";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Messages for TREZOR communication
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Sugar for easier handling in Java
|
|
||||||
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
|
||||||
option java_outer_classname = "TrezorMessage";
|
|
||||||
|
|
||||||
import "types.proto";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapping between Trezor wire identifier (uint) and a protobuf message
|
|
||||||
*/
|
|
||||||
enum MessageType {
|
|
||||||
MessageType_Initialize = 0 [(wire_in) = true];
|
|
||||||
MessageType_Ping = 1 [(wire_in) = true];
|
|
||||||
MessageType_Success = 2 [(wire_out) = true];
|
|
||||||
MessageType_Failure = 3 [(wire_out) = true];
|
|
||||||
MessageType_ChangePin = 4 [(wire_in) = true];
|
|
||||||
MessageType_WipeDevice = 5 [(wire_in) = true];
|
|
||||||
MessageType_FirmwareErase = 6 [(wire_in) = true, (wire_bootloader) = true];
|
|
||||||
MessageType_FirmwareUpload = 7 [(wire_in) = true, (wire_bootloader) = true];
|
|
||||||
MessageType_FirmwareRequest = 8 [(wire_out) = true, (wire_bootloader) = true];
|
|
||||||
MessageType_GetEntropy = 9 [(wire_in) = true];
|
|
||||||
MessageType_Entropy = 10 [(wire_out) = true];
|
|
||||||
MessageType_GetPublicKey = 11 [(wire_in) = true];
|
|
||||||
MessageType_PublicKey = 12 [(wire_out) = true];
|
|
||||||
MessageType_LoadDevice = 13 [(wire_in) = true];
|
|
||||||
MessageType_ResetDevice = 14 [(wire_in) = true];
|
|
||||||
MessageType_SignTx = 15 [(wire_in) = true];
|
|
||||||
MessageType_SimpleSignTx = 16 [(wire_in) = true, deprecated = true];
|
|
||||||
MessageType_Features = 17 [(wire_out) = true];
|
|
||||||
MessageType_PinMatrixRequest = 18 [(wire_out) = true];
|
|
||||||
MessageType_PinMatrixAck = 19 [(wire_in) = true, (wire_tiny) = true];
|
|
||||||
MessageType_Cancel = 20 [(wire_in) = true];
|
|
||||||
MessageType_TxRequest = 21 [(wire_out) = true];
|
|
||||||
MessageType_TxAck = 22 [(wire_in) = true];
|
|
||||||
MessageType_CipherKeyValue = 23 [(wire_in) = true];
|
|
||||||
MessageType_ClearSession = 24 [(wire_in) = true];
|
|
||||||
MessageType_ApplySettings = 25 [(wire_in) = true];
|
|
||||||
MessageType_ButtonRequest = 26 [(wire_out) = true];
|
|
||||||
MessageType_ButtonAck = 27 [(wire_in) = true, (wire_tiny) = true];
|
|
||||||
MessageType_ApplyFlags = 28 [(wire_in) = true];
|
|
||||||
MessageType_GetAddress = 29 [(wire_in) = true];
|
|
||||||
MessageType_Address = 30 [(wire_out) = true];
|
|
||||||
MessageType_SelfTest = 32 [(wire_in) = true, (wire_bootloader) = true];
|
|
||||||
MessageType_BackupDevice = 34 [(wire_in) = true];
|
|
||||||
MessageType_EntropyRequest = 35 [(wire_out) = true];
|
|
||||||
MessageType_EntropyAck = 36 [(wire_in) = true];
|
|
||||||
MessageType_SignMessage = 38 [(wire_in) = true];
|
|
||||||
MessageType_VerifyMessage = 39 [(wire_in) = true];
|
|
||||||
MessageType_MessageSignature = 40 [(wire_out) = true];
|
|
||||||
MessageType_PassphraseRequest = 41 [(wire_out) = true];
|
|
||||||
MessageType_PassphraseAck = 42 [(wire_in) = true, (wire_tiny) = true];
|
|
||||||
MessageType_EstimateTxSize = 43 [(wire_in) = true, deprecated = true];
|
|
||||||
MessageType_TxSize = 44 [(wire_out) = true, deprecated = true];
|
|
||||||
MessageType_RecoveryDevice = 45 [(wire_in) = true];
|
|
||||||
MessageType_WordRequest = 46 [(wire_out) = true];
|
|
||||||
MessageType_WordAck = 47 [(wire_in) = true];
|
|
||||||
MessageType_CipheredKeyValue = 48 [(wire_out) = true];
|
|
||||||
MessageType_EncryptMessage = 49 [(wire_in) = true, deprecated = true];
|
|
||||||
MessageType_EncryptedMessage = 50 [(wire_out) = true, deprecated = true];
|
|
||||||
MessageType_DecryptMessage = 51 [(wire_in) = true, deprecated = true];
|
|
||||||
MessageType_DecryptedMessage = 52 [(wire_out) = true, deprecated = true];
|
|
||||||
MessageType_SignIdentity = 53 [(wire_in) = true];
|
|
||||||
MessageType_SignedIdentity = 54 [(wire_out) = true];
|
|
||||||
MessageType_GetFeatures = 55 [(wire_in) = true];
|
|
||||||
MessageType_EthereumGetAddress = 56 [(wire_in) = true];
|
|
||||||
MessageType_EthereumAddress = 57 [(wire_out) = true];
|
|
||||||
MessageType_EthereumSignTx = 58 [(wire_in) = true];
|
|
||||||
MessageType_EthereumTxRequest = 59 [(wire_out) = true];
|
|
||||||
MessageType_EthereumTxAck = 60 [(wire_in) = true];
|
|
||||||
MessageType_GetECDHSessionKey = 61 [(wire_in) = true];
|
|
||||||
MessageType_ECDHSessionKey = 62 [(wire_out) = true];
|
|
||||||
MessageType_SetU2FCounter = 63 [(wire_in) = true];
|
|
||||||
MessageType_EthereumSignMessage = 64 [(wire_in) = true];
|
|
||||||
MessageType_EthereumVerifyMessage = 65 [(wire_in) = true];
|
|
||||||
MessageType_EthereumMessageSignature = 66 [(wire_out) = true];
|
|
||||||
MessageType_DebugLinkDecision = 100 [(wire_debug_in) = true, (wire_tiny) = true];
|
|
||||||
MessageType_DebugLinkGetState = 101 [(wire_debug_in) = true];
|
|
||||||
MessageType_DebugLinkState = 102 [(wire_debug_out) = true];
|
|
||||||
MessageType_DebugLinkStop = 103 [(wire_debug_in) = true];
|
|
||||||
MessageType_DebugLinkLog = 104 [(wire_debug_out) = true];
|
|
||||||
MessageType_DebugLinkMemoryRead = 110 [(wire_debug_in) = true];
|
|
||||||
MessageType_DebugLinkMemory = 111 [(wire_debug_out) = true];
|
|
||||||
MessageType_DebugLinkMemoryWrite = 112 [(wire_debug_in) = true];
|
|
||||||
MessageType_DebugLinkFlashErase = 113 [(wire_debug_in) = true];
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////
|
|
||||||
// Basic messages //
|
|
||||||
////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Reset device to default state and ask for device details
|
|
||||||
* @next Features
|
|
||||||
*/
|
|
||||||
message Initialize {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask for device details (no device reset)
|
|
||||||
* @next Features
|
|
||||||
*/
|
|
||||||
message GetFeatures {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Reports various information about the device
|
|
||||||
* @prev Initialize
|
|
||||||
* @prev GetFeatures
|
|
||||||
*/
|
|
||||||
message Features {
|
|
||||||
optional string vendor = 1; // name of the manufacturer, e.g. "bitcointrezor.com"
|
|
||||||
optional uint32 major_version = 2; // major version of the device, e.g. 1
|
|
||||||
optional uint32 minor_version = 3; // minor version of the device, e.g. 0
|
|
||||||
optional uint32 patch_version = 4; // patch version of the device, e.g. 0
|
|
||||||
optional bool bootloader_mode = 5; // is device in bootloader mode?
|
|
||||||
optional string device_id = 6; // device's unique identifier
|
|
||||||
optional bool pin_protection = 7; // is device protected by PIN?
|
|
||||||
optional bool passphrase_protection = 8; // is node/mnemonic encrypted using passphrase?
|
|
||||||
optional string language = 9; // device language
|
|
||||||
optional string label = 10; // device description label
|
|
||||||
repeated CoinType coins = 11; // supported coins
|
|
||||||
optional bool initialized = 12; // does device contain seed?
|
|
||||||
optional bytes revision = 13; // SCM revision of firmware
|
|
||||||
optional bytes bootloader_hash = 14; // hash of the bootloader
|
|
||||||
optional bool imported = 15; // was storage imported from an external source?
|
|
||||||
optional bool pin_cached = 16; // is PIN already cached in session?
|
|
||||||
optional bool passphrase_cached = 17; // is passphrase already cached in session?
|
|
||||||
optional bool firmware_present = 18; // is valid firmware loaded?
|
|
||||||
optional bool needs_backup = 19; // does storage need backup? (equals to Storage.needs_backup)
|
|
||||||
optional uint32 flags = 20; // device flags (equals to Storage.flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: clear session (removes cached PIN, passphrase, etc).
|
|
||||||
* @next Success
|
|
||||||
*/
|
|
||||||
message ClearSession {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: change language and/or label of the device
|
|
||||||
* @next Success
|
|
||||||
* @next Failure
|
|
||||||
* @next ButtonRequest
|
|
||||||
* @next PinMatrixRequest
|
|
||||||
*/
|
|
||||||
message ApplySettings {
|
|
||||||
optional string language = 1;
|
|
||||||
optional string label = 2;
|
|
||||||
optional bool use_passphrase = 3;
|
|
||||||
optional bytes homescreen = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: set flags of the device
|
|
||||||
* @next Success
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message ApplyFlags {
|
|
||||||
optional uint32 flags = 1; // bitmask, can only set bits, not unset
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Starts workflow for setting/changing/removing the PIN
|
|
||||||
* @next ButtonRequest
|
|
||||||
* @next PinMatrixRequest
|
|
||||||
*/
|
|
||||||
message ChangePin {
|
|
||||||
optional bool remove = 1; // is PIN removal requested?
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Test if the device is alive, device sends back the message in Success response
|
|
||||||
* @next Success
|
|
||||||
*/
|
|
||||||
message Ping {
|
|
||||||
optional string message = 1; // message to send back in Success message
|
|
||||||
optional bool button_protection = 2; // ask for button press
|
|
||||||
optional bool pin_protection = 3; // ask for PIN if set in device
|
|
||||||
optional bool passphrase_protection = 4; // ask for passphrase if set in device
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Success of the previous request
|
|
||||||
*/
|
|
||||||
message Success {
|
|
||||||
optional string message = 1; // human readable description of action or request-specific payload
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Failure of the previous request
|
|
||||||
*/
|
|
||||||
message Failure {
|
|
||||||
optional FailureType code = 1; // computer-readable definition of the error state
|
|
||||||
optional string message = 2; // human-readable message of the error state
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Device is waiting for HW button press.
|
|
||||||
* @next ButtonAck
|
|
||||||
* @next Cancel
|
|
||||||
*/
|
|
||||||
message ButtonRequest {
|
|
||||||
optional ButtonRequestType code = 1;
|
|
||||||
optional string data = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Computer agrees to wait for HW button press
|
|
||||||
* @prev ButtonRequest
|
|
||||||
*/
|
|
||||||
message ButtonAck {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Device is asking computer to show PIN matrix and awaits PIN encoded using this matrix scheme
|
|
||||||
* @next PinMatrixAck
|
|
||||||
* @next Cancel
|
|
||||||
*/
|
|
||||||
message PinMatrixRequest {
|
|
||||||
optional PinMatrixRequestType type = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Computer responds with encoded PIN
|
|
||||||
* @prev PinMatrixRequest
|
|
||||||
*/
|
|
||||||
message PinMatrixAck {
|
|
||||||
required string pin = 1; // matrix encoded PIN entered by user
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Abort last operation that required user interaction
|
|
||||||
* @prev ButtonRequest
|
|
||||||
* @prev PinMatrixRequest
|
|
||||||
* @prev PassphraseRequest
|
|
||||||
*/
|
|
||||||
message Cancel {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Device awaits encryption passphrase
|
|
||||||
* @next PassphraseAck
|
|
||||||
* @next Cancel
|
|
||||||
*/
|
|
||||||
message PassphraseRequest {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Send passphrase back
|
|
||||||
* @prev PassphraseRequest
|
|
||||||
*/
|
|
||||||
message PassphraseAck {
|
|
||||||
required string passphrase = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Request a sample of random data generated by hardware RNG. May be used for testing.
|
|
||||||
* @next ButtonRequest
|
|
||||||
* @next Entropy
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message GetEntropy {
|
|
||||||
required uint32 size = 1; // size of requested entropy
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Reply with random data generated by internal RNG
|
|
||||||
* @prev GetEntropy
|
|
||||||
*/
|
|
||||||
message Entropy {
|
|
||||||
required bytes entropy = 1; // stream of random generated bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device for public key corresponding to address_n path
|
|
||||||
* @next PassphraseRequest
|
|
||||||
* @next PublicKey
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message GetPublicKey {
|
|
||||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
|
||||||
optional string ecdsa_curve_name = 2; // ECDSA curve name to use
|
|
||||||
optional bool show_display = 3; // optionally show on display before sending the result
|
|
||||||
optional string coin_name = 4 [default='Bitcoin'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Contains public key derived from device private seed
|
|
||||||
* @prev GetPublicKey
|
|
||||||
*/
|
|
||||||
message PublicKey {
|
|
||||||
required HDNodeType node = 1; // BIP32 public node
|
|
||||||
optional string xpub = 2; // serialized form of public node
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device for address corresponding to address_n path
|
|
||||||
* @next PassphraseRequest
|
|
||||||
* @next Address
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message GetAddress {
|
|
||||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
|
||||||
optional string coin_name = 2 [default='Bitcoin'];
|
|
||||||
optional bool show_display = 3 ; // optionally show on display before sending the result
|
|
||||||
optional MultisigRedeemScriptType multisig = 4; // filled if we are showing a multisig address
|
|
||||||
optional InputScriptType script_type = 5 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device for Ethereum address corresponding to address_n path
|
|
||||||
* @next PassphraseRequest
|
|
||||||
* @next EthereumAddress
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message EthereumGetAddress {
|
|
||||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
|
||||||
optional bool show_display = 2; // optionally show on display before sending the result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Contains address derived from device private seed
|
|
||||||
* @prev GetAddress
|
|
||||||
*/
|
|
||||||
message Address {
|
|
||||||
required string address = 1; // Coin address in Base58 encoding
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Contains an Ethereum address derived from device private seed
|
|
||||||
* @prev EthereumGetAddress
|
|
||||||
*/
|
|
||||||
message EthereumAddress {
|
|
||||||
required bytes address = 1; // Coin address as an Ethereum 160 bit hash
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Request device to wipe all sensitive data and settings
|
|
||||||
* @next ButtonRequest
|
|
||||||
*/
|
|
||||||
message WipeDevice {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Load seed and related internal settings from the computer
|
|
||||||
* @next ButtonRequest
|
|
||||||
* @next Success
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message LoadDevice {
|
|
||||||
optional string mnemonic = 1; // seed encoded as BIP-39 mnemonic (12, 18 or 24 words)
|
|
||||||
optional HDNodeType node = 2; // BIP-32 node
|
|
||||||
optional string pin = 3; // set PIN protection
|
|
||||||
optional bool passphrase_protection = 4; // enable master node encryption using passphrase
|
|
||||||
optional string language = 5 [default='english']; // device language
|
|
||||||
optional string label = 6; // device label
|
|
||||||
optional bool skip_checksum = 7; // do not test mnemonic for valid BIP-39 checksum
|
|
||||||
optional uint32 u2f_counter = 8; // U2F counter
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to do initialization involving user interaction
|
|
||||||
* @next EntropyRequest
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message ResetDevice {
|
|
||||||
optional bool display_random = 1; // display entropy generated by the device before asking for additional entropy
|
|
||||||
optional uint32 strength = 2 [default=256]; // strength of seed in bits
|
|
||||||
optional bool passphrase_protection = 3; // enable master node encryption using passphrase
|
|
||||||
optional bool pin_protection = 4; // enable PIN protection
|
|
||||||
optional string language = 5 [default='english']; // device language
|
|
||||||
optional string label = 6; // device label
|
|
||||||
optional uint32 u2f_counter = 7; // U2F counter
|
|
||||||
optional bool skip_backup = 8; // postpone seed backup to BackupDevice workflow
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Perform backup of the device seed if not backed up using ResetDevice
|
|
||||||
* @next ButtonRequest
|
|
||||||
*/
|
|
||||||
message BackupDevice {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Ask for additional entropy from host computer
|
|
||||||
* @prev ResetDevice
|
|
||||||
* @next EntropyAck
|
|
||||||
*/
|
|
||||||
message EntropyRequest {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Provide additional entropy for seed generation function
|
|
||||||
* @prev EntropyRequest
|
|
||||||
* @next ButtonRequest
|
|
||||||
*/
|
|
||||||
message EntropyAck {
|
|
||||||
optional bytes entropy = 1; // 256 bits (32 bytes) of random data
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Start recovery workflow asking user for specific words of mnemonic
|
|
||||||
* Used to recovery device safely even on untrusted computer.
|
|
||||||
* @next WordRequest
|
|
||||||
*/
|
|
||||||
message RecoveryDevice {
|
|
||||||
optional uint32 word_count = 1; // number of words in BIP-39 mnemonic
|
|
||||||
optional bool passphrase_protection = 2; // enable master node encryption using passphrase
|
|
||||||
optional bool pin_protection = 3; // enable PIN protection
|
|
||||||
optional string language = 4 [default='english']; // device language
|
|
||||||
optional string label = 5; // device label
|
|
||||||
optional bool enforce_wordlist = 6; // enforce BIP-39 wordlist during the process
|
|
||||||
// 7 reserved for unused recovery method
|
|
||||||
optional uint32 type = 8; // supported recovery type (see RecoveryType)
|
|
||||||
optional uint32 u2f_counter = 9; // U2F counter
|
|
||||||
optional bool dry_run = 10; // perform dry-run recovery workflow (for safe mnemonic validation)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Device is waiting for user to enter word of the mnemonic
|
|
||||||
* Its position is shown only on device's internal display.
|
|
||||||
* @prev RecoveryDevice
|
|
||||||
* @prev WordAck
|
|
||||||
*/
|
|
||||||
message WordRequest {
|
|
||||||
optional WordRequestType type = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Computer replies with word from the mnemonic
|
|
||||||
* @prev WordRequest
|
|
||||||
* @next WordRequest
|
|
||||||
* @next Success
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message WordAck {
|
|
||||||
required string word = 1; // one word of mnemonic on asked position
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////
|
|
||||||
// Message signing messages //
|
|
||||||
//////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to sign message
|
|
||||||
* @next MessageSignature
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message SignMessage {
|
|
||||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
|
||||||
required bytes message = 2; // message to be signed
|
|
||||||
optional string coin_name = 3 [default='Bitcoin']; // coin to use for signing
|
|
||||||
optional InputScriptType script_type = 4 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to verify message
|
|
||||||
* @next Success
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message VerifyMessage {
|
|
||||||
optional string address = 1; // address to verify
|
|
||||||
optional bytes signature = 2; // signature to verify
|
|
||||||
optional bytes message = 3; // message to verify
|
|
||||||
optional string coin_name = 4 [default='Bitcoin']; // coin to use for verifying
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Signed message
|
|
||||||
* @prev SignMessage
|
|
||||||
*/
|
|
||||||
message MessageSignature {
|
|
||||||
optional string address = 1; // address used to sign the message
|
|
||||||
optional bytes signature = 2; // signature of the message
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////
|
|
||||||
// Encryption/decryption //
|
|
||||||
///////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to encrypt message
|
|
||||||
* @next EncryptedMessage
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message EncryptMessage {
|
|
||||||
optional bytes pubkey = 1; // public key
|
|
||||||
optional bytes message = 2; // message to encrypt
|
|
||||||
optional bool display_only = 3; // show just on display? (don't send back via wire)
|
|
||||||
repeated uint32 address_n = 4; // BIP-32 path to derive the signing key from master node
|
|
||||||
optional string coin_name = 5 [default='Bitcoin']; // coin to use for signing
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Encrypted message
|
|
||||||
* @prev EncryptMessage
|
|
||||||
*/
|
|
||||||
message EncryptedMessage {
|
|
||||||
optional bytes nonce = 1; // nonce used during encryption
|
|
||||||
optional bytes message = 2; // encrypted message
|
|
||||||
optional bytes hmac = 3; // message hmac
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to decrypt message
|
|
||||||
* @next Success
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message DecryptMessage {
|
|
||||||
repeated uint32 address_n = 1; // BIP-32 path to derive the decryption key from master node
|
|
||||||
optional bytes nonce = 2; // nonce used during encryption
|
|
||||||
optional bytes message = 3; // message to decrypt
|
|
||||||
optional bytes hmac = 4; // message hmac
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Decrypted message
|
|
||||||
* @prev DecryptedMessage
|
|
||||||
*/
|
|
||||||
message DecryptedMessage {
|
|
||||||
optional bytes message = 1; // decrypted message
|
|
||||||
optional string address = 2; // address used to sign the message (if used)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to encrypt or decrypt value of given key
|
|
||||||
* @next CipheredKeyValue
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message CipherKeyValue {
|
|
||||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
|
||||||
optional string key = 2; // key component of key:value
|
|
||||||
optional bytes value = 3; // value component of key:value
|
|
||||||
optional bool encrypt = 4; // are we encrypting (True) or decrypting (False)?
|
|
||||||
optional bool ask_on_encrypt = 5; // should we ask on encrypt operation?
|
|
||||||
optional bool ask_on_decrypt = 6; // should we ask on decrypt operation?
|
|
||||||
optional bytes iv = 7; // initialization vector (will be computed if not set)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Return ciphered/deciphered value
|
|
||||||
* @prev CipherKeyValue
|
|
||||||
*/
|
|
||||||
message CipheredKeyValue {
|
|
||||||
optional bytes value = 1; // ciphered/deciphered value
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////
|
|
||||||
// Transaction signing messages //
|
|
||||||
//////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Estimated size of the transaction
|
|
||||||
* This behaves exactly like SignTx, which means that it can ask using TxRequest
|
|
||||||
* This call is non-blocking (except possible PassphraseRequest to unlock the seed)
|
|
||||||
* @next TxSize
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message EstimateTxSize {
|
|
||||||
required uint32 outputs_count = 1; // number of transaction outputs
|
|
||||||
required uint32 inputs_count = 2; // number of transaction inputs
|
|
||||||
optional string coin_name = 3 [default='Bitcoin']; // coin to use
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Estimated size of the transaction
|
|
||||||
* @prev EstimateTxSize
|
|
||||||
*/
|
|
||||||
message TxSize {
|
|
||||||
optional uint32 tx_size = 1; // estimated size of transaction in bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to sign transaction
|
|
||||||
* @next PassphraseRequest
|
|
||||||
* @next PinMatrixRequest
|
|
||||||
* @next TxRequest
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message SignTx {
|
|
||||||
required uint32 outputs_count = 1; // number of transaction outputs
|
|
||||||
required uint32 inputs_count = 2; // number of transaction inputs
|
|
||||||
optional string coin_name = 3 [default='Bitcoin']; // coin to use
|
|
||||||
optional uint32 version = 4 [default=1]; // transaction version
|
|
||||||
optional uint32 lock_time = 5 [default=0]; // transaction lock_time
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Simplified transaction signing
|
|
||||||
* This method doesn't support streaming, so there are hardware limits in number of inputs and outputs.
|
|
||||||
* In case of success, the result is returned using TxRequest message.
|
|
||||||
* @next PassphraseRequest
|
|
||||||
* @next PinMatrixRequest
|
|
||||||
* @next TxRequest
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message SimpleSignTx {
|
|
||||||
repeated TxInputType inputs = 1; // transaction inputs
|
|
||||||
repeated TxOutputType outputs = 2; // transaction outputs
|
|
||||||
repeated TransactionType transactions = 3; // transactions whose outputs are used to build current inputs
|
|
||||||
optional string coin_name = 4 [default='Bitcoin']; // coin to use
|
|
||||||
optional uint32 version = 5 [default=1]; // transaction version
|
|
||||||
optional uint32 lock_time = 6 [default=0]; // transaction lock_time
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Device asks for information for signing transaction or returns the last result
|
|
||||||
* If request_index is set, device awaits TxAck message (with fields filled in according to request_type)
|
|
||||||
* If signature_index is set, 'signature' contains signed input of signature_index's input
|
|
||||||
* @prev SignTx
|
|
||||||
* @prev SimpleSignTx
|
|
||||||
* @prev TxAck
|
|
||||||
*/
|
|
||||||
message TxRequest {
|
|
||||||
optional RequestType request_type = 1; // what should be filled in TxAck message?
|
|
||||||
optional TxRequestDetailsType details = 2; // request for tx details
|
|
||||||
optional TxRequestSerializedType serialized = 3; // serialized data and request for next
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Reported transaction data
|
|
||||||
* @prev TxRequest
|
|
||||||
* @next TxRequest
|
|
||||||
*/
|
|
||||||
message TxAck {
|
|
||||||
optional TransactionType tx = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to sign transaction
|
|
||||||
* All fields are optional from the protocol's point of view. Each field defaults to value `0` if missing.
|
|
||||||
* Note: the first at most 1024 bytes of data MUST be transmitted as part of this message.
|
|
||||||
* @next PassphraseRequest
|
|
||||||
* @next PinMatrixRequest
|
|
||||||
* @next EthereumTxRequest
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message EthereumSignTx {
|
|
||||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
|
||||||
optional bytes nonce = 2; // <=256 bit unsigned big endian
|
|
||||||
optional bytes gas_price = 3; // <=256 bit unsigned big endian (in wei)
|
|
||||||
optional bytes gas_limit = 4; // <=256 bit unsigned big endian
|
|
||||||
optional bytes to = 5; // 160 bit address hash
|
|
||||||
optional bytes value = 6; // <=256 bit unsigned big endian (in wei)
|
|
||||||
optional bytes data_initial_chunk = 7; // The initial data chunk (<= 1024 bytes)
|
|
||||||
optional uint32 data_length = 8; // Length of transaction payload
|
|
||||||
optional uint32 chain_id = 9; // Chain Id for EIP 155
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Device asks for more data from transaction payload, or returns the signature.
|
|
||||||
* If data_length is set, device awaits that many more bytes of payload.
|
|
||||||
* Otherwise, the signature_* fields contain the computed transaction signature. All three fields will be present.
|
|
||||||
* @prev EthereumSignTx
|
|
||||||
* @next EthereumTxAck
|
|
||||||
*/
|
|
||||||
message EthereumTxRequest {
|
|
||||||
optional uint32 data_length = 1; // Number of bytes being requested (<= 1024)
|
|
||||||
optional uint32 signature_v = 2; // Computed signature (recovery parameter, limited to 27 or 28)
|
|
||||||
optional bytes signature_r = 3; // Computed signature R component (256 bit)
|
|
||||||
optional bytes signature_s = 4; // Computed signature S component (256 bit)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Transaction payload data.
|
|
||||||
* @prev EthereumTxRequest
|
|
||||||
* @next EthereumTxRequest
|
|
||||||
*/
|
|
||||||
message EthereumTxAck {
|
|
||||||
optional bytes data_chunk = 1; // Bytes from transaction payload (<= 1024 bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////
|
|
||||||
// Ethereum: Message signing messages //
|
|
||||||
////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to sign message
|
|
||||||
* @next EthereumMessageSignature
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message EthereumSignMessage {
|
|
||||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
|
||||||
required bytes message = 2; // message to be signed
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to verify message
|
|
||||||
* @next Success
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message EthereumVerifyMessage {
|
|
||||||
optional bytes address = 1; // address to verify
|
|
||||||
optional bytes signature = 2; // signature to verify
|
|
||||||
optional bytes message = 3; // message to verify
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Signed message
|
|
||||||
* @prev EthereumSignMessage
|
|
||||||
*/
|
|
||||||
message EthereumMessageSignature {
|
|
||||||
optional bytes address = 1; // address used to sign the message
|
|
||||||
optional bytes signature = 2; // signature of the message
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////
|
|
||||||
// Identity messages //
|
|
||||||
///////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to sign identity
|
|
||||||
* @next SignedIdentity
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message SignIdentity {
|
|
||||||
optional IdentityType identity = 1; // identity
|
|
||||||
optional bytes challenge_hidden = 2; // non-visible challenge
|
|
||||||
optional string challenge_visual = 3; // challenge shown on display (e.g. date+time)
|
|
||||||
optional string ecdsa_curve_name = 4; // ECDSA curve name to use
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Device provides signed identity
|
|
||||||
* @prev SignIdentity
|
|
||||||
*/
|
|
||||||
message SignedIdentity {
|
|
||||||
optional string address = 1; // identity address
|
|
||||||
optional bytes public_key = 2; // identity public key
|
|
||||||
optional bytes signature = 3; // signature of the identity data
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////
|
|
||||||
// ECDH messages //
|
|
||||||
///////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to generate ECDH session key
|
|
||||||
* @next ECDHSessionKey
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message GetECDHSessionKey {
|
|
||||||
optional IdentityType identity = 1; // identity
|
|
||||||
optional bytes peer_public_key = 2; // peer's public key
|
|
||||||
optional string ecdsa_curve_name = 3; // ECDSA curve name to use
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Device provides ECDH session key
|
|
||||||
* @prev GetECDHSessionKey
|
|
||||||
*/
|
|
||||||
message ECDHSessionKey {
|
|
||||||
optional bytes session_key = 1; // ECDH session key
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////
|
|
||||||
// U2F messages //
|
|
||||||
///////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Set U2F counter
|
|
||||||
* @next Success
|
|
||||||
*/
|
|
||||||
message SetU2FCounter {
|
|
||||||
optional uint32 u2f_counter = 1; // counter
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////
|
|
||||||
// Bootloader messages //
|
|
||||||
/////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to erase its firmware (so it can be replaced via FirmwareUpload)
|
|
||||||
* @next Success
|
|
||||||
* @next FirmwareRequest
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message FirmwareErase {
|
|
||||||
optional uint32 length = 1; // length of new firmware
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Ask for firmware chunk
|
|
||||||
* @next FirmwareUpload
|
|
||||||
*/
|
|
||||||
message FirmwareRequest {
|
|
||||||
optional uint32 offset = 1; // offset of requested firmware chunk
|
|
||||||
optional uint32 length = 2; // length of requested firmware chunk
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Send firmware in binary form to the device
|
|
||||||
* @next Success
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message FirmwareUpload {
|
|
||||||
required bytes payload = 1; // firmware to be loaded into device
|
|
||||||
optional bytes hash = 2; // hash of the payload
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Perform a device self-test
|
|
||||||
* @next Success
|
|
||||||
* @next Failure
|
|
||||||
*/
|
|
||||||
message SelfTest {
|
|
||||||
optional bytes payload = 1; // payload to be used in self-test
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////
|
|
||||||
// Debug messages (only available if DebugLink is enabled) //
|
|
||||||
/////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: "Press" the button on the device
|
|
||||||
* @next Success
|
|
||||||
*/
|
|
||||||
message DebugLinkDecision {
|
|
||||||
required bool yes_no = 1; // true for "Confirm", false for "Cancel"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Computer asks for device state
|
|
||||||
* @next DebugLinkState
|
|
||||||
*/
|
|
||||||
message DebugLinkGetState {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Device current state
|
|
||||||
* @prev DebugLinkGetState
|
|
||||||
*/
|
|
||||||
message DebugLinkState {
|
|
||||||
optional bytes layout = 1; // raw buffer of display
|
|
||||||
optional string pin = 2; // current PIN, blank if PIN is not set/enabled
|
|
||||||
optional string matrix = 3; // current PIN matrix
|
|
||||||
optional string mnemonic = 4; // current BIP-39 mnemonic
|
|
||||||
optional HDNodeType node = 5; // current BIP-32 node
|
|
||||||
optional bool passphrase_protection = 6; // is node/mnemonic encrypted using passphrase?
|
|
||||||
optional string reset_word = 7; // word on device display during ResetDevice workflow
|
|
||||||
optional bytes reset_entropy = 8; // current entropy during ResetDevice workflow
|
|
||||||
optional string recovery_fake_word = 9; // (fake) word on display during RecoveryDevice workflow
|
|
||||||
optional uint32 recovery_word_pos = 10; // index of mnemonic word the device is expecting during RecoveryDevice workflow
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Ask device to restart
|
|
||||||
*/
|
|
||||||
message DebugLinkStop {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Device wants host to log event
|
|
||||||
*/
|
|
||||||
message DebugLinkLog {
|
|
||||||
optional uint32 level = 1;
|
|
||||||
optional string bucket = 2;
|
|
||||||
optional string text = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Read memory from device
|
|
||||||
* @next DebugLinkMemory
|
|
||||||
*/
|
|
||||||
message DebugLinkMemoryRead {
|
|
||||||
optional uint32 address = 1;
|
|
||||||
optional uint32 length = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Device sends memory back
|
|
||||||
* @prev DebugLinkMemoryRead
|
|
||||||
*/
|
|
||||||
message DebugLinkMemory {
|
|
||||||
optional bytes memory = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Write memory to device.
|
|
||||||
* WARNING: Writing to the wrong location can irreparably break the device.
|
|
||||||
*/
|
|
||||||
message DebugLinkMemoryWrite {
|
|
||||||
optional uint32 address = 1;
|
|
||||||
optional bytes memory = 2;
|
|
||||||
optional bool flash = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Erase block of flash on device
|
|
||||||
* WARNING: Writing to the wrong location can irreparably break the device.
|
|
||||||
*/
|
|
||||||
message DebugLinkFlashErase {
|
|
||||||
optional uint32 sector = 1;
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
// Copyright 2017 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// This file contains the implementation for interacting with the Trezor hardware
|
|
||||||
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
|
||||||
// https://doc.satoshilabs.com/trezor-tech/api-protobuf.html
|
|
||||||
|
|
||||||
//go:generate protoc --go_out=import_path=trezor:. types.proto messages.proto
|
|
||||||
|
|
||||||
// Package trezor contains the wire protocol wrapper in Go.
|
|
||||||
package trezor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Type returns the protocol buffer type number of a specific message. If the
|
|
||||||
// message is nil, this method panics!
|
|
||||||
func Type(msg proto.Message) uint16 {
|
|
||||||
return uint16(MessageType_value["MessageType_"+reflect.TypeOf(msg).Elem().Name()])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the friendly message type name of a specific protocol buffer
|
|
||||||
// type numbers.
|
|
||||||
func Name(kind uint16) string {
|
|
||||||
name := MessageType_name[int32(kind)]
|
|
||||||
if len(name) < 12 {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
return name[12:]
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,278 +0,0 @@
|
||||||
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
|
||||||
// https://github.com/trezor/trezor-common/blob/master/protob/types.proto
|
|
||||||
// dated 28.07.2017, commit dd8ec3231fb5f7992360aff9bdfe30bb58130f4b.
|
|
||||||
|
|
||||||
syntax = "proto2";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Types for TREZOR communication
|
|
||||||
*
|
|
||||||
* @author Marek Palatinus <slush@satoshilabs.com>
|
|
||||||
* @version 1.2
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Sugar for easier handling in Java
|
|
||||||
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
|
||||||
option java_outer_classname = "TrezorType";
|
|
||||||
|
|
||||||
import "google/protobuf/descriptor.proto";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for specifying message direction and type of wire (normal/debug)
|
|
||||||
*/
|
|
||||||
extend google.protobuf.EnumValueOptions {
|
|
||||||
optional bool wire_in = 50002; // message can be transmitted via wire from PC to TREZOR
|
|
||||||
optional bool wire_out = 50003; // message can be transmitted via wire from TREZOR to PC
|
|
||||||
optional bool wire_debug_in = 50004; // message can be transmitted via debug wire from PC to TREZOR
|
|
||||||
optional bool wire_debug_out = 50005; // message can be transmitted via debug wire from TREZOR to PC
|
|
||||||
optional bool wire_tiny = 50006; // message is handled by TREZOR when the USB stack is in tiny mode
|
|
||||||
optional bool wire_bootloader = 50007; // message is only handled by TREZOR Bootloader
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of failures returned by Failure message
|
|
||||||
* @used_in Failure
|
|
||||||
*/
|
|
||||||
enum FailureType {
|
|
||||||
Failure_UnexpectedMessage = 1;
|
|
||||||
Failure_ButtonExpected = 2;
|
|
||||||
Failure_DataError = 3;
|
|
||||||
Failure_ActionCancelled = 4;
|
|
||||||
Failure_PinExpected = 5;
|
|
||||||
Failure_PinCancelled = 6;
|
|
||||||
Failure_PinInvalid = 7;
|
|
||||||
Failure_InvalidSignature = 8;
|
|
||||||
Failure_ProcessError = 9;
|
|
||||||
Failure_NotEnoughFunds = 10;
|
|
||||||
Failure_NotInitialized = 11;
|
|
||||||
Failure_FirmwareError = 99;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of script which will be used for transaction output
|
|
||||||
* @used_in TxOutputType
|
|
||||||
*/
|
|
||||||
enum OutputScriptType {
|
|
||||||
PAYTOADDRESS = 0; // used for all addresses (bitcoin, p2sh, witness)
|
|
||||||
PAYTOSCRIPTHASH = 1; // p2sh address (deprecated; use PAYTOADDRESS)
|
|
||||||
PAYTOMULTISIG = 2; // only for change output
|
|
||||||
PAYTOOPRETURN = 3; // op_return
|
|
||||||
PAYTOWITNESS = 4; // only for change output
|
|
||||||
PAYTOP2SHWITNESS = 5; // only for change output
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of script which will be used for transaction output
|
|
||||||
* @used_in TxInputType
|
|
||||||
*/
|
|
||||||
enum InputScriptType {
|
|
||||||
SPENDADDRESS = 0; // standard p2pkh address
|
|
||||||
SPENDMULTISIG = 1; // p2sh multisig address
|
|
||||||
EXTERNAL = 2; // reserved for external inputs (coinjoin)
|
|
||||||
SPENDWITNESS = 3; // native segwit
|
|
||||||
SPENDP2SHWITNESS = 4; // segwit over p2sh (backward compatible)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of information required by transaction signing process
|
|
||||||
* @used_in TxRequest
|
|
||||||
*/
|
|
||||||
enum RequestType {
|
|
||||||
TXINPUT = 0;
|
|
||||||
TXOUTPUT = 1;
|
|
||||||
TXMETA = 2;
|
|
||||||
TXFINISHED = 3;
|
|
||||||
TXEXTRADATA = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of button request
|
|
||||||
* @used_in ButtonRequest
|
|
||||||
*/
|
|
||||||
enum ButtonRequestType {
|
|
||||||
ButtonRequest_Other = 1;
|
|
||||||
ButtonRequest_FeeOverThreshold = 2;
|
|
||||||
ButtonRequest_ConfirmOutput = 3;
|
|
||||||
ButtonRequest_ResetDevice = 4;
|
|
||||||
ButtonRequest_ConfirmWord = 5;
|
|
||||||
ButtonRequest_WipeDevice = 6;
|
|
||||||
ButtonRequest_ProtectCall = 7;
|
|
||||||
ButtonRequest_SignTx = 8;
|
|
||||||
ButtonRequest_FirmwareCheck = 9;
|
|
||||||
ButtonRequest_Address = 10;
|
|
||||||
ButtonRequest_PublicKey = 11;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of PIN request
|
|
||||||
* @used_in PinMatrixRequest
|
|
||||||
*/
|
|
||||||
enum PinMatrixRequestType {
|
|
||||||
PinMatrixRequestType_Current = 1;
|
|
||||||
PinMatrixRequestType_NewFirst = 2;
|
|
||||||
PinMatrixRequestType_NewSecond = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of recovery procedure. These should be used as bitmask, e.g.,
|
|
||||||
* `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix`
|
|
||||||
* listing every method supported by the host computer.
|
|
||||||
*
|
|
||||||
* Note that ScrambledWords must be supported by every implementation
|
|
||||||
* for backward compatibility; there is no way to not support it.
|
|
||||||
*
|
|
||||||
* @used_in RecoveryDevice
|
|
||||||
*/
|
|
||||||
enum RecoveryDeviceType {
|
|
||||||
// use powers of two when extending this field
|
|
||||||
RecoveryDeviceType_ScrambledWords = 0; // words in scrambled order
|
|
||||||
RecoveryDeviceType_Matrix = 1; // matrix recovery type
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of Recovery Word request
|
|
||||||
* @used_in WordRequest
|
|
||||||
*/
|
|
||||||
enum WordRequestType {
|
|
||||||
WordRequestType_Plain = 0;
|
|
||||||
WordRequestType_Matrix9 = 1;
|
|
||||||
WordRequestType_Matrix6 = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure representing BIP32 (hierarchical deterministic) node
|
|
||||||
* Used for imports of private key into the device and exporting public key out of device
|
|
||||||
* @used_in PublicKey
|
|
||||||
* @used_in LoadDevice
|
|
||||||
* @used_in DebugLinkState
|
|
||||||
* @used_in Storage
|
|
||||||
*/
|
|
||||||
message HDNodeType {
|
|
||||||
required uint32 depth = 1;
|
|
||||||
required uint32 fingerprint = 2;
|
|
||||||
required uint32 child_num = 3;
|
|
||||||
required bytes chain_code = 4;
|
|
||||||
optional bytes private_key = 5;
|
|
||||||
optional bytes public_key = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message HDNodePathType {
|
|
||||||
required HDNodeType node = 1; // BIP-32 node in deserialized form
|
|
||||||
repeated uint32 address_n = 2; // BIP-32 path to derive the key from node
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure representing Coin
|
|
||||||
* @used_in Features
|
|
||||||
*/
|
|
||||||
message CoinType {
|
|
||||||
optional string coin_name = 1;
|
|
||||||
optional string coin_shortcut = 2;
|
|
||||||
optional uint32 address_type = 3 [default=0];
|
|
||||||
optional uint64 maxfee_kb = 4;
|
|
||||||
optional uint32 address_type_p2sh = 5 [default=5];
|
|
||||||
optional string signed_message_header = 8;
|
|
||||||
optional uint32 xpub_magic = 9 [default=76067358]; // default=0x0488b21e
|
|
||||||
optional uint32 xprv_magic = 10 [default=76066276]; // default=0x0488ade4
|
|
||||||
optional bool segwit = 11;
|
|
||||||
optional uint32 forkid = 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of redeem script used in input
|
|
||||||
* @used_in TxInputType
|
|
||||||
*/
|
|
||||||
message MultisigRedeemScriptType {
|
|
||||||
repeated HDNodePathType pubkeys = 1; // pubkeys from multisig address (sorted lexicographically)
|
|
||||||
repeated bytes signatures = 2; // existing signatures for partially signed input
|
|
||||||
optional uint32 m = 3; // "m" from n, how many valid signatures is necessary for spending
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure representing transaction input
|
|
||||||
* @used_in SimpleSignTx
|
|
||||||
* @used_in TransactionType
|
|
||||||
*/
|
|
||||||
message TxInputType {
|
|
||||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
|
||||||
required bytes prev_hash = 2; // hash of previous transaction output to spend by this input
|
|
||||||
required uint32 prev_index = 3; // index of previous output to spend
|
|
||||||
optional bytes script_sig = 4; // script signature, unset for tx to sign
|
|
||||||
optional uint32 sequence = 5 [default=4294967295]; // sequence (default=0xffffffff)
|
|
||||||
optional InputScriptType script_type = 6 [default=SPENDADDRESS]; // defines template of input script
|
|
||||||
optional MultisigRedeemScriptType multisig = 7; // Filled if input is going to spend multisig tx
|
|
||||||
optional uint64 amount = 8; // amount of previous transaction output (for segwit only)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure representing transaction output
|
|
||||||
* @used_in SimpleSignTx
|
|
||||||
* @used_in TransactionType
|
|
||||||
*/
|
|
||||||
message TxOutputType {
|
|
||||||
optional string address = 1; // target coin address in Base58 encoding
|
|
||||||
repeated uint32 address_n = 2; // BIP-32 path to derive the key from master node; has higher priority than "address"
|
|
||||||
required uint64 amount = 3; // amount to spend in satoshis
|
|
||||||
required OutputScriptType script_type = 4; // output script type
|
|
||||||
optional MultisigRedeemScriptType multisig = 5; // defines multisig address; script_type must be PAYTOMULTISIG
|
|
||||||
optional bytes op_return_data = 6; // defines op_return data; script_type must be PAYTOOPRETURN, amount must be 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure representing compiled transaction output
|
|
||||||
* @used_in TransactionType
|
|
||||||
*/
|
|
||||||
message TxOutputBinType {
|
|
||||||
required uint64 amount = 1;
|
|
||||||
required bytes script_pubkey = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure representing transaction
|
|
||||||
* @used_in SimpleSignTx
|
|
||||||
*/
|
|
||||||
message TransactionType {
|
|
||||||
optional uint32 version = 1;
|
|
||||||
repeated TxInputType inputs = 2;
|
|
||||||
repeated TxOutputBinType bin_outputs = 3;
|
|
||||||
repeated TxOutputType outputs = 5;
|
|
||||||
optional uint32 lock_time = 4;
|
|
||||||
optional uint32 inputs_cnt = 6;
|
|
||||||
optional uint32 outputs_cnt = 7;
|
|
||||||
optional bytes extra_data = 8;
|
|
||||||
optional uint32 extra_data_len = 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure representing request details
|
|
||||||
* @used_in TxRequest
|
|
||||||
*/
|
|
||||||
message TxRequestDetailsType {
|
|
||||||
optional uint32 request_index = 1; // device expects TxAck message from the computer
|
|
||||||
optional bytes tx_hash = 2; // tx_hash of requested transaction
|
|
||||||
optional uint32 extra_data_len = 3; // length of requested extra data
|
|
||||||
optional uint32 extra_data_offset = 4; // offset of requested extra data
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure representing serialized data
|
|
||||||
* @used_in TxRequest
|
|
||||||
*/
|
|
||||||
message TxRequestSerializedType {
|
|
||||||
optional uint32 signature_index = 1; // 'signature' field contains signed input of this index
|
|
||||||
optional bytes signature = 2; // signature of the signature_index input
|
|
||||||
optional bytes serialized_tx = 3; // part of serialized and signed transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure representing identity data
|
|
||||||
* @used_in IdentityType
|
|
||||||
*/
|
|
||||||
message IdentityType {
|
|
||||||
optional string proto = 1; // proto part of URI
|
|
||||||
optional string user = 2; // user part of URI
|
|
||||||
optional string host = 3; // host part of URI
|
|
||||||
optional string port = 4; // port part of URI
|
|
||||||
optional string path = 5; // path part of URI
|
|
||||||
optional uint32 index = 6 [default=0]; // identity index
|
|
||||||
}
|
|
||||||
|
|
@ -52,13 +52,15 @@ const (
|
||||||
ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path
|
ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path
|
||||||
ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters
|
ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters
|
||||||
ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration
|
ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration
|
||||||
|
ledgerOpSignTypedMessage ledgerOpcode = 0x0c // Signs an Ethereum message following the EIP 712 specification
|
||||||
|
|
||||||
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
|
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
|
||||||
ledgerP1ConfirmFetchAddress ledgerParam1 = 0x01 // Require a user confirmation before returning the address
|
ledgerP1InitTypedMessageData ledgerParam1 = 0x00 // First chunk of Typed Message data
|
||||||
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
|
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
|
||||||
ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing
|
ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing
|
||||||
ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address
|
ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address
|
||||||
ledgerP2ReturnAddressChainCode ledgerParam2 = 0x01 // Require a user confirmation before returning the address
|
|
||||||
|
ledgerEip155Size int = 3 // Size of the EIP-155 chain_id,r,s in unsigned transactions
|
||||||
)
|
)
|
||||||
|
|
||||||
// errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange
|
// errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange
|
||||||
|
|
@ -165,12 +167,31 @@ func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio
|
||||||
}
|
}
|
||||||
// Ensure the wallet is capable of signing the given transaction
|
// Ensure the wallet is capable of signing the given transaction
|
||||||
if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 {
|
if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 {
|
||||||
return common.Address{}, nil, fmt.Errorf("ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2])
|
//lint:ignore ST1005 brand name displayed on the console
|
||||||
|
return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2])
|
||||||
}
|
}
|
||||||
// All infos gathered and metadata checks out, request signing
|
// All infos gathered and metadata checks out, request signing
|
||||||
return w.ledgerSign(path, tx, chainID)
|
return w.ledgerSign(path, tx, chainID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignTypedMessage implements usbwallet.driver, sending the message to the Ledger and
|
||||||
|
// waiting for the user to sign or deny the transaction.
|
||||||
|
//
|
||||||
|
// Note: this was introduced in the ledger 1.5.0 firmware
|
||||||
|
func (w *ledgerDriver) SignTypedMessage(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) {
|
||||||
|
// If the Ethereum app doesn't run, abort
|
||||||
|
if w.offline() {
|
||||||
|
return nil, accounts.ErrWalletClosed
|
||||||
|
}
|
||||||
|
// Ensure the wallet is capable of signing the given transaction
|
||||||
|
if w.version[0] < 1 && w.version[1] < 5 {
|
||||||
|
//lint:ignore ST1005 brand name displayed on the console
|
||||||
|
return nil, fmt.Errorf("Ledger version >= 1.5.0 required for EIP-712 signing (found version v%d.%d.%d)", w.version[0], w.version[1], w.version[2])
|
||||||
|
}
|
||||||
|
// All infos gathered and metadata checks out, request signing
|
||||||
|
return w.ledgerSignTypedMessage(path, domainHash, messageHash)
|
||||||
|
}
|
||||||
|
|
||||||
// ledgerVersion retrieves the current version of the Ethereum wallet app running
|
// ledgerVersion retrieves the current version of the Ethereum wallet app running
|
||||||
// on the Ledger wallet.
|
// on the Ledger wallet.
|
||||||
//
|
//
|
||||||
|
|
@ -258,9 +279,11 @@ func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, er
|
||||||
}
|
}
|
||||||
hexstr := reply[1 : 1+int(reply[0])]
|
hexstr := reply[1 : 1+int(reply[0])]
|
||||||
|
|
||||||
// Decode the hex sting into an Ethereum address and return
|
// Decode the hex string into an Ethereum address and return
|
||||||
var address common.Address
|
var address common.Address
|
||||||
hex.Decode(address[:], hexstr)
|
if _, err = hex.Decode(address[:], hexstr); err != nil {
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
return address, nil
|
return address, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -305,7 +328,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
|
||||||
for i, component := range derivationPath {
|
for i, component := range derivationPath {
|
||||||
binary.BigEndian.PutUint32(path[1+4*i:], component)
|
binary.BigEndian.PutUint32(path[1+4*i:], component)
|
||||||
}
|
}
|
||||||
// Create the transaction RLP based on whether legacy or EIP155 signing was requeste
|
// Create the transaction RLP based on whether legacy or EIP155 signing was requested
|
||||||
var (
|
var (
|
||||||
txrlp []byte
|
txrlp []byte
|
||||||
err error
|
err error
|
||||||
|
|
@ -315,8 +338,22 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
|
||||||
return common.Address{}, nil, err
|
return common.Address{}, nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil {
|
if tx.Type() == types.DynamicFeeTxType {
|
||||||
return common.Address{}, nil, err
|
if txrlp, err = rlp.EncodeToBytes([]interface{}{chainID, tx.Nonce(), tx.GasTipCap(), tx.GasFeeCap(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), tx.AccessList()}); err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
// append type to transaction
|
||||||
|
txrlp = append([]byte{tx.Type()}, txrlp...)
|
||||||
|
} else if tx.Type() == types.AccessListTxType {
|
||||||
|
if txrlp, err = rlp.EncodeToBytes([]interface{}{chainID, tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), tx.AccessList()}); err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
// append type to transaction
|
||||||
|
txrlp = append([]byte{tx.Type()}, txrlp...)
|
||||||
|
} else if tx.Type() == types.LegacyTxType {
|
||||||
|
if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
payload := append(path, txrlp...)
|
payload := append(path, txrlp...)
|
||||||
|
|
@ -326,9 +363,17 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
|
||||||
op = ledgerP1InitTransactionData
|
op = ledgerP1InitTransactionData
|
||||||
reply []byte
|
reply []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Chunk size selection to mitigate an underlying RLP deserialization issue on the ledger app.
|
||||||
|
// https://github.com/LedgerHQ/app-ethereum/issues/409
|
||||||
|
chunk := 255
|
||||||
|
if tx.Type() == types.LegacyTxType {
|
||||||
|
for ; len(payload)%chunk <= ledgerEip155Size; chunk-- {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for len(payload) > 0 {
|
for len(payload) > 0 {
|
||||||
// Calculate the size of the next data chunk
|
// Calculate the size of the next data chunk
|
||||||
chunk := 255
|
|
||||||
if chunk > len(payload) {
|
if chunk > len(payload) {
|
||||||
chunk = len(payload)
|
chunk = len(payload)
|
||||||
}
|
}
|
||||||
|
|
@ -352,8 +397,11 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
|
||||||
if chainID == nil {
|
if chainID == nil {
|
||||||
signer = new(types.HomesteadSigner)
|
signer = new(types.HomesteadSigner)
|
||||||
} else {
|
} else {
|
||||||
signer = types.NewEIP155Signer(chainID)
|
signer = types.LatestSignerForChainID(chainID)
|
||||||
signature[crypto.RecoveryIDOffset] = signature[crypto.RecoveryIDOffset] - byte(chainID.Uint64()*2+35)
|
// For non-legacy transactions, V is 0 or 1, no need to subtract here.
|
||||||
|
if tx.Type() == types.LegacyTxType {
|
||||||
|
signature[64] -= byte(chainID.Uint64()*2 + 35)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
signed, err := tx.WithSignature(signer, signature)
|
signed, err := tx.WithSignature(signer, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -366,6 +414,66 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
|
||||||
return sender, signed, nil
|
return sender, signed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ledgerSignTypedMessage sends the transaction to the Ledger wallet, and waits for the user
|
||||||
|
// to confirm or deny the transaction.
|
||||||
|
//
|
||||||
|
// The signing protocol is defined as follows:
|
||||||
|
//
|
||||||
|
// CLA | INS | P1 | P2 | Lc | Le
|
||||||
|
// ----+-----+----+-----------------------------+-----+---
|
||||||
|
// E0 | 0C | 00 | implementation version : 00 | variable | variable
|
||||||
|
//
|
||||||
|
// Where the input is:
|
||||||
|
//
|
||||||
|
// Description | Length
|
||||||
|
// -------------------------------------------------+----------
|
||||||
|
// Number of BIP 32 derivations to perform (max 10) | 1 byte
|
||||||
|
// First derivation index (big endian) | 4 bytes
|
||||||
|
// ... | 4 bytes
|
||||||
|
// Last derivation index (big endian) | 4 bytes
|
||||||
|
// domain hash | 32 bytes
|
||||||
|
// message hash | 32 bytes
|
||||||
|
//
|
||||||
|
// And the output data is:
|
||||||
|
//
|
||||||
|
// Description | Length
|
||||||
|
// ------------+---------
|
||||||
|
// signature V | 1 byte
|
||||||
|
// signature R | 32 bytes
|
||||||
|
// signature S | 32 bytes
|
||||||
|
func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHash []byte, messageHash []byte) ([]byte, error) {
|
||||||
|
// Flatten the derivation path into the Ledger request
|
||||||
|
path := make([]byte, 1+4*len(derivationPath))
|
||||||
|
path[0] = byte(len(derivationPath))
|
||||||
|
for i, component := range derivationPath {
|
||||||
|
binary.BigEndian.PutUint32(path[1+4*i:], component)
|
||||||
|
}
|
||||||
|
// Create the 712 message
|
||||||
|
payload := append(path, domainHash...)
|
||||||
|
payload = append(payload, messageHash...)
|
||||||
|
|
||||||
|
// Send the request and wait for the response
|
||||||
|
var (
|
||||||
|
op = ledgerP1InitTypedMessageData
|
||||||
|
reply []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Send the message over, ensuring it's processed correctly
|
||||||
|
reply, err = w.ledgerExchange(ledgerOpSignTypedMessage, op, 0, payload)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the Ethereum signature and do a sanity validation
|
||||||
|
if len(reply) != crypto.SignatureLength {
|
||||||
|
return nil, errors.New("reply lacks signature")
|
||||||
|
}
|
||||||
|
signature := append(reply[1:], reply[0])
|
||||||
|
return signature, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ledgerExchange performs a data exchange with the Ledger wallet, sending it a
|
// ledgerExchange performs a data exchange with the Ledger wallet, sending it a
|
||||||
// message and retrieving the response.
|
// message and retrieving the response.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,13 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/usbwallet/internal/trezor"
|
"github.com/XinFinOrg/XDPoSChain/accounts/usbwallet/trezor"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
|
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
"github.com/XinFinOrg/XDPoSChain/log"
|
"github.com/XinFinOrg/XDPoSChain/log"
|
||||||
"github.com/golang/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In
|
// ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In
|
||||||
|
|
@ -42,6 +42,9 @@ import (
|
||||||
// encoded passphrase.
|
// encoded passphrase.
|
||||||
var ErrTrezorPINNeeded = errors.New("trezor: pin needed")
|
var ErrTrezorPINNeeded = errors.New("trezor: pin needed")
|
||||||
|
|
||||||
|
// ErrTrezorPassphraseNeeded is returned if opening the trezor requires a passphrase
|
||||||
|
var ErrTrezorPassphraseNeeded = errors.New("trezor: passphrase needed")
|
||||||
|
|
||||||
// errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange
|
// errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange
|
||||||
// if the device replies with a mismatching header. This usually means the device
|
// if the device replies with a mismatching header. This usually means the device
|
||||||
// is in browser mode.
|
// is in browser mode.
|
||||||
|
|
@ -49,12 +52,13 @@ var errTrezorReplyInvalidHeader = errors.New("trezor: invalid reply header")
|
||||||
|
|
||||||
// trezorDriver implements the communication with a Trezor hardware wallet.
|
// trezorDriver implements the communication with a Trezor hardware wallet.
|
||||||
type trezorDriver struct {
|
type trezorDriver struct {
|
||||||
device io.ReadWriter // USB device connection to communicate through
|
device io.ReadWriter // USB device connection to communicate through
|
||||||
version [3]uint32 // Current version of the Trezor firmware
|
version [3]uint32 // Current version of the Trezor firmware
|
||||||
label string // Current textual label of the Trezor device
|
label string // Current textual label of the Trezor device
|
||||||
pinwait bool // Flags whether the device is waiting for PIN entry
|
pinwait bool // Flags whether the device is waiting for PIN entry
|
||||||
failure error // Any failure that would make the device unusable
|
passphrasewait bool // Flags whether the device is waiting for passphrase entry
|
||||||
log log.Logger // Contextual logger to tag the trezor with its id
|
failure error // Any failure that would make the device unusable
|
||||||
|
log log.Logger // Contextual logger to tag the trezor with its id
|
||||||
}
|
}
|
||||||
|
|
||||||
// newTrezorDriver creates a new instance of a Trezor USB protocol driver.
|
// newTrezorDriver creates a new instance of a Trezor USB protocol driver.
|
||||||
|
|
@ -80,19 +84,21 @@ func (w *trezorDriver) Status() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open implements usbwallet.driver, attempting to initialize the connection to
|
// Open implements usbwallet.driver, attempting to initialize the connection to
|
||||||
// the Trezor hardware wallet. Initializing the Trezor is a two phase operation:
|
// the Trezor hardware wallet. Initializing the Trezor is a two or three phase operation:
|
||||||
// - The first phase is to initialize the connection and read the wallet's
|
// - The first phase is to initialize the connection and read the wallet's
|
||||||
// features. This phase is invoked is the provided passphrase is empty. The
|
// features. This phase is invoked if the provided passphrase is empty. The
|
||||||
// device will display the pinpad as a result and will return an appropriate
|
// device will display the pinpad as a result and will return an appropriate
|
||||||
// error to notify the user that a second open phase is needed.
|
// error to notify the user that a second open phase is needed.
|
||||||
// - The second phase is to unlock access to the Trezor, which is done by the
|
// - The second phase is to unlock access to the Trezor, which is done by the
|
||||||
// user actually providing a passphrase mapping a keyboard keypad to the pin
|
// user actually providing a passphrase mapping a keyboard keypad to the pin
|
||||||
// number of the user (shuffled according to the pinpad displayed).
|
// number of the user (shuffled according to the pinpad displayed).
|
||||||
|
// - If needed the device will ask for passphrase which will require calling
|
||||||
|
// open again with the actual passphrase (3rd phase)
|
||||||
func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
|
func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
|
||||||
w.device, w.failure = device, nil
|
w.device, w.failure = device, nil
|
||||||
|
|
||||||
// If phase 1 is requested, init the connection and wait for user callback
|
// If phase 1 is requested, init the connection and wait for user callback
|
||||||
if passphrase == "" {
|
if passphrase == "" && !w.passphrasewait {
|
||||||
// If we're already waiting for a PIN entry, insta-return
|
// If we're already waiting for a PIN entry, insta-return
|
||||||
if w.pinwait {
|
if w.pinwait {
|
||||||
return ErrTrezorPINNeeded
|
return ErrTrezorPINNeeded
|
||||||
|
|
@ -105,26 +111,46 @@ func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
|
||||||
w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()}
|
w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()}
|
||||||
w.label = features.GetLabel()
|
w.label = features.GetLabel()
|
||||||
|
|
||||||
// Do a manual ping, forcing the device to ask for its PIN
|
// Do a manual ping, forcing the device to ask for its PIN and Passphrase
|
||||||
askPin := true
|
askPin := true
|
||||||
res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin}, new(trezor.PinMatrixRequest), new(trezor.Success))
|
askPassphrase := true
|
||||||
|
res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin, PassphraseProtection: &askPassphrase}, new(trezor.PinMatrixRequest), new(trezor.PassphraseRequest), new(trezor.Success))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Only return the PIN request if the device wasn't unlocked until now
|
// Only return the PIN request if the device wasn't unlocked until now
|
||||||
if res == 1 {
|
switch res {
|
||||||
return nil // Device responded with trezor.Success
|
case 0:
|
||||||
|
w.pinwait = true
|
||||||
|
return ErrTrezorPINNeeded
|
||||||
|
case 1:
|
||||||
|
w.pinwait = false
|
||||||
|
w.passphrasewait = true
|
||||||
|
return ErrTrezorPassphraseNeeded
|
||||||
|
case 2:
|
||||||
|
return nil // responded with trezor.Success
|
||||||
}
|
}
|
||||||
w.pinwait = true
|
|
||||||
return ErrTrezorPINNeeded
|
|
||||||
}
|
}
|
||||||
// Phase 2 requested with actual PIN entry
|
// Phase 2 requested with actual PIN entry
|
||||||
w.pinwait = false
|
if w.pinwait {
|
||||||
|
w.pinwait = false
|
||||||
if _, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success)); err != nil {
|
res, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success), new(trezor.PassphraseRequest))
|
||||||
w.failure = err
|
if err != nil {
|
||||||
return err
|
w.failure = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if res == 1 {
|
||||||
|
w.passphrasewait = true
|
||||||
|
return ErrTrezorPassphraseNeeded
|
||||||
|
}
|
||||||
|
} else if w.passphrasewait {
|
||||||
|
w.passphrasewait = false
|
||||||
|
if _, err := w.trezorExchange(&trezor.PassphraseAck{Passphrase: &passphrase}, new(trezor.Success)); err != nil {
|
||||||
|
w.failure = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,6 +186,10 @@ func (w *trezorDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio
|
||||||
return w.trezorSign(path, tx, chainID)
|
return w.trezorSign(path, tx, chainID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *trezorDriver) SignTypedMessage(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) {
|
||||||
|
return nil, accounts.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
// trezorDerive sends a derivation request to the Trezor device and returns the
|
// trezorDerive sends a derivation request to the Trezor device and returns the
|
||||||
// Ethereum address located on that path.
|
// Ethereum address located on that path.
|
||||||
func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, error) {
|
func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, error) {
|
||||||
|
|
@ -167,7 +197,13 @@ func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, er
|
||||||
if _, err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil {
|
if _, err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
return common.BytesToAddress(address.GetAddress()), nil
|
if addr := address.GetAddressBin(); len(addr) > 0 { // Older firmwares use binary formats
|
||||||
|
return common.BytesToAddress(addr), nil
|
||||||
|
}
|
||||||
|
if addr := address.GetAddressHex(); len(addr) > 0 { // Newer firmwares use hexadecimal formats
|
||||||
|
return common.HexToAddress(addr), nil
|
||||||
|
}
|
||||||
|
return common.Address{}, errors.New("missing derived address")
|
||||||
}
|
}
|
||||||
|
|
||||||
// trezorSign sends the transaction to the Trezor wallet, and waits for the user
|
// trezorSign sends the transaction to the Trezor wallet, and waits for the user
|
||||||
|
|
@ -186,7 +222,10 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction
|
||||||
DataLength: &length,
|
DataLength: &length,
|
||||||
}
|
}
|
||||||
if to := tx.To(); to != nil {
|
if to := tx.To(); to != nil {
|
||||||
request.To = (*to)[:] // Non contract deploy, set recipient explicitly
|
// Non contract deploy, set recipient explicitly
|
||||||
|
hex := to.Hex()
|
||||||
|
request.ToHex = &hex // Newer firmwares (old will ignore)
|
||||||
|
request.ToBin = (*to)[:] // Older firmwares (new will ignore)
|
||||||
}
|
}
|
||||||
if length > 1024 { // Send the data chunked if that was requested
|
if length > 1024 { // Send the data chunked if that was requested
|
||||||
request.DataInitialChunk, data = data[:1024], data[1024:]
|
request.DataInitialChunk, data = data[:1024], data[1024:]
|
||||||
|
|
@ -223,7 +262,7 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction
|
||||||
} else {
|
} else {
|
||||||
// Trezor backend does not support typed transactions yet.
|
// Trezor backend does not support typed transactions yet.
|
||||||
signer = types.NewEIP155Signer(chainID)
|
signer = types.NewEIP155Signer(chainID)
|
||||||
signature[crypto.RecoveryIDOffset] = signature[crypto.RecoveryIDOffset] - byte(chainID.Uint64()*2+35)
|
signature[crypto.RecoveryIDOffset] -= byte(chainID.Uint64()*2 + 35)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject the final signature into the transaction and sanity check the sender
|
// Inject the final signature into the transaction and sanity check the sender
|
||||||
|
|
|
||||||
1198
accounts/usbwallet/trezor/messages-common.pb.go
Normal file
1198
accounts/usbwallet/trezor/messages-common.pb.go
Normal file
File diff suppressed because it is too large
Load diff
149
accounts/usbwallet/trezor/messages-common.proto
Normal file
149
accounts/usbwallet/trezor/messages-common.proto
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
||||||
|
// https://github.com/trezor/trezor-common/blob/master/protob/messages-common.proto
|
||||||
|
// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9.
|
||||||
|
|
||||||
|
syntax = "proto2";
|
||||||
|
package hw.trezor.messages.common;
|
||||||
|
|
||||||
|
option go_package = "github.com/XinFinOrg/XDPoSChain/accounts/usbwallet/trezor";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Success of the previous request
|
||||||
|
* @end
|
||||||
|
*/
|
||||||
|
message Success {
|
||||||
|
optional string message = 1; // human readable description of action or request-specific payload
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Failure of the previous request
|
||||||
|
* @end
|
||||||
|
*/
|
||||||
|
message Failure {
|
||||||
|
optional FailureType code = 1; // computer-readable definition of the error state
|
||||||
|
optional string message = 2; // human-readable message of the error state
|
||||||
|
enum FailureType {
|
||||||
|
Failure_UnexpectedMessage = 1;
|
||||||
|
Failure_ButtonExpected = 2;
|
||||||
|
Failure_DataError = 3;
|
||||||
|
Failure_ActionCancelled = 4;
|
||||||
|
Failure_PinExpected = 5;
|
||||||
|
Failure_PinCancelled = 6;
|
||||||
|
Failure_PinInvalid = 7;
|
||||||
|
Failure_InvalidSignature = 8;
|
||||||
|
Failure_ProcessError = 9;
|
||||||
|
Failure_NotEnoughFunds = 10;
|
||||||
|
Failure_NotInitialized = 11;
|
||||||
|
Failure_PinMismatch = 12;
|
||||||
|
Failure_FirmwareError = 99;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device is waiting for HW button press.
|
||||||
|
* @auxstart
|
||||||
|
* @next ButtonAck
|
||||||
|
*/
|
||||||
|
message ButtonRequest {
|
||||||
|
optional ButtonRequestType code = 1;
|
||||||
|
optional string data = 2;
|
||||||
|
/**
|
||||||
|
* Type of button request
|
||||||
|
*/
|
||||||
|
enum ButtonRequestType {
|
||||||
|
ButtonRequest_Other = 1;
|
||||||
|
ButtonRequest_FeeOverThreshold = 2;
|
||||||
|
ButtonRequest_ConfirmOutput = 3;
|
||||||
|
ButtonRequest_ResetDevice = 4;
|
||||||
|
ButtonRequest_ConfirmWord = 5;
|
||||||
|
ButtonRequest_WipeDevice = 6;
|
||||||
|
ButtonRequest_ProtectCall = 7;
|
||||||
|
ButtonRequest_SignTx = 8;
|
||||||
|
ButtonRequest_FirmwareCheck = 9;
|
||||||
|
ButtonRequest_Address = 10;
|
||||||
|
ButtonRequest_PublicKey = 11;
|
||||||
|
ButtonRequest_MnemonicWordCount = 12;
|
||||||
|
ButtonRequest_MnemonicInput = 13;
|
||||||
|
ButtonRequest_PassphraseType = 14;
|
||||||
|
ButtonRequest_UnknownDerivationPath = 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Computer agrees to wait for HW button press
|
||||||
|
* @auxend
|
||||||
|
*/
|
||||||
|
message ButtonAck {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device is asking computer to show PIN matrix and awaits PIN encoded using this matrix scheme
|
||||||
|
* @auxstart
|
||||||
|
* @next PinMatrixAck
|
||||||
|
*/
|
||||||
|
message PinMatrixRequest {
|
||||||
|
optional PinMatrixRequestType type = 1;
|
||||||
|
/**
|
||||||
|
* Type of PIN request
|
||||||
|
*/
|
||||||
|
enum PinMatrixRequestType {
|
||||||
|
PinMatrixRequestType_Current = 1;
|
||||||
|
PinMatrixRequestType_NewFirst = 2;
|
||||||
|
PinMatrixRequestType_NewSecond = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Computer responds with encoded PIN
|
||||||
|
* @auxend
|
||||||
|
*/
|
||||||
|
message PinMatrixAck {
|
||||||
|
required string pin = 1; // matrix encoded PIN entered by user
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device awaits encryption passphrase
|
||||||
|
* @auxstart
|
||||||
|
* @next PassphraseAck
|
||||||
|
*/
|
||||||
|
message PassphraseRequest {
|
||||||
|
optional bool on_device = 1; // passphrase is being entered on the device
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Send passphrase back
|
||||||
|
* @next PassphraseStateRequest
|
||||||
|
*/
|
||||||
|
message PassphraseAck {
|
||||||
|
optional string passphrase = 1;
|
||||||
|
optional bytes state = 2; // expected device state
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device awaits passphrase state
|
||||||
|
* @next PassphraseStateAck
|
||||||
|
*/
|
||||||
|
message PassphraseStateRequest {
|
||||||
|
optional bytes state = 1; // actual device state
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Send passphrase state back
|
||||||
|
* @auxend
|
||||||
|
*/
|
||||||
|
message PassphraseStateAck {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure representing BIP32 (hierarchical deterministic) node
|
||||||
|
* Used for imports of private key into the device and exporting public key out of device
|
||||||
|
* @embed
|
||||||
|
*/
|
||||||
|
message HDNodeType {
|
||||||
|
required uint32 depth = 1;
|
||||||
|
required uint32 fingerprint = 2;
|
||||||
|
required uint32 child_num = 3;
|
||||||
|
required bytes chain_code = 4;
|
||||||
|
optional bytes private_key = 5;
|
||||||
|
optional bytes public_key = 6;
|
||||||
|
}
|
||||||
1002
accounts/usbwallet/trezor/messages-ethereum.pb.go
Normal file
1002
accounts/usbwallet/trezor/messages-ethereum.pb.go
Normal file
File diff suppressed because it is too large
Load diff
133
accounts/usbwallet/trezor/messages-ethereum.proto
Normal file
133
accounts/usbwallet/trezor/messages-ethereum.proto
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
||||||
|
// https://github.com/trezor/trezor-common/blob/master/protob/messages-ethereum.proto
|
||||||
|
// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9.
|
||||||
|
|
||||||
|
syntax = "proto2";
|
||||||
|
package hw.trezor.messages.ethereum;
|
||||||
|
|
||||||
|
option go_package = "github.com/XinFinOrg/XDPoSChain/accounts/usbwallet/trezor";
|
||||||
|
|
||||||
|
// Sugar for easier handling in Java
|
||||||
|
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||||
|
option java_outer_classname = "TrezorMessageEthereum";
|
||||||
|
|
||||||
|
import "messages-common.proto";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device for public key corresponding to address_n path
|
||||||
|
* @start
|
||||||
|
* @next EthereumPublicKey
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message EthereumGetPublicKey {
|
||||||
|
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||||
|
optional bool show_display = 2; // optionally show on display before sending the result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Contains public key derived from device private seed
|
||||||
|
* @end
|
||||||
|
*/
|
||||||
|
message EthereumPublicKey {
|
||||||
|
optional hw.trezor.messages.common.HDNodeType node = 1; // BIP32 public node
|
||||||
|
optional string xpub = 2; // serialized form of public node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device for Ethereum address corresponding to address_n path
|
||||||
|
* @start
|
||||||
|
* @next EthereumAddress
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message EthereumGetAddress {
|
||||||
|
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||||
|
optional bool show_display = 2; // optionally show on display before sending the result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Contains an Ethereum address derived from device private seed
|
||||||
|
* @end
|
||||||
|
*/
|
||||||
|
message EthereumAddress {
|
||||||
|
optional bytes addressBin = 1; // Ethereum address as 20 bytes (legacy firmwares)
|
||||||
|
optional string addressHex = 2; // Ethereum address as hex string (newer firmwares)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to sign transaction
|
||||||
|
* All fields are optional from the protocol's point of view. Each field defaults to value `0` if missing.
|
||||||
|
* Note: the first at most 1024 bytes of data MUST be transmitted as part of this message.
|
||||||
|
* @start
|
||||||
|
* @next EthereumTxRequest
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message EthereumSignTx {
|
||||||
|
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||||
|
optional bytes nonce = 2; // <=256 bit unsigned big endian
|
||||||
|
optional bytes gas_price = 3; // <=256 bit unsigned big endian (in wei)
|
||||||
|
optional bytes gas_limit = 4; // <=256 bit unsigned big endian
|
||||||
|
optional bytes toBin = 5; // recipient address (20 bytes, legacy firmware)
|
||||||
|
optional string toHex = 11; // recipient address (hex string, newer firmware)
|
||||||
|
optional bytes value = 6; // <=256 bit unsigned big endian (in wei)
|
||||||
|
optional bytes data_initial_chunk = 7; // The initial data chunk (<= 1024 bytes)
|
||||||
|
optional uint32 data_length = 8; // Length of transaction payload
|
||||||
|
optional uint32 chain_id = 9; // Chain Id for EIP 155
|
||||||
|
optional uint32 tx_type = 10; // (only for Wanchain)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device asks for more data from transaction payload, or returns the signature.
|
||||||
|
* If data_length is set, device awaits that many more bytes of payload.
|
||||||
|
* Otherwise, the signature_* fields contain the computed transaction signature. All three fields will be present.
|
||||||
|
* @end
|
||||||
|
* @next EthereumTxAck
|
||||||
|
*/
|
||||||
|
message EthereumTxRequest {
|
||||||
|
optional uint32 data_length = 1; // Number of bytes being requested (<= 1024)
|
||||||
|
optional uint32 signature_v = 2; // Computed signature (recovery parameter, limited to 27 or 28)
|
||||||
|
optional bytes signature_r = 3; // Computed signature R component (256 bit)
|
||||||
|
optional bytes signature_s = 4; // Computed signature S component (256 bit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Transaction payload data.
|
||||||
|
* @next EthereumTxRequest
|
||||||
|
*/
|
||||||
|
message EthereumTxAck {
|
||||||
|
optional bytes data_chunk = 1; // Bytes from transaction payload (<= 1024 bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to sign message
|
||||||
|
* @start
|
||||||
|
* @next EthereumMessageSignature
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message EthereumSignMessage {
|
||||||
|
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||||
|
optional bytes message = 2; // message to be signed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Signed message
|
||||||
|
* @end
|
||||||
|
*/
|
||||||
|
message EthereumMessageSignature {
|
||||||
|
optional bytes addressBin = 1; // address used to sign the message (20 bytes, legacy firmware)
|
||||||
|
optional bytes signature = 2; // signature of the message
|
||||||
|
optional string addressHex = 3; // address used to sign the message (hex string, newer firmware)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to verify message
|
||||||
|
* @start
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message EthereumVerifyMessage {
|
||||||
|
optional bytes addressBin = 1; // address to verify (20 bytes, legacy firmware)
|
||||||
|
optional bytes signature = 2; // signature to verify
|
||||||
|
optional bytes message = 3; // message to verify
|
||||||
|
optional string addressHex = 4; // address to verify (hex string, newer firmware)
|
||||||
|
}
|
||||||
2276
accounts/usbwallet/trezor/messages-management.pb.go
Normal file
2276
accounts/usbwallet/trezor/messages-management.pb.go
Normal file
File diff suppressed because it is too large
Load diff
291
accounts/usbwallet/trezor/messages-management.proto
Normal file
291
accounts/usbwallet/trezor/messages-management.proto
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
||||||
|
// https://github.com/trezor/trezor-common/blob/master/protob/messages-management.proto
|
||||||
|
// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9.
|
||||||
|
|
||||||
|
syntax = "proto2";
|
||||||
|
package hw.trezor.messages.management;
|
||||||
|
|
||||||
|
option go_package = "github.com/XinFinOrg/XDPoSChain/accounts/usbwallet/trezor";
|
||||||
|
|
||||||
|
// Sugar for easier handling in Java
|
||||||
|
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||||
|
option java_outer_classname = "TrezorMessageManagement";
|
||||||
|
|
||||||
|
import "messages-common.proto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Reset device to default state and ask for device details
|
||||||
|
* @start
|
||||||
|
* @next Features
|
||||||
|
*/
|
||||||
|
message Initialize {
|
||||||
|
optional bytes state = 1; // assumed device state, clear session if set and different
|
||||||
|
optional bool skip_passphrase = 2; // this session should always assume empty passphrase
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask for device details (no device reset)
|
||||||
|
* @start
|
||||||
|
* @next Features
|
||||||
|
*/
|
||||||
|
message GetFeatures {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Reports various information about the device
|
||||||
|
* @end
|
||||||
|
*/
|
||||||
|
message Features {
|
||||||
|
optional string vendor = 1; // name of the manufacturer, e.g. "trezor.io"
|
||||||
|
optional uint32 major_version = 2; // major version of the firmware/bootloader, e.g. 1
|
||||||
|
optional uint32 minor_version = 3; // minor version of the firmware/bootloader, e.g. 0
|
||||||
|
optional uint32 patch_version = 4; // patch version of the firmware/bootloader, e.g. 0
|
||||||
|
optional bool bootloader_mode = 5; // is device in bootloader mode?
|
||||||
|
optional string device_id = 6; // device's unique identifier
|
||||||
|
optional bool pin_protection = 7; // is device protected by PIN?
|
||||||
|
optional bool passphrase_protection = 8; // is node/mnemonic encrypted using passphrase?
|
||||||
|
optional string language = 9; // device language
|
||||||
|
optional string label = 10; // device description label
|
||||||
|
optional bool initialized = 12; // does device contain seed?
|
||||||
|
optional bytes revision = 13; // SCM revision of firmware
|
||||||
|
optional bytes bootloader_hash = 14; // hash of the bootloader
|
||||||
|
optional bool imported = 15; // was storage imported from an external source?
|
||||||
|
optional bool pin_cached = 16; // is PIN already cached in session?
|
||||||
|
optional bool passphrase_cached = 17; // is passphrase already cached in session?
|
||||||
|
optional bool firmware_present = 18; // is valid firmware loaded?
|
||||||
|
optional bool needs_backup = 19; // does storage need backup? (equals to Storage.needs_backup)
|
||||||
|
optional uint32 flags = 20; // device flags (equals to Storage.flags)
|
||||||
|
optional string model = 21; // device hardware model
|
||||||
|
optional uint32 fw_major = 22; // reported firmware version if in bootloader mode
|
||||||
|
optional uint32 fw_minor = 23; // reported firmware version if in bootloader mode
|
||||||
|
optional uint32 fw_patch = 24; // reported firmware version if in bootloader mode
|
||||||
|
optional string fw_vendor = 25; // reported firmware vendor if in bootloader mode
|
||||||
|
optional bytes fw_vendor_keys = 26; // reported firmware vendor keys (their hash)
|
||||||
|
optional bool unfinished_backup = 27; // report unfinished backup (equals to Storage.unfinished_backup)
|
||||||
|
optional bool no_backup = 28; // report no backup (equals to Storage.no_backup)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: clear session (removes cached PIN, passphrase, etc).
|
||||||
|
* @start
|
||||||
|
* @next Success
|
||||||
|
*/
|
||||||
|
message ClearSession {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: change language and/or label of the device
|
||||||
|
* @start
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message ApplySettings {
|
||||||
|
optional string language = 1;
|
||||||
|
optional string label = 2;
|
||||||
|
optional bool use_passphrase = 3;
|
||||||
|
optional bytes homescreen = 4;
|
||||||
|
optional PassphraseSourceType passphrase_source = 5;
|
||||||
|
optional uint32 auto_lock_delay_ms = 6;
|
||||||
|
optional uint32 display_rotation = 7; // in degrees from North
|
||||||
|
/**
|
||||||
|
* Structure representing passphrase source
|
||||||
|
*/
|
||||||
|
enum PassphraseSourceType {
|
||||||
|
ASK = 0;
|
||||||
|
DEVICE = 1;
|
||||||
|
HOST = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: set flags of the device
|
||||||
|
* @start
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message ApplyFlags {
|
||||||
|
optional uint32 flags = 1; // bitmask, can only set bits, not unset
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Starts workflow for setting/changing/removing the PIN
|
||||||
|
* @start
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message ChangePin {
|
||||||
|
optional bool remove = 1; // is PIN removal requested?
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Test if the device is alive, device sends back the message in Success response
|
||||||
|
* @start
|
||||||
|
* @next Success
|
||||||
|
*/
|
||||||
|
message Ping {
|
||||||
|
optional string message = 1; // message to send back in Success message
|
||||||
|
optional bool button_protection = 2; // ask for button press
|
||||||
|
optional bool pin_protection = 3; // ask for PIN if set in device
|
||||||
|
optional bool passphrase_protection = 4; // ask for passphrase if set in device
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Abort last operation that required user interaction
|
||||||
|
* @start
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message Cancel {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Request a sample of random data generated by hardware RNG. May be used for testing.
|
||||||
|
* @start
|
||||||
|
* @next Entropy
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message GetEntropy {
|
||||||
|
required uint32 size = 1; // size of requested entropy
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Reply with random data generated by internal RNG
|
||||||
|
* @end
|
||||||
|
*/
|
||||||
|
message Entropy {
|
||||||
|
required bytes entropy = 1; // chunk of random generated bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Request device to wipe all sensitive data and settings
|
||||||
|
* @start
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message WipeDevice {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Load seed and related internal settings from the computer
|
||||||
|
* @start
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message LoadDevice {
|
||||||
|
optional string mnemonic = 1; // seed encoded as BIP-39 mnemonic (12, 18 or 24 words)
|
||||||
|
optional hw.trezor.messages.common.HDNodeType node = 2; // BIP-32 node
|
||||||
|
optional string pin = 3; // set PIN protection
|
||||||
|
optional bool passphrase_protection = 4; // enable master node encryption using passphrase
|
||||||
|
optional string language = 5 [default='english']; // device language
|
||||||
|
optional string label = 6; // device label
|
||||||
|
optional bool skip_checksum = 7; // do not test mnemonic for valid BIP-39 checksum
|
||||||
|
optional uint32 u2f_counter = 8; // U2F counter
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to do initialization involving user interaction
|
||||||
|
* @start
|
||||||
|
* @next EntropyRequest
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message ResetDevice {
|
||||||
|
optional bool display_random = 1; // display entropy generated by the device before asking for additional entropy
|
||||||
|
optional uint32 strength = 2 [default=256]; // strength of seed in bits
|
||||||
|
optional bool passphrase_protection = 3; // enable master node encryption using passphrase
|
||||||
|
optional bool pin_protection = 4; // enable PIN protection
|
||||||
|
optional string language = 5 [default='english']; // device language
|
||||||
|
optional string label = 6; // device label
|
||||||
|
optional uint32 u2f_counter = 7; // U2F counter
|
||||||
|
optional bool skip_backup = 8; // postpone seed backup to BackupDevice workflow
|
||||||
|
optional bool no_backup = 9; // indicate that no backup is going to be made
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Perform backup of the device seed if not backed up using ResetDevice
|
||||||
|
* @start
|
||||||
|
* @next Success
|
||||||
|
*/
|
||||||
|
message BackupDevice {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Ask for additional entropy from host computer
|
||||||
|
* @next EntropyAck
|
||||||
|
*/
|
||||||
|
message EntropyRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Provide additional entropy for seed generation function
|
||||||
|
* @next Success
|
||||||
|
*/
|
||||||
|
message EntropyAck {
|
||||||
|
optional bytes entropy = 1; // 256 bits (32 bytes) of random data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Start recovery workflow asking user for specific words of mnemonic
|
||||||
|
* Used to recovery device safely even on untrusted computer.
|
||||||
|
* @start
|
||||||
|
* @next WordRequest
|
||||||
|
*/
|
||||||
|
message RecoveryDevice {
|
||||||
|
optional uint32 word_count = 1; // number of words in BIP-39 mnemonic
|
||||||
|
optional bool passphrase_protection = 2; // enable master node encryption using passphrase
|
||||||
|
optional bool pin_protection = 3; // enable PIN protection
|
||||||
|
optional string language = 4 [default='english']; // device language
|
||||||
|
optional string label = 5; // device label
|
||||||
|
optional bool enforce_wordlist = 6; // enforce BIP-39 wordlist during the process
|
||||||
|
// 7 reserved for unused recovery method
|
||||||
|
optional RecoveryDeviceType type = 8; // supported recovery type
|
||||||
|
optional uint32 u2f_counter = 9; // U2F counter
|
||||||
|
optional bool dry_run = 10; // perform dry-run recovery workflow (for safe mnemonic validation)
|
||||||
|
/**
|
||||||
|
* Type of recovery procedure. These should be used as bitmask, e.g.,
|
||||||
|
* `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix`
|
||||||
|
* listing every method supported by the host computer.
|
||||||
|
*
|
||||||
|
* Note that ScrambledWords must be supported by every implementation
|
||||||
|
* for backward compatibility; there is no way to not support it.
|
||||||
|
*/
|
||||||
|
enum RecoveryDeviceType {
|
||||||
|
// use powers of two when extending this field
|
||||||
|
RecoveryDeviceType_ScrambledWords = 0; // words in scrambled order
|
||||||
|
RecoveryDeviceType_Matrix = 1; // matrix recovery type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device is waiting for user to enter word of the mnemonic
|
||||||
|
* Its position is shown only on device's internal display.
|
||||||
|
* @next WordAck
|
||||||
|
*/
|
||||||
|
message WordRequest {
|
||||||
|
optional WordRequestType type = 1;
|
||||||
|
/**
|
||||||
|
* Type of Recovery Word request
|
||||||
|
*/
|
||||||
|
enum WordRequestType {
|
||||||
|
WordRequestType_Plain = 0;
|
||||||
|
WordRequestType_Matrix9 = 1;
|
||||||
|
WordRequestType_Matrix6 = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Computer replies with word from the mnemonic
|
||||||
|
* @next WordRequest
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message WordAck {
|
||||||
|
required string word = 1; // one word of mnemonic on asked position
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Set U2F counter
|
||||||
|
* @start
|
||||||
|
* @next Success
|
||||||
|
*/
|
||||||
|
message SetU2FCounter {
|
||||||
|
optional uint32 u2f_counter = 1; // counter
|
||||||
|
}
|
||||||
1366
accounts/usbwallet/trezor/messages.pb.go
Normal file
1366
accounts/usbwallet/trezor/messages.pb.go
Normal file
File diff suppressed because it is too large
Load diff
267
accounts/usbwallet/trezor/messages.proto
Normal file
267
accounts/usbwallet/trezor/messages.proto
Normal file
|
|
@ -0,0 +1,267 @@
|
||||||
|
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
||||||
|
// https://github.com/trezor/trezor-common/blob/master/protob/messages.proto
|
||||||
|
// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9.
|
||||||
|
|
||||||
|
syntax = "proto2";
|
||||||
|
package hw.trezor.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Messages for TREZOR communication
|
||||||
|
*/
|
||||||
|
|
||||||
|
option go_package = "github.com/XinFinOrg/XDPoSChain/accounts/usbwallet/trezor";
|
||||||
|
|
||||||
|
// Sugar for easier handling in Java
|
||||||
|
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||||
|
option java_outer_classname = "TrezorMessage";
|
||||||
|
|
||||||
|
|
||||||
|
import "google/protobuf/descriptor.proto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for specifying message direction and type of wire (normal/debug)
|
||||||
|
*/
|
||||||
|
extend google.protobuf.EnumValueOptions {
|
||||||
|
optional bool wire_in = 50002; // message can be transmitted via wire from PC to TREZOR
|
||||||
|
optional bool wire_out = 50003; // message can be transmitted via wire from TREZOR to PC
|
||||||
|
optional bool wire_debug_in = 50004; // message can be transmitted via debug wire from PC to TREZOR
|
||||||
|
optional bool wire_debug_out = 50005; // message can be transmitted via debug wire from TREZOR to PC
|
||||||
|
optional bool wire_tiny = 50006; // message is handled by TREZOR when the USB stack is in tiny mode
|
||||||
|
optional bool wire_bootloader = 50007; // message is only handled by TREZOR Bootloader
|
||||||
|
optional bool wire_no_fsm = 50008; // message is not handled by TREZOR unless the USB stack is in tiny mode
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping between TREZOR wire identifier (uint) and a protobuf message
|
||||||
|
*/
|
||||||
|
enum MessageType {
|
||||||
|
|
||||||
|
// Management
|
||||||
|
MessageType_Initialize = 0 [(wire_in) = true, (wire_tiny) = true];
|
||||||
|
MessageType_Ping = 1 [(wire_in) = true];
|
||||||
|
MessageType_Success = 2 [(wire_out) = true];
|
||||||
|
MessageType_Failure = 3 [(wire_out) = true];
|
||||||
|
MessageType_ChangePin = 4 [(wire_in) = true];
|
||||||
|
MessageType_WipeDevice = 5 [(wire_in) = true];
|
||||||
|
MessageType_GetEntropy = 9 [(wire_in) = true];
|
||||||
|
MessageType_Entropy = 10 [(wire_out) = true];
|
||||||
|
MessageType_LoadDevice = 13 [(wire_in) = true];
|
||||||
|
MessageType_ResetDevice = 14 [(wire_in) = true];
|
||||||
|
MessageType_Features = 17 [(wire_out) = true];
|
||||||
|
MessageType_PinMatrixRequest = 18 [(wire_out) = true];
|
||||||
|
MessageType_PinMatrixAck = 19 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true];
|
||||||
|
MessageType_Cancel = 20 [(wire_in) = true, (wire_tiny) = true];
|
||||||
|
MessageType_ClearSession = 24 [(wire_in) = true];
|
||||||
|
MessageType_ApplySettings = 25 [(wire_in) = true];
|
||||||
|
MessageType_ButtonRequest = 26 [(wire_out) = true];
|
||||||
|
MessageType_ButtonAck = 27 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true];
|
||||||
|
MessageType_ApplyFlags = 28 [(wire_in) = true];
|
||||||
|
MessageType_BackupDevice = 34 [(wire_in) = true];
|
||||||
|
MessageType_EntropyRequest = 35 [(wire_out) = true];
|
||||||
|
MessageType_EntropyAck = 36 [(wire_in) = true];
|
||||||
|
MessageType_PassphraseRequest = 41 [(wire_out) = true];
|
||||||
|
MessageType_PassphraseAck = 42 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true];
|
||||||
|
MessageType_PassphraseStateRequest = 77 [(wire_out) = true];
|
||||||
|
MessageType_PassphraseStateAck = 78 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true];
|
||||||
|
MessageType_RecoveryDevice = 45 [(wire_in) = true];
|
||||||
|
MessageType_WordRequest = 46 [(wire_out) = true];
|
||||||
|
MessageType_WordAck = 47 [(wire_in) = true];
|
||||||
|
MessageType_GetFeatures = 55 [(wire_in) = true];
|
||||||
|
MessageType_SetU2FCounter = 63 [(wire_in) = true];
|
||||||
|
|
||||||
|
// Bootloader
|
||||||
|
MessageType_FirmwareErase = 6 [(wire_in) = true, (wire_bootloader) = true];
|
||||||
|
MessageType_FirmwareUpload = 7 [(wire_in) = true, (wire_bootloader) = true];
|
||||||
|
MessageType_FirmwareRequest = 8 [(wire_out) = true, (wire_bootloader) = true];
|
||||||
|
MessageType_SelfTest = 32 [(wire_in) = true, (wire_bootloader) = true];
|
||||||
|
|
||||||
|
// Bitcoin
|
||||||
|
MessageType_GetPublicKey = 11 [(wire_in) = true];
|
||||||
|
MessageType_PublicKey = 12 [(wire_out) = true];
|
||||||
|
MessageType_SignTx = 15 [(wire_in) = true];
|
||||||
|
MessageType_TxRequest = 21 [(wire_out) = true];
|
||||||
|
MessageType_TxAck = 22 [(wire_in) = true];
|
||||||
|
MessageType_GetAddress = 29 [(wire_in) = true];
|
||||||
|
MessageType_Address = 30 [(wire_out) = true];
|
||||||
|
MessageType_SignMessage = 38 [(wire_in) = true];
|
||||||
|
MessageType_VerifyMessage = 39 [(wire_in) = true];
|
||||||
|
MessageType_MessageSignature = 40 [(wire_out) = true];
|
||||||
|
|
||||||
|
// Crypto
|
||||||
|
MessageType_CipherKeyValue = 23 [(wire_in) = true];
|
||||||
|
MessageType_CipheredKeyValue = 48 [(wire_out) = true];
|
||||||
|
MessageType_SignIdentity = 53 [(wire_in) = true];
|
||||||
|
MessageType_SignedIdentity = 54 [(wire_out) = true];
|
||||||
|
MessageType_GetECDHSessionKey = 61 [(wire_in) = true];
|
||||||
|
MessageType_ECDHSessionKey = 62 [(wire_out) = true];
|
||||||
|
MessageType_CosiCommit = 71 [(wire_in) = true];
|
||||||
|
MessageType_CosiCommitment = 72 [(wire_out) = true];
|
||||||
|
MessageType_CosiSign = 73 [(wire_in) = true];
|
||||||
|
MessageType_CosiSignature = 74 [(wire_out) = true];
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
MessageType_DebugLinkDecision = 100 [(wire_debug_in) = true, (wire_tiny) = true, (wire_no_fsm) = true];
|
||||||
|
MessageType_DebugLinkGetState = 101 [(wire_debug_in) = true, (wire_tiny) = true];
|
||||||
|
MessageType_DebugLinkState = 102 [(wire_debug_out) = true];
|
||||||
|
MessageType_DebugLinkStop = 103 [(wire_debug_in) = true];
|
||||||
|
MessageType_DebugLinkLog = 104 [(wire_debug_out) = true];
|
||||||
|
MessageType_DebugLinkMemoryRead = 110 [(wire_debug_in) = true];
|
||||||
|
MessageType_DebugLinkMemory = 111 [(wire_debug_out) = true];
|
||||||
|
MessageType_DebugLinkMemoryWrite = 112 [(wire_debug_in) = true];
|
||||||
|
MessageType_DebugLinkFlashErase = 113 [(wire_debug_in) = true];
|
||||||
|
|
||||||
|
// Ethereum
|
||||||
|
MessageType_EthereumGetPublicKey = 450 [(wire_in) = true];
|
||||||
|
MessageType_EthereumPublicKey = 451 [(wire_out) = true];
|
||||||
|
MessageType_EthereumGetAddress = 56 [(wire_in) = true];
|
||||||
|
MessageType_EthereumAddress = 57 [(wire_out) = true];
|
||||||
|
MessageType_EthereumSignTx = 58 [(wire_in) = true];
|
||||||
|
MessageType_EthereumTxRequest = 59 [(wire_out) = true];
|
||||||
|
MessageType_EthereumTxAck = 60 [(wire_in) = true];
|
||||||
|
MessageType_EthereumSignMessage = 64 [(wire_in) = true];
|
||||||
|
MessageType_EthereumVerifyMessage = 65 [(wire_in) = true];
|
||||||
|
MessageType_EthereumMessageSignature = 66 [(wire_out) = true];
|
||||||
|
|
||||||
|
// NEM
|
||||||
|
MessageType_NEMGetAddress = 67 [(wire_in) = true];
|
||||||
|
MessageType_NEMAddress = 68 [(wire_out) = true];
|
||||||
|
MessageType_NEMSignTx = 69 [(wire_in) = true];
|
||||||
|
MessageType_NEMSignedTx = 70 [(wire_out) = true];
|
||||||
|
MessageType_NEMDecryptMessage = 75 [(wire_in) = true];
|
||||||
|
MessageType_NEMDecryptedMessage = 76 [(wire_out) = true];
|
||||||
|
|
||||||
|
// Lisk
|
||||||
|
MessageType_LiskGetAddress = 114 [(wire_in) = true];
|
||||||
|
MessageType_LiskAddress = 115 [(wire_out) = true];
|
||||||
|
MessageType_LiskSignTx = 116 [(wire_in) = true];
|
||||||
|
MessageType_LiskSignedTx = 117 [(wire_out) = true];
|
||||||
|
MessageType_LiskSignMessage = 118 [(wire_in) = true];
|
||||||
|
MessageType_LiskMessageSignature = 119 [(wire_out) = true];
|
||||||
|
MessageType_LiskVerifyMessage = 120 [(wire_in) = true];
|
||||||
|
MessageType_LiskGetPublicKey = 121 [(wire_in) = true];
|
||||||
|
MessageType_LiskPublicKey = 122 [(wire_out) = true];
|
||||||
|
|
||||||
|
// Tezos
|
||||||
|
MessageType_TezosGetAddress = 150 [(wire_in) = true];
|
||||||
|
MessageType_TezosAddress = 151 [(wire_out) = true];
|
||||||
|
MessageType_TezosSignTx = 152 [(wire_in) = true];
|
||||||
|
MessageType_TezosSignedTx = 153 [(wire_out) = true];
|
||||||
|
MessageType_TezosGetPublicKey = 154 [(wire_in) = true];
|
||||||
|
MessageType_TezosPublicKey = 155 [(wire_out) = true];
|
||||||
|
|
||||||
|
// Stellar
|
||||||
|
MessageType_StellarSignTx = 202 [(wire_in) = true];
|
||||||
|
MessageType_StellarTxOpRequest = 203 [(wire_out) = true];
|
||||||
|
MessageType_StellarGetAddress = 207 [(wire_in) = true];
|
||||||
|
MessageType_StellarAddress = 208 [(wire_out) = true];
|
||||||
|
MessageType_StellarCreateAccountOp = 210 [(wire_in) = true];
|
||||||
|
MessageType_StellarPaymentOp = 211 [(wire_in) = true];
|
||||||
|
MessageType_StellarPathPaymentOp = 212 [(wire_in) = true];
|
||||||
|
MessageType_StellarManageOfferOp = 213 [(wire_in) = true];
|
||||||
|
MessageType_StellarCreatePassiveOfferOp = 214 [(wire_in) = true];
|
||||||
|
MessageType_StellarSetOptionsOp = 215 [(wire_in) = true];
|
||||||
|
MessageType_StellarChangeTrustOp = 216 [(wire_in) = true];
|
||||||
|
MessageType_StellarAllowTrustOp = 217 [(wire_in) = true];
|
||||||
|
MessageType_StellarAccountMergeOp = 218 [(wire_in) = true];
|
||||||
|
// omitted: StellarInflationOp is not a supported operation, would be 219
|
||||||
|
MessageType_StellarManageDataOp = 220 [(wire_in) = true];
|
||||||
|
MessageType_StellarBumpSequenceOp = 221 [(wire_in) = true];
|
||||||
|
MessageType_StellarSignedTx = 230 [(wire_out) = true];
|
||||||
|
|
||||||
|
// TRON
|
||||||
|
MessageType_TronGetAddress = 250 [(wire_in) = true];
|
||||||
|
MessageType_TronAddress = 251 [(wire_out) = true];
|
||||||
|
MessageType_TronSignTx = 252 [(wire_in) = true];
|
||||||
|
MessageType_TronSignedTx = 253 [(wire_out) = true];
|
||||||
|
|
||||||
|
// Cardano
|
||||||
|
// dropped Sign/VerifyMessage ids 300-302
|
||||||
|
MessageType_CardanoSignTx = 303 [(wire_in) = true];
|
||||||
|
MessageType_CardanoTxRequest = 304 [(wire_out) = true];
|
||||||
|
MessageType_CardanoGetPublicKey = 305 [(wire_in) = true];
|
||||||
|
MessageType_CardanoPublicKey = 306 [(wire_out) = true];
|
||||||
|
MessageType_CardanoGetAddress = 307 [(wire_in) = true];
|
||||||
|
MessageType_CardanoAddress = 308 [(wire_out) = true];
|
||||||
|
MessageType_CardanoTxAck = 309 [(wire_in) = true];
|
||||||
|
MessageType_CardanoSignedTx = 310 [(wire_out) = true];
|
||||||
|
|
||||||
|
// Ontology
|
||||||
|
MessageType_OntologyGetAddress = 350 [(wire_in) = true];
|
||||||
|
MessageType_OntologyAddress = 351 [(wire_out) = true];
|
||||||
|
MessageType_OntologyGetPublicKey = 352 [(wire_in) = true];
|
||||||
|
MessageType_OntologyPublicKey = 353 [(wire_out) = true];
|
||||||
|
MessageType_OntologySignTransfer = 354 [(wire_in) = true];
|
||||||
|
MessageType_OntologySignedTransfer = 355 [(wire_out) = true];
|
||||||
|
MessageType_OntologySignWithdrawOng = 356 [(wire_in) = true];
|
||||||
|
MessageType_OntologySignedWithdrawOng = 357 [(wire_out) = true];
|
||||||
|
MessageType_OntologySignOntIdRegister = 358 [(wire_in) = true];
|
||||||
|
MessageType_OntologySignedOntIdRegister = 359 [(wire_out) = true];
|
||||||
|
MessageType_OntologySignOntIdAddAttributes = 360 [(wire_in) = true];
|
||||||
|
MessageType_OntologySignedOntIdAddAttributes = 361 [(wire_out) = true];
|
||||||
|
|
||||||
|
// Ripple
|
||||||
|
MessageType_RippleGetAddress = 400 [(wire_in) = true];
|
||||||
|
MessageType_RippleAddress = 401 [(wire_out) = true];
|
||||||
|
MessageType_RippleSignTx = 402 [(wire_in) = true];
|
||||||
|
MessageType_RippleSignedTx = 403 [(wire_in) = true];
|
||||||
|
|
||||||
|
// Monero
|
||||||
|
MessageType_MoneroTransactionInitRequest = 501 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionInitAck = 502 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionSetInputRequest = 503 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionSetInputAck = 504 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionInputsPermutationRequest = 505 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionInputsPermutationAck = 506 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionInputViniRequest = 507 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionInputViniAck = 508 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionAllInputsSetRequest = 509 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionAllInputsSetAck = 510 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionSetOutputRequest = 511 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionSetOutputAck = 512 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionAllOutSetRequest = 513 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionAllOutSetAck = 514 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionSignInputRequest = 515 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionSignInputAck = 516 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionFinalRequest = 517 [(wire_out) = true];
|
||||||
|
MessageType_MoneroTransactionFinalAck = 518 [(wire_out) = true];
|
||||||
|
MessageType_MoneroKeyImageExportInitRequest = 530 [(wire_out) = true];
|
||||||
|
MessageType_MoneroKeyImageExportInitAck = 531 [(wire_out) = true];
|
||||||
|
MessageType_MoneroKeyImageSyncStepRequest = 532 [(wire_out) = true];
|
||||||
|
MessageType_MoneroKeyImageSyncStepAck = 533 [(wire_out) = true];
|
||||||
|
MessageType_MoneroKeyImageSyncFinalRequest = 534 [(wire_out) = true];
|
||||||
|
MessageType_MoneroKeyImageSyncFinalAck = 535 [(wire_out) = true];
|
||||||
|
MessageType_MoneroGetAddress = 540 [(wire_in) = true];
|
||||||
|
MessageType_MoneroAddress = 541 [(wire_out) = true];
|
||||||
|
MessageType_MoneroGetWatchKey = 542 [(wire_in) = true];
|
||||||
|
MessageType_MoneroWatchKey = 543 [(wire_out) = true];
|
||||||
|
MessageType_DebugMoneroDiagRequest = 546 [(wire_in) = true];
|
||||||
|
MessageType_DebugMoneroDiagAck = 547 [(wire_out) = true];
|
||||||
|
MessageType_MoneroGetTxKeyRequest = 550 [(wire_in) = true];
|
||||||
|
MessageType_MoneroGetTxKeyAck = 551 [(wire_out) = true];
|
||||||
|
MessageType_MoneroLiveRefreshStartRequest = 552 [(wire_in) = true];
|
||||||
|
MessageType_MoneroLiveRefreshStartAck = 553 [(wire_out) = true];
|
||||||
|
MessageType_MoneroLiveRefreshStepRequest = 554 [(wire_in) = true];
|
||||||
|
MessageType_MoneroLiveRefreshStepAck = 555 [(wire_out) = true];
|
||||||
|
MessageType_MoneroLiveRefreshFinalRequest = 556 [(wire_in) = true];
|
||||||
|
MessageType_MoneroLiveRefreshFinalAck = 557 [(wire_out) = true];
|
||||||
|
|
||||||
|
// EOS
|
||||||
|
MessageType_EosGetPublicKey = 600 [(wire_in) = true];
|
||||||
|
MessageType_EosPublicKey = 601 [(wire_out) = true];
|
||||||
|
MessageType_EosSignTx = 602 [(wire_in) = true];
|
||||||
|
MessageType_EosTxActionRequest = 603 [(wire_out) = true];
|
||||||
|
MessageType_EosTxActionAck = 604 [(wire_in) = true];
|
||||||
|
MessageType_EosSignedTx = 605 [(wire_out) = true];
|
||||||
|
|
||||||
|
// Binance
|
||||||
|
MessageType_BinanceGetAddress = 700 [(wire_in) = true];
|
||||||
|
MessageType_BinanceAddress = 701 [(wire_out) = true];
|
||||||
|
MessageType_BinanceGetPublicKey = 702 [(wire_in) = true];
|
||||||
|
MessageType_BinancePublicKey = 703 [(wire_out) = true];
|
||||||
|
MessageType_BinanceSignTx = 704 [(wire_in) = true];
|
||||||
|
MessageType_BinanceTxRequest = 705 [(wire_out) = true];
|
||||||
|
MessageType_BinanceTransferMsg = 706 [(wire_in) = true];
|
||||||
|
MessageType_BinanceOrderMsg = 707 [(wire_in) = true];
|
||||||
|
MessageType_BinanceCancelMsg = 708 [(wire_in) = true];
|
||||||
|
MessageType_BinanceSignedTx = 709 [(wire_out) = true];
|
||||||
|
}
|
||||||
70
accounts/usbwallet/trezor/trezor.go
Normal file
70
accounts/usbwallet/trezor/trezor.go
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// This file contains the implementation for interacting with the Trezor hardware
|
||||||
|
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
||||||
|
// https://docs.trezor.io/trezor-firmware/common/message-workflows.html
|
||||||
|
|
||||||
|
// !!! STAHP !!!
|
||||||
|
//
|
||||||
|
// Before you touch the protocol files, you need to be aware of a breaking change
|
||||||
|
// that occurred between firmware versions 1.7.3->1.8.0 (Model One) and 2.0.10->
|
||||||
|
// 2.1.0 (Model T). The Ethereum address representation was changed from the 20
|
||||||
|
// byte binary blob to a 42 byte hex string. The upstream protocol buffer files
|
||||||
|
// only support the new format, so blindly pulling in a new spec will break old
|
||||||
|
// devices!
|
||||||
|
//
|
||||||
|
// The Trezor devs had the foresight to add the string version as a new message
|
||||||
|
// code instead of replacing the binary one. This means that the proto file can
|
||||||
|
// actually define both the old and the new versions as optional. Please ensure
|
||||||
|
// that you add back the old addresses everywhere (to avoid name clash. use the
|
||||||
|
// addressBin and addressHex names).
|
||||||
|
//
|
||||||
|
// If in doubt, reach out to @karalabe.
|
||||||
|
|
||||||
|
// To regenerate the protocol files in this package:
|
||||||
|
// - Download the latest protoc https://github.com/protocolbuffers/protobuf/releases
|
||||||
|
// - Build with the usual `./configure && make` and ensure it's on your $PATH
|
||||||
|
// - Delete all the .proto and .pb.go files, pull in fresh ones from Trezor
|
||||||
|
// - Grab the latest Go plugin `go get -u google.golang.org/protobuf/cmd/protoc-gen-go`
|
||||||
|
// - Vendor in the latest Go plugin `govendor fetch google.golang.org/protobuf/...`
|
||||||
|
|
||||||
|
//go:generate protoc -I/usr/local/include:. --go_out=paths=source_relative:. messages.proto messages-common.proto messages-management.proto messages-ethereum.proto
|
||||||
|
|
||||||
|
// Package trezor contains the wire protocol.
|
||||||
|
package trezor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type returns the protocol buffer type number of a specific message. If the
|
||||||
|
// message is nil, this method panics!
|
||||||
|
func Type(msg proto.Message) uint16 {
|
||||||
|
return uint16(MessageType_value["MessageType_"+reflect.TypeOf(msg).Elem().Name()])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the friendly message type name of a specific protocol buffer
|
||||||
|
// type number.
|
||||||
|
func Name(kind uint16) string {
|
||||||
|
name := MessageType_name[int32(kind)]
|
||||||
|
if len(name) < 12 {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return name[12:]
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
"github.com/XinFinOrg/XDPoSChain/log"
|
"github.com/XinFinOrg/XDPoSChain/log"
|
||||||
"github.com/karalabe/hid"
|
"github.com/karalabe/hid"
|
||||||
)
|
)
|
||||||
|
|
@ -66,6 +67,8 @@ type driver interface {
|
||||||
// SignTx sends the transaction to the USB device and waits for the user to confirm
|
// SignTx sends the transaction to the USB device and waits for the user to confirm
|
||||||
// or deny the transaction.
|
// or deny the transaction.
|
||||||
SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error)
|
SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error)
|
||||||
|
|
||||||
|
SignTypedMessage(path accounts.DerivationPath, messageHash []byte, domainHash []byte) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// wallet represents the common functionality shared by all USB hardware
|
// wallet represents the common functionality shared by all USB hardware
|
||||||
|
|
@ -77,16 +80,16 @@ type wallet struct {
|
||||||
url *accounts.URL // Textual URL uniquely identifying this wallet
|
url *accounts.URL // Textual URL uniquely identifying this wallet
|
||||||
|
|
||||||
info hid.DeviceInfo // Known USB device infos about the wallet
|
info hid.DeviceInfo // Known USB device infos about the wallet
|
||||||
device *hid.Device // USB device advertising itself as a hardware wallet
|
device hid.Device // USB device advertising itself as a hardware wallet
|
||||||
|
|
||||||
accounts []accounts.Account // List of derive accounts pinned on the hardware wallet
|
accounts []accounts.Account // List of derive accounts pinned on the hardware wallet
|
||||||
paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
|
paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
|
||||||
|
|
||||||
deriveNextPath accounts.DerivationPath // Next derivation path for account auto-discovery
|
deriveNextPaths []accounts.DerivationPath // Next derivation paths for account auto-discovery (multiple bases supported)
|
||||||
deriveNextAddr common.Address // Next derived account address for auto-discovery
|
deriveNextAddrs []common.Address // Next derived account addresses for auto-discovery (multiple bases supported)
|
||||||
deriveChain ethereum.ChainStateReader // Blockchain state reader to discover used account with
|
deriveChain ethereum.ChainStateReader // Blockchain state reader to discover used account with
|
||||||
deriveReq chan chan struct{} // Channel to request a self-derivation on
|
deriveReq chan chan struct{} // Channel to request a self-derivation on
|
||||||
deriveQuit chan chan error // Channel to terminate the self-deriver with
|
deriveQuit chan chan error // Channel to terminate the self-deriver with
|
||||||
|
|
||||||
healthQuit chan chan error
|
healthQuit chan chan error
|
||||||
|
|
||||||
|
|
@ -99,7 +102,7 @@ type wallet struct {
|
||||||
//
|
//
|
||||||
// As such, a hardware wallet needs two locks to function correctly. A state
|
// As such, a hardware wallet needs two locks to function correctly. A state
|
||||||
// lock can be used to protect the wallet's software-side internal state, which
|
// lock can be used to protect the wallet's software-side internal state, which
|
||||||
// must not be held exlusively during hardware communication. A communication
|
// must not be held exclusively during hardware communication. A communication
|
||||||
// lock can be used to achieve exclusive access to the device itself, this one
|
// lock can be used to achieve exclusive access to the device itself, this one
|
||||||
// however should allow "skipping" waiting for operations that might want to
|
// however should allow "skipping" waiting for operations that might want to
|
||||||
// use the device, but can live without too (e.g. account self-derivation).
|
// use the device, but can live without too (e.g. account self-derivation).
|
||||||
|
|
@ -273,9 +276,7 @@ func (w *wallet) close() error {
|
||||||
w.device = nil
|
w.device = nil
|
||||||
|
|
||||||
w.accounts, w.paths = nil, nil
|
w.accounts, w.paths = nil, nil
|
||||||
w.driver.Close()
|
return w.driver.Close()
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accounts implements accounts.Wallet, returning the list of accounts pinned to
|
// Accounts implements accounts.Wallet, returning the list of accounts pinned to
|
||||||
|
|
@ -340,57 +341,66 @@ func (w *wallet) selfDerive() {
|
||||||
accs []accounts.Account
|
accs []accounts.Account
|
||||||
paths []accounts.DerivationPath
|
paths []accounts.DerivationPath
|
||||||
|
|
||||||
nextAddr = w.deriveNextAddr
|
nextPaths = append([]accounts.DerivationPath{}, w.deriveNextPaths...)
|
||||||
nextPath = w.deriveNextPath
|
nextAddrs = append([]common.Address{}, w.deriveNextAddrs...)
|
||||||
|
|
||||||
context = context.Background()
|
context = context.Background()
|
||||||
)
|
)
|
||||||
for empty := false; !empty; {
|
for i := 0; i < len(nextAddrs); i++ {
|
||||||
// Retrieve the next derived Ethereum account
|
for empty := false; !empty; {
|
||||||
if nextAddr == (common.Address{}) {
|
// Retrieve the next derived Ethereum account
|
||||||
if nextAddr, err = w.driver.Derive(nextPath); err != nil {
|
if nextAddrs[i] == (common.Address{}) {
|
||||||
w.log.Warn("USB wallet account derivation failed", "err", err)
|
if nextAddrs[i], err = w.driver.Derive(nextPaths[i]); err != nil {
|
||||||
|
w.log.Warn("USB wallet account derivation failed", "err", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check the account's status against the current chain state
|
||||||
|
var (
|
||||||
|
balance *big.Int
|
||||||
|
nonce uint64
|
||||||
|
)
|
||||||
|
balance, err = w.deriveChain.BalanceAt(context, nextAddrs[i], nil)
|
||||||
|
if err != nil {
|
||||||
|
w.log.Warn("USB wallet balance retrieval failed", "err", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
nonce, err = w.deriveChain.NonceAt(context, nextAddrs[i], nil)
|
||||||
// Check the account's status against the current chain state
|
if err != nil {
|
||||||
var (
|
w.log.Warn("USB wallet nonce retrieval failed", "err", err)
|
||||||
balance *big.Int
|
break
|
||||||
nonce uint64
|
}
|
||||||
)
|
// We've just self-derived a new account, start tracking it locally
|
||||||
balance, err = w.deriveChain.BalanceAt(context, nextAddr, nil)
|
// unless the account was empty.
|
||||||
if err != nil {
|
path := make(accounts.DerivationPath, len(nextPaths[i]))
|
||||||
w.log.Warn("USB wallet balance retrieval failed", "err", err)
|
copy(path[:], nextPaths[i][:])
|
||||||
break
|
if balance.Sign() == 0 && nonce == 0 {
|
||||||
}
|
empty = true
|
||||||
nonce, err = w.deriveChain.NonceAt(context, nextAddr, nil)
|
// If it indeed was empty, make a log output for it anyway. In the case
|
||||||
if err != nil {
|
// of legacy-ledger, the first account on the legacy-path will
|
||||||
w.log.Warn("USB wallet nonce retrieval failed", "err", err)
|
// be shown to the user, even if we don't actively track it
|
||||||
break
|
if i < len(nextAddrs)-1 {
|
||||||
}
|
w.log.Info("Skipping tracking first account on legacy path, use personal.deriveAccount(<url>,<path>, false) to track",
|
||||||
// If the next account is empty, stop self-derivation, but add it nonetheless
|
"path", path, "address", nextAddrs[i])
|
||||||
if balance.Sign() == 0 && nonce == 0 {
|
break
|
||||||
empty = true
|
}
|
||||||
}
|
}
|
||||||
// We've just self-derived a new account, start tracking it locally
|
paths = append(paths, path)
|
||||||
path := make(accounts.DerivationPath, len(nextPath))
|
account := accounts.Account{
|
||||||
copy(path[:], nextPath[:])
|
Address: nextAddrs[i],
|
||||||
paths = append(paths, path)
|
URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)},
|
||||||
|
}
|
||||||
|
accs = append(accs, account)
|
||||||
|
|
||||||
account := accounts.Account{
|
// Display a log message to the user for new (or previously empty accounts)
|
||||||
Address: nextAddr,
|
if _, known := w.paths[nextAddrs[i]]; !known || (!empty && nextAddrs[i] == w.deriveNextAddrs[i]) {
|
||||||
URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)},
|
w.log.Info("USB wallet discovered new account", "address", nextAddrs[i], "path", path, "balance", balance, "nonce", nonce)
|
||||||
}
|
}
|
||||||
accs = append(accs, account)
|
// Fetch the next potential account
|
||||||
|
if !empty {
|
||||||
// Display a log message to the user for new (or previously empty accounts)
|
nextAddrs[i] = common.Address{}
|
||||||
if _, known := w.paths[nextAddr]; !known || (!empty && nextAddr == w.deriveNextAddr) {
|
nextPaths[i][len(nextPaths[i])-1]++
|
||||||
w.log.Info("USB wallet discovered new account", "address", nextAddr, "path", path, "balance", balance, "nonce", nonce)
|
}
|
||||||
}
|
|
||||||
// Fetch the next potential account
|
|
||||||
if !empty {
|
|
||||||
nextAddr = common.Address{}
|
|
||||||
nextPath[len(nextPath)-1]++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Self derivation complete, release device lock
|
// Self derivation complete, release device lock
|
||||||
|
|
@ -407,8 +417,8 @@ func (w *wallet) selfDerive() {
|
||||||
}
|
}
|
||||||
// Shift the self-derivation forward
|
// Shift the self-derivation forward
|
||||||
// TODO(karalabe): don't overwrite changes from wallet.SelfDerive
|
// TODO(karalabe): don't overwrite changes from wallet.SelfDerive
|
||||||
w.deriveNextAddr = nextAddr
|
w.deriveNextAddrs = nextAddrs
|
||||||
w.deriveNextPath = nextPath
|
w.deriveNextPaths = nextPaths
|
||||||
w.stateLock.Unlock()
|
w.stateLock.Unlock()
|
||||||
|
|
||||||
// Notify the user of termination and loop after a bit of time (to avoid trashing)
|
// Notify the user of termination and loop after a bit of time (to avoid trashing)
|
||||||
|
|
@ -473,34 +483,105 @@ func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun
|
||||||
w.stateLock.Lock()
|
w.stateLock.Lock()
|
||||||
defer w.stateLock.Unlock()
|
defer w.stateLock.Unlock()
|
||||||
|
|
||||||
|
if w.device == nil {
|
||||||
|
return accounts.Account{}, accounts.ErrWalletClosed
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := w.paths[address]; !ok {
|
if _, ok := w.paths[address]; !ok {
|
||||||
w.accounts = append(w.accounts, account)
|
w.accounts = append(w.accounts, account)
|
||||||
w.paths[address] = path
|
w.paths[address] = make(accounts.DerivationPath, len(path))
|
||||||
|
copy(w.paths[address], path)
|
||||||
}
|
}
|
||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelfDerive implements accounts.Wallet, trying to discover accounts that the
|
// SelfDerive sets a base account derivation path from which the wallet attempts
|
||||||
// user used previously (based on the chain state), but ones that he/she did not
|
// to discover non zero accounts and automatically add them to list of tracked
|
||||||
// explicitly pin to the wallet manually. To avoid chain head monitoring, self
|
// accounts.
|
||||||
// derivation only runs during account listing (and even then throttled).
|
//
|
||||||
func (w *wallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
// Note, self derivation will increment the last component of the specified path
|
||||||
|
// opposed to descending into a child path to allow discovering accounts starting
|
||||||
|
// from non zero components.
|
||||||
|
//
|
||||||
|
// Some hardware wallets switched derivation paths through their evolution, so
|
||||||
|
// this method supports providing multiple bases to discover old user accounts
|
||||||
|
// too. Only the last base will be used to derive the next empty account.
|
||||||
|
//
|
||||||
|
// You can disable automatic account discovery by calling SelfDerive with a nil
|
||||||
|
// chain state reader.
|
||||||
|
func (w *wallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
||||||
w.stateLock.Lock()
|
w.stateLock.Lock()
|
||||||
defer w.stateLock.Unlock()
|
defer w.stateLock.Unlock()
|
||||||
|
|
||||||
w.deriveNextPath = make(accounts.DerivationPath, len(base))
|
w.deriveNextPaths = make([]accounts.DerivationPath, len(bases))
|
||||||
copy(w.deriveNextPath[:], base[:])
|
for i, base := range bases {
|
||||||
|
w.deriveNextPaths[i] = make(accounts.DerivationPath, len(base))
|
||||||
w.deriveNextAddr = common.Address{}
|
copy(w.deriveNextPaths[i][:], base[:])
|
||||||
|
}
|
||||||
|
w.deriveNextAddrs = make([]common.Address, len(bases))
|
||||||
w.deriveChain = chain
|
w.deriveChain = chain
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignHash implements accounts.Wallet, however signing arbitrary data is not
|
// signHash implements accounts.Wallet, however signing arbitrary data is not
|
||||||
// supported for hardware wallets, so this method will always return an error.
|
// supported for hardware wallets, so this method will always return an error.
|
||||||
func (w *wallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
|
func (w *wallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
|
||||||
return nil, accounts.ErrNotSupported
|
return nil, accounts.ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
|
||||||
|
func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
// Unless we are doing 712 signing, simply dispatch to signHash
|
||||||
|
if !(mimeType == accounts.MimetypeTypedData && len(data) == 66 && data[0] == 0x19 && data[1] == 0x01) {
|
||||||
|
return w.SignHash(account, crypto.Keccak256(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatch to 712 signing if the mimetype is TypedData and the format matches
|
||||||
|
w.stateLock.RLock() // Comms have own mutex, this is for the state fields
|
||||||
|
defer w.stateLock.RUnlock()
|
||||||
|
|
||||||
|
// If the wallet is closed, abort
|
||||||
|
if w.device == nil {
|
||||||
|
return nil, accounts.ErrWalletClosed
|
||||||
|
}
|
||||||
|
// Make sure the requested account is contained within
|
||||||
|
path, ok := w.paths[account.Address]
|
||||||
|
if !ok {
|
||||||
|
return nil, accounts.ErrUnknownAccount
|
||||||
|
}
|
||||||
|
// All infos gathered and metadata checks out, request signing
|
||||||
|
<-w.commsLock
|
||||||
|
defer func() { w.commsLock <- struct{}{} }()
|
||||||
|
|
||||||
|
// Ensure the device isn't screwed with while user confirmation is pending
|
||||||
|
// TODO(karalabe): remove if hotplug lands on Windows
|
||||||
|
w.hub.commsLock.Lock()
|
||||||
|
w.hub.commsPend++
|
||||||
|
w.hub.commsLock.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
w.hub.commsLock.Lock()
|
||||||
|
w.hub.commsPend--
|
||||||
|
w.hub.commsLock.Unlock()
|
||||||
|
}()
|
||||||
|
// Sign the transaction
|
||||||
|
signature, err := w.driver.SignTypedMessage(path, data[2:34], data[34:66])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return signature, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||||
|
// data with the given account using passphrase as extra authentication.
|
||||||
|
// Since USB wallets don't rely on passphrases, these are silently ignored.
|
||||||
|
func (w *wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
return w.SignData(account, mimeType, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
|
||||||
|
return w.SignHash(account, accounts.TextHash(text))
|
||||||
|
}
|
||||||
|
|
||||||
// SignTx implements accounts.Wallet. It sends the transaction over to the Ledger
|
// SignTx implements accounts.Wallet. It sends the transaction over to the Ledger
|
||||||
// wallet to request a confirmation from the user. It returns either the signed
|
// wallet to request a confirmation from the user. It returns either the signed
|
||||||
// transaction or a failure if the user denied the transaction.
|
// transaction or a failure if the user denied the transaction.
|
||||||
|
|
@ -547,11 +628,11 @@ func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID
|
||||||
return signed, nil
|
return signed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary
|
// SignTextWithPassphrase implements accounts.Wallet, however signing arbitrary
|
||||||
// data is not supported for Ledger wallets, so this method will always return
|
// data is not supported for Ledger wallets, so this method will always return
|
||||||
// an error.
|
// an error.
|
||||||
func (w *wallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) {
|
func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||||
return w.SignHash(account, hash)
|
return w.SignText(account, accounts.TextHash(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||||
|
|
|
||||||
|
|
@ -302,20 +302,31 @@ func accountCreate(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
utils.SetNodeConfig(ctx, &cfg.Node)
|
utils.SetNodeConfig(ctx, &cfg.Node)
|
||||||
scryptN, scryptP, keydir, err := cfg.Node.AccountConfig()
|
keydir, err := cfg.Node.KeyDirConfig()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to read configuration: %v", err)
|
utils.Fatalf("Failed to read configuration: %v", err)
|
||||||
}
|
}
|
||||||
|
scryptN := keystore.StandardScryptN
|
||||||
|
scryptP := keystore.StandardScryptP
|
||||||
|
if cfg.Node.UseLightweightKDF {
|
||||||
|
scryptN = keystore.LightScryptN
|
||||||
|
scryptP = keystore.LightScryptP
|
||||||
|
}
|
||||||
|
|
||||||
password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
|
password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
|
||||||
|
|
||||||
address, err := keystore.StoreKey(keydir, password, scryptN, scryptP)
|
account, err := keystore.StoreKey(keydir, password, scryptN, scryptP)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to create account: %v", err)
|
utils.Fatalf("Failed to create account: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Address: {xdc%x}\n", address)
|
fmt.Printf("\nYour new key was generated\n\n")
|
||||||
|
fmt.Printf("Public address of the key: %s\n", account.Address.Hex())
|
||||||
|
fmt.Printf("Path of the secret key file: %s\n\n", account.URL.Path)
|
||||||
|
fmt.Printf("- You can share your public address with anyone. Others need it to interact with you.\n")
|
||||||
|
fmt.Printf("- You must NEVER share the secret key with anyone! The key controls access to your funds!\n")
|
||||||
|
fmt.Printf("- You must BACKUP your key file! Without the key, it's impossible to access account funds!\n")
|
||||||
|
fmt.Printf("- You must REMEMBER your password! Without the password, it's impossible to decrypt the key!\n\n")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import (
|
||||||
// are copied into a temporary keystore directory.
|
// are copied into a temporary keystore directory.
|
||||||
|
|
||||||
func tmpDatadirWithKeystore(t *testing.T) string {
|
func tmpDatadirWithKeystore(t *testing.T) string {
|
||||||
datadir := tmpdir(t)
|
datadir := t.TempDir()
|
||||||
keystore := filepath.Join(datadir, "keystore")
|
keystore := filepath.Join(datadir, "keystore")
|
||||||
source := filepath.Join("..", "..", "accounts", "keystore", "testdata", "keystore")
|
source := filepath.Join("..", "..", "accounts", "keystore", "testdata", "keystore")
|
||||||
if err := cp.CopyAll(keystore, source); err != nil {
|
if err := cp.CopyAll(keystore, source); err != nil {
|
||||||
|
|
@ -43,8 +43,7 @@ func tmpDatadirWithKeystore(t *testing.T) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountListEmpty(t *testing.T) {
|
func TestAccountListEmpty(t *testing.T) {
|
||||||
datadir := tmpdir(t)
|
datadir := t.TempDir()
|
||||||
defer os.RemoveAll(datadir)
|
|
||||||
XDC := runXDC(t, "account", "list", "--datadir", datadir)
|
XDC := runXDC(t, "account", "list", "--datadir", datadir)
|
||||||
XDC.ExpectExit()
|
XDC.ExpectExit()
|
||||||
}
|
}
|
||||||
|
|
@ -79,8 +78,41 @@ Your new account is locked with a password. Please give a password. Do not forge
|
||||||
!! Unsupported terminal, password will be echoed.
|
!! Unsupported terminal, password will be echoed.
|
||||||
Passphrase: {{.InputLine "foobar"}}
|
Passphrase: {{.InputLine "foobar"}}
|
||||||
Repeat passphrase: {{.InputLine "foobar"}}
|
Repeat passphrase: {{.InputLine "foobar"}}
|
||||||
|
|
||||||
|
Your new key was generated
|
||||||
|
|
||||||
`)
|
`)
|
||||||
XDC.ExpectRegexp(`Address: \{xdc[0-9a-f]{40}\}\n`)
|
XDC.ExpectRegexp(`
|
||||||
|
Public address of the key: xdc[0-9a-fA-F]{40}
|
||||||
|
Path of the secret key file: .*UTC--.+--xdc[0-9a-fA-F]{40}
|
||||||
|
|
||||||
|
- You can share your public address with anyone. Others need it to interact with you.
|
||||||
|
- You must NEVER share the secret key with anyone! The key controls access to your funds!
|
||||||
|
- You must BACKUP your key file! Without the key, it's impossible to access account funds!
|
||||||
|
- You must REMEMBER your password! Without the password, it's impossible to decrypt the key!
|
||||||
|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountImport(t *testing.T) {
|
||||||
|
tests := []struct{ name, key, output string }{
|
||||||
|
{
|
||||||
|
name: "correct account",
|
||||||
|
key: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||||
|
output: "Address: {xdcfcad0b19bb29d4674531d6f115237e16afce377c}\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid character",
|
||||||
|
key: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef1",
|
||||||
|
output: "Fatal: Failed to load the private key: invalid character '1' at end of key file\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
importAccountWithExpect(t, test.key, test.output)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountNewBadRepeat(t *testing.T) {
|
func TestAccountNewBadRepeat(t *testing.T) {
|
||||||
|
|
@ -111,8 +143,7 @@ Repeat passphrase: {{.InputLine "foobar2"}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWalletImport(t *testing.T) {
|
func TestWalletImport(t *testing.T) {
|
||||||
datadir := tmpdir(t)
|
datadir := t.TempDir()
|
||||||
defer os.RemoveAll(datadir)
|
|
||||||
XDC := runXDC(t, "wallet", "import", "--datadir", datadir, "--lightkdf", "testdata/guswallet.json")
|
XDC := runXDC(t, "wallet", "import", "--datadir", datadir, "--lightkdf", "testdata/guswallet.json")
|
||||||
defer XDC.ExpectExit()
|
defer XDC.ExpectExit()
|
||||||
XDC.Expect(`
|
XDC.Expect(`
|
||||||
|
|
@ -163,7 +194,7 @@ func TestWalletImportBadPassword(t *testing.T) {
|
||||||
XDC.Expect(`
|
XDC.Expect(`
|
||||||
!! Unsupported terminal, password will be echoed.
|
!! Unsupported terminal, password will be echoed.
|
||||||
Passphrase: {{.InputLine "wrong"}}
|
Passphrase: {{.InputLine "wrong"}}
|
||||||
Fatal: could not decrypt key with given passphrase
|
Fatal: could not decrypt key with given password
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,7 +238,7 @@ Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 2/3
|
||||||
Passphrase: {{.InputLine "wrong2"}}
|
Passphrase: {{.InputLine "wrong2"}}
|
||||||
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 3/3
|
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 3/3
|
||||||
Passphrase: {{.InputLine "wrong3"}}
|
Passphrase: {{.InputLine "wrong3"}}
|
||||||
Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could not decrypt key with given passphrase)
|
Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could not decrypt key with given password)
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,7 +300,7 @@ func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) {
|
||||||
"--password", "testdata/wrong-passwords.txt", "--unlock", "0,2")
|
"--password", "testdata/wrong-passwords.txt", "--unlock", "0,2")
|
||||||
defer XDC.ExpectExit()
|
defer XDC.ExpectExit()
|
||||||
XDC.Expect(`
|
XDC.Expect(`
|
||||||
Fatal: Failed to unlock account 0 (could not decrypt key with given passphrase)
|
Fatal: Failed to unlock account 0 (could not decrypt key with given password)
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,9 @@ import (
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/XDCx"
|
"github.com/XinFinOrg/XDPoSChain/XDCx"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/accounts/scwallet"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/accounts/usbwallet"
|
||||||
"github.com/XinFinOrg/XDPoSChain/cmd/utils"
|
"github.com/XinFinOrg/XDPoSChain/cmd/utils"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/eth/ethconfig"
|
"github.com/XinFinOrg/XDPoSChain/eth/ethconfig"
|
||||||
|
|
@ -207,6 +210,11 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, XDCConfig) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to create the protocol stack: %v", err)
|
utils.Fatalf("Failed to create the protocol stack: %v", err)
|
||||||
}
|
}
|
||||||
|
// Node doesn't by default populate account manager backends
|
||||||
|
if err := setAccountManagerBackends(stack); err != nil {
|
||||||
|
utils.Fatalf("Failed to set account manager backends: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
utils.SetEthConfig(ctx, stack, &cfg.Eth)
|
utils.SetEthConfig(ctx, stack, &cfg.Eth)
|
||||||
if ctx.IsSet(utils.EthStatsURLFlag.Name) {
|
if ctx.IsSet(utils.EthStatsURLFlag.Name) {
|
||||||
cfg.Ethstats.URL = ctx.String(utils.EthStatsURLFlag.Name)
|
cfg.Ethstats.URL = ctx.String(utils.EthStatsURLFlag.Name)
|
||||||
|
|
@ -335,3 +343,51 @@ func applyMetricConfig(ctx *cli.Context, cfg *XDCConfig) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setAccountManagerBackends(stack *node.Node) error {
|
||||||
|
conf := stack.Config()
|
||||||
|
am := stack.AccountManager()
|
||||||
|
keydir := stack.KeyStoreDir()
|
||||||
|
scryptN := keystore.StandardScryptN
|
||||||
|
scryptP := keystore.StandardScryptP
|
||||||
|
if conf.UseLightweightKDF {
|
||||||
|
scryptN = keystore.LightScryptN
|
||||||
|
scryptP = keystore.LightScryptP
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, we're using EITHER external signer OR local signers.
|
||||||
|
// If/when we implement some form of lockfile for USB and keystore wallets,
|
||||||
|
// we can have both, but it's very confusing for the user to see the same
|
||||||
|
// accounts in both externally and locally, plus very racey.
|
||||||
|
am.AddBackend(keystore.NewKeyStore(keydir, scryptN, scryptP))
|
||||||
|
if conf.USB {
|
||||||
|
// Start a USB hub for Ledger hardware wallets
|
||||||
|
if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil {
|
||||||
|
log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err))
|
||||||
|
} else {
|
||||||
|
am.AddBackend(ledgerhub)
|
||||||
|
}
|
||||||
|
// Start a USB hub for Trezor hardware wallets (HID version)
|
||||||
|
if trezorhub, err := usbwallet.NewTrezorHubWithHID(); err != nil {
|
||||||
|
log.Warn(fmt.Sprintf("Failed to start HID Trezor hub, disabling: %v", err))
|
||||||
|
} else {
|
||||||
|
am.AddBackend(trezorhub)
|
||||||
|
}
|
||||||
|
// Start a USB hub for Trezor hardware wallets (WebUSB version)
|
||||||
|
if trezorhub, err := usbwallet.NewTrezorHubWithWebUSB(); err != nil {
|
||||||
|
log.Warn(fmt.Sprintf("Failed to start WebUSB Trezor hub, disabling: %v", err))
|
||||||
|
} else {
|
||||||
|
am.AddBackend(trezorhub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(conf.SmartCardDaemonPath) > 0 {
|
||||||
|
// Start a smart card hub
|
||||||
|
if schub, err := scwallet.NewHub(conf.SmartCardDaemonPath, scwallet.Scheme, keydir); err != nil {
|
||||||
|
log.Warn(fmt.Sprintf("Failed to start smart card hub, disabling: %v", err))
|
||||||
|
} else {
|
||||||
|
am.AddBackend(schub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -39,8 +38,7 @@ const (
|
||||||
// then terminated by closing the input stream.
|
// then terminated by closing the input stream.
|
||||||
func TestConsoleWelcome(t *testing.T) {
|
func TestConsoleWelcome(t *testing.T) {
|
||||||
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
||||||
datadir := tmpdir(t)
|
datadir := t.TempDir()
|
||||||
defer os.RemoveAll(datadir)
|
|
||||||
|
|
||||||
// Start a XDC console, make sure it's cleaned up and terminate the console
|
// Start a XDC console, make sure it's cleaned up and terminate the console
|
||||||
XDC := runXDC(t,
|
XDC := runXDC(t,
|
||||||
|
|
@ -77,8 +75,7 @@ at block: 0 ({{niltime}})
|
||||||
func TestIPCAttachWelcome(t *testing.T) {
|
func TestIPCAttachWelcome(t *testing.T) {
|
||||||
// Configure the instance for IPC attachement
|
// Configure the instance for IPC attachement
|
||||||
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
||||||
datadir := tmpdir(t)
|
datadir := t.TempDir()
|
||||||
defer os.RemoveAll(datadir)
|
|
||||||
var ipc string
|
var ipc string
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
ipc = `\\.\pipe\XDC` + strconv.Itoa(trulyRandInt(100000, 999999))
|
ipc = `\\.\pipe\XDC` + strconv.Itoa(trulyRandInt(100000, 999999))
|
||||||
|
|
@ -100,8 +97,7 @@ func TestIPCAttachWelcome(t *testing.T) {
|
||||||
func TestHTTPAttachWelcome(t *testing.T) {
|
func TestHTTPAttachWelcome(t *testing.T) {
|
||||||
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
||||||
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
|
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
|
||||||
datadir := tmpdir(t)
|
datadir := t.TempDir()
|
||||||
defer os.RemoveAll(datadir)
|
|
||||||
XDC := runXDC(t,
|
XDC := runXDC(t,
|
||||||
"--datadir", datadir, "--XDCx-datadir", datadir+"/XDCx",
|
"--datadir", datadir, "--XDCx-datadir", datadir+"/XDCx",
|
||||||
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
||||||
|
|
@ -117,8 +113,7 @@ func TestHTTPAttachWelcome(t *testing.T) {
|
||||||
func TestWSAttachWelcome(t *testing.T) {
|
func TestWSAttachWelcome(t *testing.T) {
|
||||||
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
||||||
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
|
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
|
||||||
datadir := tmpdir(t)
|
datadir := t.TempDir()
|
||||||
defer os.RemoveAll(datadir)
|
|
||||||
XDC := runXDC(t,
|
XDC := runXDC(t,
|
||||||
"--datadir", datadir, "--XDCx-datadir", datadir+"/XDCx",
|
"--datadir", datadir, "--XDCx-datadir", datadir+"/XDCx",
|
||||||
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
||||||
|
|
|
||||||
|
|
@ -100,8 +100,7 @@ func TestDAOForkBlockNewChain(t *testing.T) {
|
||||||
|
|
||||||
func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBlock *big.Int, expectVote bool) {
|
func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBlock *big.Int, expectVote bool) {
|
||||||
// Create a temporary data directory to use and inspect later
|
// Create a temporary data directory to use and inspect later
|
||||||
datadir := tmpdir(t)
|
datadir := t.TempDir()
|
||||||
defer os.RemoveAll(datadir)
|
|
||||||
|
|
||||||
// Start a XDC instance with the requested flags set and immediately terminate
|
// Start a XDC instance with the requested flags set and immediately terminate
|
||||||
if genesis != "" {
|
if genesis != "" {
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,9 @@ var (
|
||||||
utils.BootnodesV5Flag,
|
utils.BootnodesV5Flag,
|
||||||
utils.DataDirFlag,
|
utils.DataDirFlag,
|
||||||
utils.KeyStoreDirFlag,
|
utils.KeyStoreDirFlag,
|
||||||
//utils.NoUSBFlag,
|
utils.NoUSBFlag, // deprecated
|
||||||
|
utils.USBFlag,
|
||||||
|
utils.SmartCardDaemonPathFlag,
|
||||||
//utils.EthashCacheDirFlag,
|
//utils.EthashCacheDirFlag,
|
||||||
//utils.EthashCachesInMemoryFlag,
|
//utils.EthashCachesInMemoryFlag,
|
||||||
//utils.EthashCachesOnDiskFlag,
|
//utils.EthashCachesOnDiskFlag,
|
||||||
|
|
@ -312,11 +314,13 @@ func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) {
|
||||||
status, _ := event.Wallet.Status()
|
status, _ := event.Wallet.Status()
|
||||||
log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
|
log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
|
||||||
|
|
||||||
|
var derivationPaths []accounts.DerivationPath
|
||||||
if event.Wallet.URL().Scheme == "ledger" {
|
if event.Wallet.URL().Scheme == "ledger" {
|
||||||
event.Wallet.SelfDerive(accounts.DefaultLedgerBaseDerivationPath, stateReader)
|
derivationPaths = append(derivationPaths, accounts.LegacyLedgerBaseDerivationPath)
|
||||||
} else {
|
|
||||||
event.Wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader)
|
|
||||||
}
|
}
|
||||||
|
derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath)
|
||||||
|
|
||||||
|
event.Wallet.SelfDerive(derivationPaths, stateReader)
|
||||||
|
|
||||||
case accounts.WalletDropped:
|
case accounts.WalletDropped:
|
||||||
log.Info("Old wallet dropped", "url", event.Wallet.URL())
|
log.Info("Old wallet dropped", "url", event.Wallet.URL())
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,6 @@ import (
|
||||||
"github.com/docker/docker/pkg/reexec"
|
"github.com/docker/docker/pkg/reexec"
|
||||||
)
|
)
|
||||||
|
|
||||||
func tmpdir(t *testing.T) string {
|
|
||||||
dir, err := os.MkdirTemp("", "XDC-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return dir
|
|
||||||
}
|
|
||||||
|
|
||||||
type testXDC struct {
|
type testXDC struct {
|
||||||
*cmdtest.TestCmd
|
*cmdtest.TestCmd
|
||||||
|
|
||||||
|
|
@ -78,15 +70,10 @@ func runXDC(t *testing.T, args ...string) *testXDC {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tt.Datadir == "" {
|
if tt.Datadir == "" {
|
||||||
tt.Datadir = tmpdir(t)
|
// The temporary datadir will be removed automatically if something fails below.
|
||||||
|
tt.Datadir = t.TempDir()
|
||||||
tt.Cleanup = func() { os.RemoveAll(tt.Datadir) }
|
tt.Cleanup = func() { os.RemoveAll(tt.Datadir) }
|
||||||
args = append([]string{"--datadir", tt.Datadir}, args...)
|
args = append([]string{"--datadir", tt.Datadir}, args...)
|
||||||
// Remove the temporary datadir if something fails below.
|
|
||||||
defer func() {
|
|
||||||
if t.Failed() {
|
|
||||||
tt.Cleanup()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Boot "XDC". This actually runs the test binary but the TestMain
|
// Boot "XDC". This actually runs the test binary but the TestMain
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||||
|
|
@ -32,13 +33,6 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// Git SHA1 commit hash of the release (set via linker flags)
|
|
||||||
gitCommit = ""
|
|
||||||
|
|
||||||
app *cli.App
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Flags needed by abigen
|
// Flags needed by abigen
|
||||||
abiFlag = &cli.StringFlag{
|
abiFlag = &cli.StringFlag{
|
||||||
|
|
@ -55,7 +49,7 @@ var (
|
||||||
}
|
}
|
||||||
jsonFlag = &cli.StringFlag{
|
jsonFlag = &cli.StringFlag{
|
||||||
Name: "combined-json",
|
Name: "combined-json",
|
||||||
Usage: "Path to the combined-json file generated by compiler",
|
Usage: "Path to the combined-json file generated by compiler, - for STDIN",
|
||||||
}
|
}
|
||||||
excFlag = &cli.StringFlag{
|
excFlag = &cli.StringFlag{
|
||||||
Name: "exc",
|
Name: "exc",
|
||||||
|
|
@ -74,10 +68,15 @@ var (
|
||||||
Usage: "Destination language for the bindings (go)",
|
Usage: "Destination language for the bindings (go)",
|
||||||
Value: "go",
|
Value: "go",
|
||||||
}
|
}
|
||||||
|
aliasFlag = &cli.StringFlag{
|
||||||
|
Name: "alias",
|
||||||
|
Usage: "Comma separated aliases for function and event renaming, e.g. original1=alias1, original2=alias2",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var app = flags.NewApp("", "XDC ABI wrapper code generator")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
app = flags.NewApp(gitCommit, "ethereum checkpoint helper tool")
|
|
||||||
app.Name = "abigen"
|
app.Name = "abigen"
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = []cli.Flag{
|
||||||
abiFlag,
|
abiFlag,
|
||||||
|
|
@ -88,6 +87,7 @@ func init() {
|
||||||
pkgFlag,
|
pkgFlag,
|
||||||
outFlag,
|
outFlag,
|
||||||
langFlag,
|
langFlag,
|
||||||
|
aliasFlag,
|
||||||
}
|
}
|
||||||
app.Action = abigen
|
app.Action = abigen
|
||||||
}
|
}
|
||||||
|
|
@ -110,11 +110,12 @@ func abigen(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
// If the entire solidity code was specified, build and bind based on that
|
// If the entire solidity code was specified, build and bind based on that
|
||||||
var (
|
var (
|
||||||
abis []string
|
abis []string
|
||||||
bins []string
|
bins []string
|
||||||
types []string
|
types []string
|
||||||
sigs []map[string]string
|
sigs []map[string]string
|
||||||
libs = make(map[string]string)
|
libs = make(map[string]string)
|
||||||
|
aliases = make(map[string]string)
|
||||||
)
|
)
|
||||||
if c.String(abiFlag.Name) != "" {
|
if c.String(abiFlag.Name) != "" {
|
||||||
// Load up the ABI, optional bytecode and type name from the parameters
|
// Load up the ABI, optional bytecode and type name from the parameters
|
||||||
|
|
@ -151,16 +152,28 @@ func abigen(c *cli.Context) error {
|
||||||
types = append(types, kind)
|
types = append(types, kind)
|
||||||
} else {
|
} else {
|
||||||
// Generate the list of types to exclude from binding
|
// Generate the list of types to exclude from binding
|
||||||
exclude := make(map[string]bool)
|
var exclude *nameFilter
|
||||||
for _, kind := range strings.Split(c.String(excFlag.Name), ",") {
|
if c.IsSet(excFlag.Name) {
|
||||||
exclude[strings.ToLower(kind)] = true
|
var err error
|
||||||
|
if exclude, err = newNameFilter(strings.Split(c.String(excFlag.Name), ",")...); err != nil {
|
||||||
|
utils.Fatalf("Failed to parse excludes: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var contracts map[string]*compiler.Contract
|
var contracts map[string]*compiler.Contract
|
||||||
|
|
||||||
if c.IsSet(jsonFlag.Name) {
|
if c.IsSet(jsonFlag.Name) {
|
||||||
jsonOutput, err := os.ReadFile(c.String(jsonFlag.Name))
|
var (
|
||||||
|
input = c.String(jsonFlag.Name)
|
||||||
|
jsonOutput []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if input == "-" {
|
||||||
|
jsonOutput, err = io.ReadAll(os.Stdin)
|
||||||
|
} else {
|
||||||
|
jsonOutput, err = os.ReadFile(input)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to read combined-json from compiler: %v", err)
|
utils.Fatalf("Failed to read combined-json: %v", err)
|
||||||
}
|
}
|
||||||
contracts, err = compiler.ParseCombinedJSON(jsonOutput, "", "", "", "")
|
contracts, err = compiler.ParseCombinedJSON(jsonOutput, "", "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -169,7 +182,11 @@ func abigen(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
// Gather all non-excluded contract for binding
|
// Gather all non-excluded contract for binding
|
||||||
for name, contract := range contracts {
|
for name, contract := range contracts {
|
||||||
if exclude[strings.ToLower(name)] {
|
// fully qualified name is of the form <solFilePath>:<type>
|
||||||
|
nameParts := strings.Split(name, ":")
|
||||||
|
typeName := nameParts[len(nameParts)-1]
|
||||||
|
if exclude != nil && exclude.Matches(name) {
|
||||||
|
fmt.Fprintf(os.Stderr, "excluding: %v\n", name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
abi, err := json.Marshal(contract.Info.AbiDefinition) // Flatten the compiler parse
|
abi, err := json.Marshal(contract.Info.AbiDefinition) // Flatten the compiler parse
|
||||||
|
|
@ -179,15 +196,30 @@ func abigen(c *cli.Context) error {
|
||||||
abis = append(abis, string(abi))
|
abis = append(abis, string(abi))
|
||||||
bins = append(bins, contract.Code)
|
bins = append(bins, contract.Code)
|
||||||
sigs = append(sigs, contract.Hashes)
|
sigs = append(sigs, contract.Hashes)
|
||||||
nameParts := strings.Split(name, ":")
|
types = append(types, typeName)
|
||||||
types = append(types, nameParts[len(nameParts)-1])
|
|
||||||
|
|
||||||
libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36]
|
// Derive the library placeholder which is a 34 character prefix of the
|
||||||
libs[libPattern] = nameParts[len(nameParts)-1]
|
// hex encoding of the keccak256 hash of the fully qualified library name.
|
||||||
|
// Note that the fully qualified library name is the path of its source
|
||||||
|
// file and the library name separated by ":".
|
||||||
|
libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36] // the first 2 chars are 0x
|
||||||
|
libs[libPattern] = typeName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Extract all aliases from the flags
|
||||||
|
if c.IsSet(aliasFlag.Name) {
|
||||||
|
// We support multi-versions for aliasing
|
||||||
|
// e.g.
|
||||||
|
// foo=bar,foo2=bar2
|
||||||
|
// foo:bar,foo2:bar2
|
||||||
|
re := regexp.MustCompile(`(?:(\w+)[:=](\w+))`)
|
||||||
|
submatches := re.FindAllStringSubmatch(c.String(aliasFlag.Name), -1)
|
||||||
|
for _, match := range submatches {
|
||||||
|
aliases[match[1]] = match[2]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Generate the contract binding
|
// Generate the contract binding
|
||||||
code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs)
|
code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to generate ABI binding: %v", err)
|
utils.Fatalf("Failed to generate ABI binding: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
cmd/abigen/namefilter.go
Normal file
58
cmd/abigen/namefilter.go
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type nameFilter struct {
|
||||||
|
fulls map[string]bool // path/to/contract.sol:Type
|
||||||
|
files map[string]bool // path/to/contract.sol:*
|
||||||
|
types map[string]bool // *:Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNameFilter(patterns ...string) (*nameFilter, error) {
|
||||||
|
f := &nameFilter{
|
||||||
|
fulls: make(map[string]bool),
|
||||||
|
files: make(map[string]bool),
|
||||||
|
types: make(map[string]bool),
|
||||||
|
}
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
if err := f.add(pattern); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *nameFilter) add(pattern string) error {
|
||||||
|
ft := strings.Split(pattern, ":")
|
||||||
|
if len(ft) != 2 {
|
||||||
|
// filenames and types must not include ':' symbol
|
||||||
|
return fmt.Errorf("invalid pattern: %s", pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, typ := ft[0], ft[1]
|
||||||
|
if file == "*" {
|
||||||
|
f.types[typ] = true
|
||||||
|
return nil
|
||||||
|
} else if typ == "*" {
|
||||||
|
f.files[file] = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
f.fulls[pattern] = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *nameFilter) Matches(name string) bool {
|
||||||
|
ft := strings.Split(name, ":")
|
||||||
|
if len(ft) != 2 {
|
||||||
|
// If contract names are always of the fully-qualified form
|
||||||
|
// <filePath>:<type>, then this case will never happen.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
file, typ := ft[0], ft[1]
|
||||||
|
// full paths > file paths > types
|
||||||
|
return f.fulls[name] || f.files[file] || f.types[typ]
|
||||||
|
}
|
||||||
39
cmd/abigen/namefilter_test.go
Normal file
39
cmd/abigen/namefilter_test.go
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNameFilter(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_, err := newNameFilter("Foo")
|
||||||
|
require.Error(t, err)
|
||||||
|
_, err = newNameFilter("too/many:colons:Foo")
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
f, err := newNameFilter("a/path:A", "*:B", "c/path:*")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
match bool
|
||||||
|
}{
|
||||||
|
{"a/path:A", true},
|
||||||
|
{"unknown/path:A", false},
|
||||||
|
{"a/path:X", false},
|
||||||
|
{"unknown/path:X", false},
|
||||||
|
{"any/path:B", true},
|
||||||
|
{"c/path:X", true},
|
||||||
|
{"c/path:foo:B", false},
|
||||||
|
} {
|
||||||
|
match := f.Matches(tt.name)
|
||||||
|
if tt.match {
|
||||||
|
assert.True(t, match, "expected match")
|
||||||
|
} else {
|
||||||
|
assert.False(t, match, "expected no match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,7 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
||||||
"github.com/XinFinOrg/XDPoSChain/cmd/utils"
|
"github.com/XinFinOrg/XDPoSChain/cmd/utils"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
"github.com/pborman/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -85,9 +85,12 @@ If you want to encrypt an existing private key, it can be specified by setting
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the keyfile object with a random UUID.
|
// Create the keyfile object with a random UUID.
|
||||||
id := uuid.NewRandom()
|
UUID, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to generate random uuid: %v", err)
|
||||||
|
}
|
||||||
key := &keystore.Key{
|
key := &keystore.Key{
|
||||||
Id: id,
|
Id: UUID,
|
||||||
Address: crypto.PubkeyToAddress(privateKey.PublicKey),
|
Address: crypto.PubkeyToAddress(privateKey.PublicKey),
|
||||||
PrivateKey: privateKey,
|
PrivateKey: privateKey,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,17 +17,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMessageSignVerify(t *testing.T) {
|
func TestMessageSignVerify(t *testing.T) {
|
||||||
tmpdir, err := os.MkdirTemp("", "ethkey-test")
|
tmpdir := t.TempDir()
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Can't create temporary directory:", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpdir)
|
|
||||||
|
|
||||||
keyfile := filepath.Join(tmpdir, "the-keyfile")
|
keyfile := filepath.Join(tmpdir, "the-keyfile")
|
||||||
message := "test message"
|
message := "test message"
|
||||||
|
|
|
||||||
|
|
@ -156,18 +156,19 @@ func main() {
|
||||||
if blob, err = os.ReadFile(*accPassFlag); err != nil {
|
if blob, err = os.ReadFile(*accPassFlag); err != nil {
|
||||||
log.Crit("Failed to read account password contents", "file", *accPassFlag, "err", err)
|
log.Crit("Failed to read account password contents", "file", *accPassFlag, "err", err)
|
||||||
}
|
}
|
||||||
pass := string(blob)
|
pass := strings.TrimSuffix(string(blob), "\n")
|
||||||
|
|
||||||
ks := keystore.NewKeyStore(filepath.Join(os.Getenv("HOME"), ".faucet", "keys"), keystore.StandardScryptN, keystore.StandardScryptP)
|
ks := keystore.NewKeyStore(filepath.Join(os.Getenv("HOME"), ".faucet", "keys"), keystore.StandardScryptN, keystore.StandardScryptP)
|
||||||
if blob, err = os.ReadFile(*accJSONFlag); err != nil {
|
if blob, err = os.ReadFile(*accJSONFlag); err != nil {
|
||||||
log.Crit("Failed to read account key contents", "file", *accJSONFlag, "err", err)
|
log.Crit("Failed to read account key contents", "file", *accJSONFlag, "err", err)
|
||||||
}
|
}
|
||||||
acc, err := ks.Import(blob, pass, pass)
|
acc, err := ks.Import(blob, pass, pass)
|
||||||
if err != nil {
|
if err != nil && err != keystore.ErrAccountAlreadyExists {
|
||||||
log.Crit("Failed to import faucet signer account", "err", err)
|
log.Crit("Failed to import faucet signer account", "err", err)
|
||||||
}
|
}
|
||||||
ks.Unlock(acc, pass)
|
if err := ks.Unlock(acc, pass); err != nil {
|
||||||
|
log.Crit("Failed to unlock faucet signer account", "err", err)
|
||||||
|
}
|
||||||
// Assemble and start the faucet light service
|
// Assemble and start the faucet light service
|
||||||
faucet, err := newFaucet(genesis, *ethPortFlag, enodes, *netFlag, *statsFlag, ks, website.Bytes())
|
faucet, err := newFaucet(genesis, *ethPortFlag, enodes, *netFlag, *statsFlag, ks, website.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -710,8 +711,11 @@ func authTwitter(url string) (string, string, common.Address, error) {
|
||||||
return "", "", common.Address{}, errors.New("invalid Twitter status URL")
|
return "", "", common.Address{}, errors.New("invalid Twitter status URL")
|
||||||
}
|
}
|
||||||
// Twitter's API isn't really friendly with direct links. Still, we don't
|
// Twitter's API isn't really friendly with direct links. Still, we don't
|
||||||
// want to do ask read permissions from users, so just load the public posts and
|
// want to do ask read permissions from users, so just load the public posts
|
||||||
// scrape it for the Ethereum address and profile URL.
|
// and scrape it for the Ethereum address and profile URL. We need to load
|
||||||
|
// the mobile page though since the main page loads tweet contents via JS.
|
||||||
|
url = strings.Replace(url, "https://twitter.com/", "https://mobile.twitter.com/", 1)
|
||||||
|
|
||||||
res, err := http.Get(url)
|
res, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", common.Address{}, err
|
return "", "", common.Address{}, err
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue