github.com/neatlab/neatio@v1.7.3-0.20220425043230-d903e92fcc75/chain/accounts/abi/bind/base.go (about)

     1  package bind
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math/big"
     8  
     9  	"github.com/neatlab/neatio"
    10  	"github.com/neatlab/neatio/chain/accounts/abi"
    11  	"github.com/neatlab/neatio/chain/core/types"
    12  	"github.com/neatlab/neatio/utilities/common"
    13  	"github.com/neatlab/neatio/utilities/crypto"
    14  	"github.com/neatlab/neatio/utilities/event"
    15  )
    16  
    17  type SignerFn func(types.Signer, common.Address, *types.Transaction) (*types.Transaction, error)
    18  
    19  type CallOpts struct {
    20  	Pending     bool
    21  	From        common.Address
    22  	BlockNumber *big.Int
    23  	Context     context.Context
    24  }
    25  
    26  type TransactOpts struct {
    27  	From   common.Address
    28  	Nonce  *big.Int
    29  	Signer SignerFn
    30  
    31  	Value    *big.Int
    32  	GasPrice *big.Int
    33  	GasLimit uint64
    34  
    35  	Context context.Context
    36  }
    37  
    38  type FilterOpts struct {
    39  	Start uint64
    40  	End   *uint64
    41  
    42  	Context context.Context
    43  }
    44  
    45  type WatchOpts struct {
    46  	Start   *uint64
    47  	Context context.Context
    48  }
    49  
    50  type BoundContract struct {
    51  	address    common.Address
    52  	abi        abi.ABI
    53  	caller     ContractCaller
    54  	transactor ContractTransactor
    55  	filterer   ContractFilterer
    56  }
    57  
    58  func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor, filterer ContractFilterer) *BoundContract {
    59  	return &BoundContract{
    60  		address:    address,
    61  		abi:        abi,
    62  		caller:     caller,
    63  		transactor: transactor,
    64  		filterer:   filterer,
    65  	}
    66  }
    67  
    68  func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (common.Address, *types.Transaction, *BoundContract, error) {
    69  
    70  	c := NewBoundContract(common.Address{}, abi, backend, backend, backend)
    71  
    72  	input, err := c.abi.Pack("", params...)
    73  	if err != nil {
    74  		return common.Address{}, nil, nil, err
    75  	}
    76  	tx, err := c.transact(opts, nil, append(bytecode, input...))
    77  	if err != nil {
    78  		return common.Address{}, nil, nil, err
    79  	}
    80  	c.address = crypto.CreateAddress(opts.From, tx.Nonce())
    81  	return c.address, tx, c, nil
    82  }
    83  
    84  func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, params ...interface{}) error {
    85  
    86  	if opts == nil {
    87  		opts = new(CallOpts)
    88  	}
    89  
    90  	input, err := c.abi.Pack(method, params...)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	var (
    95  		msg    = neatio.CallMsg{From: opts.From, To: &c.address, Data: input}
    96  		ctx    = ensureContext(opts.Context)
    97  		code   []byte
    98  		output []byte
    99  	)
   100  	if opts.Pending {
   101  		pb, ok := c.caller.(PendingContractCaller)
   102  		if !ok {
   103  			return ErrNoPendingState
   104  		}
   105  		output, err = pb.PendingCallContract(ctx, msg)
   106  		if err == nil && len(output) == 0 {
   107  
   108  			if code, err = pb.PendingCodeAt(ctx, c.address); err != nil {
   109  				return err
   110  			} else if len(code) == 0 {
   111  				return ErrNoCode
   112  			}
   113  		}
   114  	} else {
   115  		output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
   116  		if err == nil && len(output) == 0 {
   117  
   118  			if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil {
   119  				return err
   120  			} else if len(code) == 0 {
   121  				return ErrNoCode
   122  			}
   123  		}
   124  	}
   125  	if err != nil {
   126  		return err
   127  	}
   128  	return c.abi.Unpack(result, method, output)
   129  }
   130  
   131  func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
   132  
   133  	input, err := c.abi.Pack(method, params...)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	return c.transact(opts, &c.address, input)
   138  }
   139  
   140  func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
   141  	return c.transact(opts, &c.address, nil)
   142  }
   143  
   144  func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) {
   145  	var err error
   146  
   147  	value := opts.Value
   148  	if value == nil {
   149  		value = new(big.Int)
   150  	}
   151  	var nonce uint64
   152  	if opts.Nonce == nil {
   153  		nonce, err = c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From)
   154  		if err != nil {
   155  			return nil, fmt.Errorf("failed to retrieve account nonce: %v", err)
   156  		}
   157  	} else {
   158  		nonce = opts.Nonce.Uint64()
   159  	}
   160  
   161  	gasPrice := opts.GasPrice
   162  	if gasPrice == nil {
   163  		gasPrice, err = c.transactor.SuggestGasPrice(ensureContext(opts.Context))
   164  		if err != nil {
   165  			return nil, fmt.Errorf("failed to suggest gas price: %v", err)
   166  		}
   167  	}
   168  	gasLimit := opts.GasLimit
   169  	if gasLimit == 0 {
   170  
   171  		if contract != nil {
   172  			if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil {
   173  				return nil, err
   174  			} else if len(code) == 0 {
   175  				return nil, ErrNoCode
   176  			}
   177  		}
   178  
   179  		msg := neatio.CallMsg{From: opts.From, To: contract, GasPrice: gasPrice, Value: value, Data: input}
   180  		gasLimit, err = c.transactor.EstimateGas(ensureContext(opts.Context), msg)
   181  		if err != nil {
   182  			return nil, fmt.Errorf("failed to estimate gas needed: %v", err)
   183  		}
   184  	}
   185  
   186  	var rawTx *types.Transaction
   187  	if contract == nil {
   188  		rawTx = types.NewContractCreation(nonce, value, gasLimit, gasPrice, input)
   189  	} else {
   190  		rawTx = types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input)
   191  	}
   192  	if opts.Signer == nil {
   193  		return nil, errors.New("no signer to authorize the transaction with")
   194  	}
   195  	signedTx, err := opts.Signer(types.HomesteadSigner{}, opts.From, rawTx)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil {
   200  		return nil, err
   201  	}
   202  	return signedTx, nil
   203  }
   204  
   205  func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
   206  
   207  	if opts == nil {
   208  		opts = new(FilterOpts)
   209  	}
   210  
   211  	query = append([][]interface{}{{c.abi.Events[name].ID()}}, query...)
   212  
   213  	topics, err := makeTopics(query...)
   214  	if err != nil {
   215  		return nil, nil, err
   216  	}
   217  
   218  	logs := make(chan types.Log, 128)
   219  
   220  	config := neatio.FilterQuery{
   221  		Addresses: []common.Address{c.address},
   222  		Topics:    topics,
   223  		FromBlock: new(big.Int).SetUint64(opts.Start),
   224  	}
   225  	if opts.End != nil {
   226  		config.ToBlock = new(big.Int).SetUint64(*opts.End)
   227  	}
   228  
   229  	buff, err := c.filterer.FilterLogs(ensureContext(opts.Context), config)
   230  	if err != nil {
   231  		return nil, nil, err
   232  	}
   233  	sub, err := event.NewSubscription(func(quit <-chan struct{}) error {
   234  		for _, log := range buff {
   235  			select {
   236  			case logs <- log:
   237  			case <-quit:
   238  				return nil
   239  			}
   240  		}
   241  		return nil
   242  	}), nil
   243  
   244  	if err != nil {
   245  		return nil, nil, err
   246  	}
   247  	return logs, sub, nil
   248  }
   249  
   250  func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
   251  
   252  	if opts == nil {
   253  		opts = new(WatchOpts)
   254  	}
   255  
   256  	query = append([][]interface{}{{c.abi.Events[name].ID()}}, query...)
   257  
   258  	topics, err := makeTopics(query...)
   259  	if err != nil {
   260  		return nil, nil, err
   261  	}
   262  
   263  	logs := make(chan types.Log, 128)
   264  
   265  	config := neatio.FilterQuery{
   266  		Addresses: []common.Address{c.address},
   267  		Topics:    topics,
   268  	}
   269  	if opts.Start != nil {
   270  		config.FromBlock = new(big.Int).SetUint64(*opts.Start)
   271  	}
   272  	sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs)
   273  	if err != nil {
   274  		return nil, nil, err
   275  	}
   276  	return logs, sub, nil
   277  }
   278  
   279  func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) error {
   280  	if len(log.Data) > 0 {
   281  		if err := c.abi.Unpack(out, event, log.Data); err != nil {
   282  			return err
   283  		}
   284  	}
   285  	var indexed abi.Arguments
   286  	for _, arg := range c.abi.Events[event].Inputs {
   287  		if arg.Indexed {
   288  			indexed = append(indexed, arg)
   289  		}
   290  	}
   291  	return parseTopics(out, indexed, log.Topics[1:])
   292  }
   293  
   294  func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event string, log types.Log) error {
   295  	if len(log.Data) > 0 {
   296  		if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil {
   297  			return err
   298  		}
   299  	}
   300  	var indexed abi.Arguments
   301  	for _, arg := range c.abi.Events[event].Inputs {
   302  		if arg.Indexed {
   303  			indexed = append(indexed, arg)
   304  		}
   305  	}
   306  	return parseTopicsIntoMap(out, indexed, log.Topics[1:])
   307  }
   308  
   309  func ensureContext(ctx context.Context) context.Context {
   310  	if ctx == nil {
   311  		return context.TODO()
   312  	}
   313  	return ctx
   314  }