github.com/ChainSafe/chainbridge-core@v1.4.2/chains/evm/calls/transactor/itx/minimalForwarder.go (about)

     1  package itx
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  	"sync"
     7  
     8  	"github.com/ChainSafe/chainbridge-core/chains/evm/calls/contracts/forwarder"
     9  	"github.com/ChainSafe/chainbridge-core/chains/evm/calls/transactor"
    10  
    11  	"github.com/ethereum/go-ethereum/common"
    12  	"github.com/ethereum/go-ethereum/common/math"
    13  	"github.com/ethereum/go-ethereum/crypto"
    14  	signer "github.com/ethereum/go-ethereum/signer/core"
    15  	"github.com/rs/zerolog/log"
    16  )
    17  
    18  type ForwarderContract interface {
    19  	GetNonce(from common.Address) (*big.Int, error)
    20  	PrepareExecute(forwardReq forwarder.ForwardRequest, sig []byte) ([]byte, error)
    21  	ContractAddress() *common.Address
    22  }
    23  
    24  type NonceStorer interface {
    25  	StoreNonce(chainID *big.Int, nonce *big.Int) error
    26  	GetNonce(chainID *big.Int) (*big.Int, error)
    27  }
    28  
    29  type MinimalForwarder struct {
    30  	signer            Signer
    31  	nonce             *big.Int
    32  	nonceLock         sync.Mutex
    33  	chainID           *big.Int
    34  	forwarderContract ForwarderContract
    35  	nonceStore        NonceStorer
    36  }
    37  
    38  // NewMinimalForwarder creates an instance of MinimalForwarder
    39  func NewMinimalForwarder(chainID *big.Int, signer Signer, forwarderContract ForwarderContract, nonceStore NonceStorer) *MinimalForwarder {
    40  	return &MinimalForwarder{
    41  		chainID:           chainID,
    42  		signer:            signer,
    43  		forwarderContract: forwarderContract,
    44  		nonceStore:        nonceStore,
    45  	}
    46  }
    47  
    48  // LockNonce locks mutex for nonce to prevent nonce duplication
    49  func (c *MinimalForwarder) LockNonce() {
    50  	c.nonceLock.Lock()
    51  }
    52  
    53  // UnlockNonce unlocks mutext for nonce and stores nonce into storage.
    54  //
    55  // Nonce is stored on unlock, because current nonce should always be the correct one when unlocking.
    56  func (c *MinimalForwarder) UnlockNonce() {
    57  	err := c.nonceStore.StoreNonce(c.chainID, c.nonce)
    58  	if err != nil {
    59  		log.Error().Err(fmt.Errorf("failed storing nonce: %v", err))
    60  	}
    61  
    62  	c.nonceLock.Unlock()
    63  }
    64  
    65  // UnsafeNonce returns current valid nonce for a forwarded transaction.
    66  //
    67  // If nonce is not set, looks for nonce in storage and on contract and returns the
    68  // higher one. Nonce in storage can be higher if there are pending transactions after
    69  // relayer has been manually shutdown.
    70  func (c *MinimalForwarder) UnsafeNonce() (*big.Int, error) {
    71  	if c.nonce == nil {
    72  		storedNonce, err := c.nonceStore.GetNonce(c.chainID)
    73  		if err != nil {
    74  			return nil, err
    75  		}
    76  		from := c.signer.CommonAddress()
    77  		contractNonce, err := c.forwarderContract.GetNonce(from)
    78  		if err != nil {
    79  			return nil, err
    80  		}
    81  
    82  		var nonce *big.Int
    83  		if storedNonce.Cmp(contractNonce) >= 0 {
    84  			nonce = storedNonce
    85  		} else {
    86  			nonce = contractNonce
    87  		}
    88  
    89  		c.nonce = nonce
    90  	}
    91  
    92  	nonce := big.NewInt(c.nonce.Int64())
    93  	return nonce, nil
    94  }
    95  
    96  // UnsafeIncreaseNonce increases nonce value by 1. Should be used
    97  // while nonce is locked.
    98  func (c *MinimalForwarder) UnsafeIncreaseNonce() {
    99  	c.nonce.Add(c.nonce, big.NewInt(1))
   100  }
   101  
   102  func (c *MinimalForwarder) ForwarderAddress() common.Address {
   103  	return *c.forwarderContract.ContractAddress()
   104  }
   105  
   106  func (c *MinimalForwarder) ChainId() *big.Int {
   107  	return c.chainID
   108  }
   109  
   110  // ForwarderData returns ABI packed and signed byte data for a forwarded transaction
   111  func (c *MinimalForwarder) ForwarderData(to *common.Address, data []byte, opts transactor.TransactOptions) ([]byte, error) {
   112  	from := c.signer.CommonAddress().Hex()
   113  	forwarderHash, err := c.typedHash(
   114  		from,
   115  		to.String(),
   116  		data,
   117  		math.NewHexOrDecimal256(opts.Value.Int64()),
   118  		math.NewHexOrDecimal256(int64(opts.GasLimit)),
   119  		opts.Nonce,
   120  		c.ForwarderAddress().Hex(),
   121  	)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	sig, err := c.signer.Sign(forwarderHash)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	sig[64] += 27 // Transform V from 0/1 to 27/28
   131  
   132  	forwardReq := forwarder.ForwardRequest{
   133  		From:  common.HexToAddress(from),
   134  		To:    *to,
   135  		Value: opts.Value,
   136  		Gas:   big.NewInt(int64(opts.GasLimit)),
   137  		Nonce: opts.Nonce,
   138  		Data:  data,
   139  	}
   140  	return c.forwarderContract.PrepareExecute(forwardReq, sig)
   141  }
   142  
   143  func (c *MinimalForwarder) typedHash(
   144  	from, to string,
   145  	data []byte,
   146  	value, gas *math.HexOrDecimal256,
   147  	nonce *big.Int,
   148  	verifyingContract string,
   149  ) ([]byte, error) {
   150  	chainId := math.NewHexOrDecimal256(c.chainID.Int64())
   151  	typedData := signer.TypedData{
   152  		Types: signer.Types{
   153  			"EIP712Domain": []signer.Type{
   154  				{Name: "name", Type: "string"},
   155  				{Name: "version", Type: "string"},
   156  				{Name: "chainId", Type: "uint256"},
   157  				{Name: "verifyingContract", Type: "address"},
   158  			},
   159  			"ForwardRequest": []signer.Type{
   160  				{Name: "from", Type: "address"},
   161  				{Name: "to", Type: "address"},
   162  				{Name: "value", Type: "uint256"},
   163  				{Name: "gas", Type: "uint256"},
   164  				{Name: "nonce", Type: "uint256"},
   165  				{Name: "data", Type: "bytes"},
   166  			},
   167  		},
   168  		PrimaryType: "ForwardRequest",
   169  		Domain: signer.TypedDataDomain{
   170  			Name:              "Forwarder",
   171  			ChainId:           chainId,
   172  			Version:           "0.0.1",
   173  			VerifyingContract: verifyingContract,
   174  		},
   175  		Message: signer.TypedDataMessage{
   176  			"from":  from,
   177  			"to":    to,
   178  			"value": value,
   179  			"gas":   gas,
   180  			"data":  data,
   181  			"nonce": math.NewHexOrDecimal256(nonce.Int64()),
   182  		},
   183  	}
   184  
   185  	domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))
   196  	return crypto.Keccak256(rawData), nil
   197  }