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 }