github.com/intfoundation/intchain@v0.0.0-20220727031208-4316ad31ca73/contracts/chequebook/cheque.go (about)

     1  // Copyright 2016 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package chequebook package wraps the 'chequebook' INT Chain smart contract.
    18  //
    19  // The functions in this package allow using chequebook for
    20  // issuing, receiving, verifying cheques in ether; (auto)cashing cheques in ether
    21  // as well as (auto)depositing ether to the chequebook contract.
    22  package chequebook
    23  
    24  //go:generate abigen --sol contract/chequebook.sol --exc contract/mortal.sol:mortal,contract/owned.sol:owned --pkg contract --out contract/chequebook.go
    25  //go:generate go run ./gencode.go
    26  
    27  import (
    28  	"bytes"
    29  	"context"
    30  	"crypto/ecdsa"
    31  	"encoding/json"
    32  	"fmt"
    33  	"io/ioutil"
    34  	"math/big"
    35  	"os"
    36  	"sync"
    37  	"time"
    38  
    39  	"github.com/intfoundation/intchain/accounts/abi/bind"
    40  	"github.com/intfoundation/intchain/common"
    41  	"github.com/intfoundation/intchain/common/hexutil"
    42  	"github.com/intfoundation/intchain/contracts/chequebook/contract"
    43  	"github.com/intfoundation/intchain/core/types"
    44  	"github.com/intfoundation/intchain/crypto"
    45  	"github.com/intfoundation/intchain/log"
    46  )
    47  
    48  // TODO(zelig): watch peer solvency and notify of bouncing cheques
    49  // TODO(zelig): enable paying with cheque by signing off
    50  
    51  // Some functionality requires interacting with the blockchain:
    52  // * setting current balance on peer's chequebook
    53  // * sending the transaction to cash the cheque
    54  // * depositing ether to the chequebook
    55  // * watching incoming ether
    56  
    57  var (
    58  	gasToCash = uint64(2000000) // gas cost of a cash transaction using chequebook
    59  	// gasToDeploy = uint64(3000000)
    60  )
    61  
    62  // Backend wraps all methods required for chequebook operation.
    63  type Backend interface {
    64  	bind.ContractBackend
    65  	TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
    66  	BalanceAt(ctx context.Context, address common.Address, blockNum *big.Int) (*big.Int, error)
    67  }
    68  
    69  // Cheque represents a payment promise to a single beneficiary.
    70  type Cheque struct {
    71  	Contract    common.Address // address of chequebook, needed to avoid cross-contract submission
    72  	Beneficiary common.Address
    73  	Amount      *big.Int // cumulative amount of all funds sent
    74  	Sig         []byte   // signature Sign(Keccak256(contract, beneficiary, amount), prvKey)
    75  }
    76  
    77  func (self *Cheque) String() string {
    78  	//return fmt.Sprintf("contract: %s, beneficiary: %s, amount: %v, signature: %x", self.Contract.Hex(), self.Beneficiary.Hex(), self.Amount, self.Sig)
    79  	return fmt.Sprintf("contract: %s, beneficiary: %s, amount: %v, signature: %x", self.Contract.String(), self.Beneficiary.String(), self.Amount, self.Sig)
    80  }
    81  
    82  type Params struct {
    83  	ContractCode, ContractAbi string
    84  }
    85  
    86  var ContractParams = &Params{contract.ChequebookBin, contract.ChequebookABI}
    87  
    88  // Chequebook can create and sign cheques from a single contract to multiple beneficiaries.
    89  // It is the outgoing payment handler for peer to peer micropayments.
    90  type Chequebook struct {
    91  	path     string                      // path to chequebook file
    92  	prvKey   *ecdsa.PrivateKey           // private key to sign cheque with
    93  	lock     sync.Mutex                  //
    94  	backend  Backend                     // blockchain API
    95  	quit     chan bool                   // when closed causes autodeposit to stop
    96  	owner    common.Address              // owner address (derived from pubkey)
    97  	contract *contract.Chequebook        // abigen binding
    98  	session  *contract.ChequebookSession // abigen binding with Tx Opts
    99  
   100  	// persisted fields
   101  	balance      *big.Int                    // not synced with blockchain
   102  	contractAddr common.Address              // contract address
   103  	sent         map[common.Address]*big.Int //tallies for beneficiaries
   104  
   105  	txhash    string   // tx hash of last deposit tx
   106  	threshold *big.Int // threshold that triggers autodeposit if not nil
   107  	buffer    *big.Int // buffer to keep on top of balance for fork protection
   108  
   109  	log log.Logger // contextual logger with the contract address embedded
   110  }
   111  
   112  func (self *Chequebook) String() string {
   113  	//return fmt.Sprintf("contract: %s, owner: %s, balance: %v, signer: %x", self.contractAddr.Hex(), self.owner.Hex(), self.balance, self.prvKey.PublicKey)
   114  	return fmt.Sprintf("contract: %s, owner: %s, balance: %v, signer: %x", self.contractAddr.String(), self.owner.String(), self.balance, self.prvKey.PublicKey)
   115  }
   116  
   117  // NewChequebook creates a new Chequebook.
   118  func NewChequebook(path string, contractAddr common.Address, prvKey *ecdsa.PrivateKey, backend Backend) (self *Chequebook, err error) {
   119  	balance := new(big.Int)
   120  	sent := make(map[common.Address]*big.Int)
   121  
   122  	chbook, err := contract.NewChequebook(contractAddr, backend)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	transactOpts := bind.NewKeyedTransactor(prvKey)
   127  	session := &contract.ChequebookSession{
   128  		Contract:     chbook,
   129  		TransactOpts: *transactOpts,
   130  	}
   131  
   132  	self = &Chequebook{
   133  		prvKey:       prvKey,
   134  		balance:      balance,
   135  		contractAddr: contractAddr,
   136  		sent:         sent,
   137  		path:         path,
   138  		backend:      backend,
   139  		owner:        transactOpts.From,
   140  		contract:     chbook,
   141  		session:      session,
   142  		log:          log.New("contract", contractAddr),
   143  	}
   144  
   145  	if (contractAddr != common.Address{}) {
   146  		self.setBalanceFromBlockChain()
   147  		self.log.Trace("New chequebook initialised", "owner", self.owner, "balance", self.balance)
   148  	}
   149  	return
   150  }
   151  
   152  func (self *Chequebook) setBalanceFromBlockChain() {
   153  	balance, err := self.backend.BalanceAt(context.TODO(), self.contractAddr, nil)
   154  	if err != nil {
   155  		log.Error("Failed to retrieve chequebook balance", "err", err)
   156  	} else {
   157  		self.balance.Set(balance)
   158  	}
   159  }
   160  
   161  // LoadChequebook loads a chequebook from disk (file path).
   162  func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend, checkBalance bool) (self *Chequebook, err error) {
   163  	var data []byte
   164  	data, err = ioutil.ReadFile(path)
   165  	if err != nil {
   166  		return
   167  	}
   168  	self, _ = NewChequebook(path, common.Address{}, prvKey, backend)
   169  
   170  	err = json.Unmarshal(data, self)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	if checkBalance {
   175  		self.setBalanceFromBlockChain()
   176  	}
   177  	log.Trace("Loaded chequebook from disk", "path", path)
   178  
   179  	return
   180  }
   181  
   182  // chequebookFile is the JSON representation of a chequebook.
   183  type chequebookFile struct {
   184  	Balance  string
   185  	Contract string
   186  	Owner    string
   187  	Sent     map[string]string
   188  }
   189  
   190  // UnmarshalJSON deserialises a chequebook.
   191  func (self *Chequebook) UnmarshalJSON(data []byte) error {
   192  	var file chequebookFile
   193  	err := json.Unmarshal(data, &file)
   194  	if err != nil {
   195  		return err
   196  	}
   197  	_, ok := self.balance.SetString(file.Balance, 10)
   198  	if !ok {
   199  		return fmt.Errorf("cumulative amount sent: unable to convert string to big integer: %v", file.Balance)
   200  	}
   201  	self.contractAddr = common.HexToAddress(file.Contract)
   202  	for addr, sent := range file.Sent {
   203  		self.sent[common.HexToAddress(addr)], ok = new(big.Int).SetString(sent, 10)
   204  		if !ok {
   205  			return fmt.Errorf("beneficiary %v cumulative amount sent: unable to convert string to big integer: %v", addr, sent)
   206  		}
   207  	}
   208  	return nil
   209  }
   210  
   211  // MarshalJSON serialises a chequebook.
   212  func (self *Chequebook) MarshalJSON() ([]byte, error) {
   213  	var file = &chequebookFile{
   214  		Balance: self.balance.String(),
   215  		//Contract: self.contractAddr.Hex(),
   216  		Contract: self.contractAddr.String(),
   217  		//Owner:    self.owner.Hex(),
   218  		Owner: self.owner.String(),
   219  		Sent:  make(map[string]string),
   220  	}
   221  	for addr, sent := range self.sent {
   222  		//file.Sent[addr.Hex()] = sent.String()
   223  		file.Sent[addr.String()] = sent.String()
   224  	}
   225  	return json.Marshal(file)
   226  }
   227  
   228  // Save persists the chequebook on disk, remembering balance, contract address and
   229  // cumulative amount of funds sent for each beneficiary.
   230  func (self *Chequebook) Save() (err error) {
   231  	data, err := json.MarshalIndent(self, "", " ")
   232  	if err != nil {
   233  		return err
   234  	}
   235  	self.log.Trace("Saving chequebook to disk", self.path)
   236  
   237  	return ioutil.WriteFile(self.path, data, os.ModePerm)
   238  }
   239  
   240  // Stop quits the autodeposit go routine to terminate
   241  func (self *Chequebook) Stop() {
   242  	defer self.lock.Unlock()
   243  	self.lock.Lock()
   244  	if self.quit != nil {
   245  		close(self.quit)
   246  		self.quit = nil
   247  	}
   248  }
   249  
   250  // Issue creates a cheque signed by the chequebook owner's private key. The
   251  // signer commits to a contract (one that they own), a beneficiary and amount.
   252  func (self *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (ch *Cheque, err error) {
   253  	defer self.lock.Unlock()
   254  	self.lock.Lock()
   255  
   256  	if amount.Sign() <= 0 {
   257  		return nil, fmt.Errorf("amount must be greater than zero (%v)", amount)
   258  	}
   259  	if self.balance.Cmp(amount) < 0 {
   260  		err = fmt.Errorf("insufficient funds to issue cheque for amount: %v. balance: %v", amount, self.balance)
   261  	} else {
   262  		var sig []byte
   263  		sent, found := self.sent[beneficiary]
   264  		if !found {
   265  			sent = new(big.Int)
   266  			self.sent[beneficiary] = sent
   267  		}
   268  		sum := new(big.Int).Set(sent)
   269  		sum.Add(sum, amount)
   270  
   271  		sig, err = crypto.Sign(sigHash(self.contractAddr, beneficiary, sum), self.prvKey)
   272  		if err == nil {
   273  			ch = &Cheque{
   274  				Contract:    self.contractAddr,
   275  				Beneficiary: beneficiary,
   276  				Amount:      sum,
   277  				Sig:         sig,
   278  			}
   279  			sent.Set(sum)
   280  			self.balance.Sub(self.balance, amount) // subtract amount from balance
   281  		}
   282  	}
   283  
   284  	// auto deposit if threshold is set and balance is less then threshold
   285  	// note this is called even if issuing cheque fails
   286  	// so we reattempt depositing
   287  	if self.threshold != nil {
   288  		if self.balance.Cmp(self.threshold) < 0 {
   289  			send := new(big.Int).Sub(self.buffer, self.balance)
   290  			self.deposit(send)
   291  		}
   292  	}
   293  
   294  	return
   295  }
   296  
   297  // Cash is a convenience method to cash any cheque.
   298  func (self *Chequebook) Cash(ch *Cheque) (txhash string, err error) {
   299  	return ch.Cash(self.session)
   300  }
   301  
   302  // data to sign: contract address, beneficiary, cumulative amount of funds ever sent
   303  func sigHash(contract, beneficiary common.Address, sum *big.Int) []byte {
   304  	bigamount := sum.Bytes()
   305  	if len(bigamount) > 32 {
   306  		return nil
   307  	}
   308  	var amount32 [32]byte
   309  	copy(amount32[32-len(bigamount):32], bigamount)
   310  	input := append(contract.Bytes(), beneficiary.Bytes()...)
   311  	input = append(input, amount32[:]...)
   312  	return crypto.Keccak256(input)
   313  }
   314  
   315  // Balance returns the current balance of the chequebook.
   316  func (self *Chequebook) Balance() *big.Int {
   317  	defer self.lock.Unlock()
   318  	self.lock.Lock()
   319  	return new(big.Int).Set(self.balance)
   320  }
   321  
   322  // Owner returns the owner account of the chequebook.
   323  func (self *Chequebook) Owner() common.Address {
   324  	return self.owner
   325  }
   326  
   327  // Address returns the on-chain contract address of the chequebook.
   328  func (self *Chequebook) Address() common.Address {
   329  	return self.contractAddr
   330  }
   331  
   332  // Deposit deposits money to the chequebook account.
   333  func (self *Chequebook) Deposit(amount *big.Int) (string, error) {
   334  	defer self.lock.Unlock()
   335  	self.lock.Lock()
   336  	return self.deposit(amount)
   337  }
   338  
   339  // deposit deposits amount to the chequebook account.
   340  // The caller must hold self.lock.
   341  func (self *Chequebook) deposit(amount *big.Int) (string, error) {
   342  	// since the amount is variable here, we do not use sessions
   343  	depositTransactor := bind.NewKeyedTransactor(self.prvKey)
   344  	depositTransactor.Value = amount
   345  	chbookRaw := &contract.ChequebookRaw{Contract: self.contract}
   346  	tx, err := chbookRaw.Transfer(depositTransactor)
   347  	if err != nil {
   348  		self.log.Warn("Failed to fund chequebook", "amount", amount, "balance", self.balance, "target", self.buffer, "err", err)
   349  		return "", err
   350  	}
   351  	// assume that transaction is actually successful, we add the amount to balance right away
   352  	self.balance.Add(self.balance, amount)
   353  	self.log.Trace("Deposited funds to chequebook", "amount", amount, "balance", self.balance, "target", self.buffer)
   354  	return tx.Hash().Hex(), nil
   355  }
   356  
   357  // AutoDeposit (re)sets interval time and amount which triggers sending funds to the
   358  // chequebook. Contract backend needs to be set if threshold is not less than buffer, then
   359  // deposit will be triggered on every new cheque issued.
   360  func (self *Chequebook) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) {
   361  	defer self.lock.Unlock()
   362  	self.lock.Lock()
   363  	self.threshold = threshold
   364  	self.buffer = buffer
   365  	self.autoDeposit(interval)
   366  }
   367  
   368  // autoDeposit starts a goroutine that periodically sends funds to the chequebook
   369  // contract caller holds the lock the go routine terminates if Chequebook.quit is closed.
   370  func (self *Chequebook) autoDeposit(interval time.Duration) {
   371  	if self.quit != nil {
   372  		close(self.quit)
   373  		self.quit = nil
   374  	}
   375  	// if threshold >= balance autodeposit after every cheque issued
   376  	if interval == time.Duration(0) || self.threshold != nil && self.buffer != nil && self.threshold.Cmp(self.buffer) >= 0 {
   377  		return
   378  	}
   379  
   380  	ticker := time.NewTicker(interval)
   381  	self.quit = make(chan bool)
   382  	quit := self.quit
   383  
   384  	go func() {
   385  		for {
   386  			select {
   387  			case <-quit:
   388  				return
   389  			case <-ticker.C:
   390  				self.lock.Lock()
   391  				if self.balance.Cmp(self.buffer) < 0 {
   392  					amount := new(big.Int).Sub(self.buffer, self.balance)
   393  					txhash, err := self.deposit(amount)
   394  					if err == nil {
   395  						self.txhash = txhash
   396  					}
   397  				}
   398  				self.lock.Unlock()
   399  			}
   400  		}
   401  	}()
   402  }
   403  
   404  // Outbox can issue cheques from a single contract to a single beneficiary.
   405  type Outbox struct {
   406  	chequeBook  *Chequebook
   407  	beneficiary common.Address
   408  }
   409  
   410  // NewOutbox creates an outbox.
   411  func NewOutbox(chbook *Chequebook, beneficiary common.Address) *Outbox {
   412  	return &Outbox{chbook, beneficiary}
   413  }
   414  
   415  // AutoDeposit enables auto-deposits on the underlying chequebook.
   416  func (self *Outbox) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) {
   417  	self.chequeBook.AutoDeposit(interval, threshold, buffer)
   418  }
   419  
   420  // Stop helps satisfy the swap.OutPayment interface.
   421  func (self *Outbox) Stop() {}
   422  
   423  // String implements fmt.Stringer.
   424  func (self *Outbox) String() string {
   425  	//return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.chequeBook.Address().Hex(), self.beneficiary.Hex(), self.chequeBook.Balance())
   426  	return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.chequeBook.Address().String(), self.beneficiary.String(), self.chequeBook.Balance())
   427  }
   428  
   429  // Inbox can deposit, verify and cash cheques from a single contract to a single
   430  // beneficiary. It is the incoming payment handler for peer to peer micropayments.
   431  type Inbox struct {
   432  	lock        sync.Mutex
   433  	contract    common.Address              // peer's chequebook contract
   434  	beneficiary common.Address              // local peer's receiving address
   435  	sender      common.Address              // local peer's address to send cashing tx from
   436  	signer      *ecdsa.PublicKey            // peer's public key
   437  	txhash      string                      // tx hash of last cashing tx
   438  	session     *contract.ChequebookSession // abi contract backend with tx opts
   439  	quit        chan bool                   // when closed causes autocash to stop
   440  	maxUncashed *big.Int                    // threshold that triggers autocashing
   441  	cashed      *big.Int                    // cumulative amount cashed
   442  	cheque      *Cheque                     // last cheque, nil if none yet received
   443  	log         log.Logger                  // contextual logger with the contract address embedded
   444  }
   445  
   446  // NewInbox creates an Inbox. An Inboxes is not persisted, the cumulative sum is updated
   447  // from blockchain when first cheque is received.
   448  func NewInbox(prvKey *ecdsa.PrivateKey, contractAddr, beneficiary common.Address, signer *ecdsa.PublicKey, abigen bind.ContractBackend) (self *Inbox, err error) {
   449  	if signer == nil {
   450  		return nil, fmt.Errorf("signer is null")
   451  	}
   452  	chbook, err := contract.NewChequebook(contractAddr, abigen)
   453  	if err != nil {
   454  		return nil, err
   455  	}
   456  	transactOpts := bind.NewKeyedTransactor(prvKey)
   457  	transactOpts.GasLimit = gasToCash
   458  	session := &contract.ChequebookSession{
   459  		Contract:     chbook,
   460  		TransactOpts: *transactOpts,
   461  	}
   462  	sender := transactOpts.From
   463  
   464  	self = &Inbox{
   465  		contract:    contractAddr,
   466  		beneficiary: beneficiary,
   467  		sender:      sender,
   468  		signer:      signer,
   469  		session:     session,
   470  		cashed:      new(big.Int).Set(common.Big0),
   471  		log:         log.New("contract", contractAddr),
   472  	}
   473  	self.log.Trace("New chequebook inbox initialized", "beneficiary", self.beneficiary, "signer", hexutil.Bytes(crypto.FromECDSAPub(signer)))
   474  	return
   475  }
   476  
   477  func (self *Inbox) String() string {
   478  	//return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.contract.Hex(), self.beneficiary.Hex(), self.cheque.Amount)
   479  	return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.contract.String(), self.beneficiary.String(), self.cheque.Amount)
   480  }
   481  
   482  // Stop quits the autocash goroutine.
   483  func (self *Inbox) Stop() {
   484  	defer self.lock.Unlock()
   485  	self.lock.Lock()
   486  	if self.quit != nil {
   487  		close(self.quit)
   488  		self.quit = nil
   489  	}
   490  }
   491  
   492  // Cash attempts to cash the current cheque.
   493  func (self *Inbox) Cash() (txhash string, err error) {
   494  	if self.cheque != nil {
   495  		txhash, err = self.cheque.Cash(self.session)
   496  		self.log.Trace("Cashing in chequebook cheque", "amount", self.cheque.Amount, "beneficiary", self.beneficiary)
   497  		self.cashed = self.cheque.Amount
   498  	}
   499  	return
   500  }
   501  
   502  // AutoCash (re)sets maximum time and amount which triggers cashing of the last uncashed
   503  // cheque if maxUncashed is set to 0, then autocash on receipt.
   504  func (self *Inbox) AutoCash(cashInterval time.Duration, maxUncashed *big.Int) {
   505  	defer self.lock.Unlock()
   506  	self.lock.Lock()
   507  	self.maxUncashed = maxUncashed
   508  	self.autoCash(cashInterval)
   509  }
   510  
   511  // autoCash starts a loop that periodically clears the last cheque
   512  // if the peer is trusted. Clearing period could be 24h or a week.
   513  // The caller must hold self.lock.
   514  func (self *Inbox) autoCash(cashInterval time.Duration) {
   515  	if self.quit != nil {
   516  		close(self.quit)
   517  		self.quit = nil
   518  	}
   519  	// if maxUncashed is set to 0, then autocash on receipt
   520  	if cashInterval == time.Duration(0) || self.maxUncashed != nil && self.maxUncashed.Sign() == 0 {
   521  		return
   522  	}
   523  
   524  	ticker := time.NewTicker(cashInterval)
   525  	self.quit = make(chan bool)
   526  	quit := self.quit
   527  
   528  	go func() {
   529  		for {
   530  			select {
   531  			case <-quit:
   532  				return
   533  			case <-ticker.C:
   534  				self.lock.Lock()
   535  				if self.cheque != nil && self.cheque.Amount.Cmp(self.cashed) != 0 {
   536  					txhash, err := self.Cash()
   537  					if err == nil {
   538  						self.txhash = txhash
   539  					}
   540  				}
   541  				self.lock.Unlock()
   542  			}
   543  		}
   544  	}()
   545  }
   546  
   547  // Verify verifies cheque for signer, contract, beneficiary, amount, valid signature.
   548  func (self *Cheque) Verify(signerKey *ecdsa.PublicKey, contract, beneficiary common.Address, sum *big.Int) (*big.Int, error) {
   549  	log.Trace("Verifying chequebook cheque", "cheque", self, "sum", sum)
   550  	if sum == nil {
   551  		return nil, fmt.Errorf("invalid amount")
   552  	}
   553  
   554  	if self.Beneficiary != beneficiary {
   555  		//return nil, fmt.Errorf("beneficiary mismatch: %v != %v", self.Beneficiary.Hex(), beneficiary.Hex())
   556  		return nil, fmt.Errorf("beneficiary mismatch: %v != %v", self.Beneficiary.String(), beneficiary.String())
   557  	}
   558  	if self.Contract != contract {
   559  		//return nil, fmt.Errorf("contract mismatch: %v != %v", self.Contract.Hex(), contract.Hex())
   560  		return nil, fmt.Errorf("contract mismatch: %v != %v", self.Contract.String(), contract.String())
   561  	}
   562  
   563  	amount := new(big.Int).Set(self.Amount)
   564  	if sum != nil {
   565  		amount.Sub(amount, sum)
   566  		if amount.Sign() <= 0 {
   567  			return nil, fmt.Errorf("incorrect amount: %v <= 0", amount)
   568  		}
   569  	}
   570  
   571  	pubKey, err := crypto.SigToPub(sigHash(self.Contract, beneficiary, self.Amount), self.Sig)
   572  	if err != nil {
   573  		return nil, fmt.Errorf("invalid signature: %v", err)
   574  	}
   575  	if !bytes.Equal(crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) {
   576  		return nil, fmt.Errorf("signer mismatch: %x != %x", crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey))
   577  	}
   578  	return amount, nil
   579  }
   580  
   581  // v/r/s representation of signature
   582  func sig2vrs(sig []byte) (v byte, r, s [32]byte) {
   583  	v = sig[64] + 27
   584  	copy(r[:], sig[:32])
   585  	copy(s[:], sig[32:64])
   586  	return
   587  }
   588  
   589  // Cash cashes the cheque by sending an Ethereum transaction.
   590  func (self *Cheque) Cash(session *contract.ChequebookSession) (string, error) {
   591  	v, r, s := sig2vrs(self.Sig)
   592  	tx, err := session.Cash(self.Beneficiary, self.Amount, v, r, s)
   593  	if err != nil {
   594  		return "", err
   595  	}
   596  	return tx.Hash().Hex(), nil
   597  }
   598  
   599  // ValidateCode checks that the on-chain code at address matches the expected chequebook
   600  // contract code. This is used to detect suicided chequebooks.
   601  func ValidateCode(ctx context.Context, b Backend, address common.Address) (ok bool, err error) {
   602  	code, err := b.CodeAt(ctx, address, nil)
   603  	if err != nil {
   604  		return false, err
   605  	}
   606  	return bytes.Equal(code, common.FromHex(contract.ContractDeployedCode)), nil
   607  }