github.com/nitinawathare/ethereumassignment3@v0.0.0-20211021213010-f07344c2b868/go-ethereum/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' Ethereum 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/ethereum/go-ethereum/accounts/abi/bind" 40 "github.com/ethereum/go-ethereum/common" 41 "github.com/ethereum/go-ethereum/common/hexutil" 42 "github.com/ethereum/go-ethereum/contracts/chequebook/contract" 43 "github.com/ethereum/go-ethereum/core/types" 44 "github.com/ethereum/go-ethereum/crypto" 45 "github.com/ethereum/go-ethereum/log" 46 "github.com/ethereum/go-ethereum/swarm/services/swap/swap" 47 ) 48 49 // TODO(zelig): watch peer solvency and notify of bouncing cheques 50 // TODO(zelig): enable paying with cheque by signing off 51 52 // Some functionality requires interacting with the blockchain: 53 // * setting current balance on peer's chequebook 54 // * sending the transaction to cash the cheque 55 // * depositing ether to the chequebook 56 // * watching incoming ether 57 58 var ( 59 gasToCash = uint64(2000000) // gas cost of a cash transaction using chequebook 60 // gasToDeploy = uint64(3000000) 61 ) 62 63 // Backend wraps all methods required for chequebook operation. 64 type Backend interface { 65 bind.ContractBackend 66 TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) 67 BalanceAt(ctx context.Context, address common.Address, blockNum *big.Int) (*big.Int, error) 68 } 69 70 // Cheque represents a payment promise to a single beneficiary. 71 type Cheque struct { 72 Contract common.Address // address of chequebook, needed to avoid cross-contract submission 73 Beneficiary common.Address 74 Amount *big.Int // cumulative amount of all funds sent 75 Sig []byte // signature Sign(Keccak256(contract, beneficiary, amount), prvKey) 76 } 77 78 func (ch *Cheque) String() string { 79 return fmt.Sprintf("contract: %s, beneficiary: %s, amount: %v, signature: %x", ch.Contract.Hex(), ch.Beneficiary.Hex(), ch.Amount, ch.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 (cb *Chequebook) String() string { 113 return fmt.Sprintf("contract: %s, owner: %s, balance: %v, signer: %x", cb.contractAddr.Hex(), cb.owner.Hex(), cb.balance, cb.prvKey.PublicKey) 114 } 115 116 // NewChequebook creates a new Chequebook. 117 func NewChequebook(path string, contractAddr common.Address, prvKey *ecdsa.PrivateKey, backend Backend) (*Chequebook, error) { 118 balance := new(big.Int) 119 sent := make(map[common.Address]*big.Int) 120 121 chbook, err := contract.NewChequebook(contractAddr, backend) 122 if err != nil { 123 return nil, err 124 } 125 transactOpts := bind.NewKeyedTransactor(prvKey) 126 session := &contract.ChequebookSession{ 127 Contract: chbook, 128 TransactOpts: *transactOpts, 129 } 130 131 cb := &Chequebook{ 132 prvKey: prvKey, 133 balance: balance, 134 contractAddr: contractAddr, 135 sent: sent, 136 path: path, 137 backend: backend, 138 owner: transactOpts.From, 139 contract: chbook, 140 session: session, 141 log: log.New("contract", contractAddr), 142 } 143 if (contractAddr != common.Address{}) { 144 cb.setBalanceFromBlockChain() 145 cb.log.Trace("New chequebook initialised", "owner", cb.owner, "balance", cb.balance) 146 } 147 return cb, nil 148 } 149 150 func (cb *Chequebook) setBalanceFromBlockChain() { 151 balance, err := cb.backend.BalanceAt(context.TODO(), cb.contractAddr, nil) 152 if err != nil { 153 log.Error("Failed to retrieve chequebook balance", "err", err) 154 } else { 155 cb.balance.Set(balance) 156 } 157 } 158 159 // LoadChequebook loads a chequebook from disk (file path). 160 func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend, checkBalance bool) (*Chequebook, error) { 161 data, err := ioutil.ReadFile(path) 162 if err != nil { 163 return nil, err 164 } 165 cb, _ := NewChequebook(path, common.Address{}, prvKey, backend) 166 167 if err = json.Unmarshal(data, cb); err != nil { 168 return nil, err 169 } 170 if checkBalance { 171 cb.setBalanceFromBlockChain() 172 } 173 log.Trace("Loaded chequebook from disk", "path", path) 174 175 return cb, nil 176 } 177 178 // chequebookFile is the JSON representation of a chequebook. 179 type chequebookFile struct { 180 Balance string 181 Contract string 182 Owner string 183 Sent map[string]string 184 } 185 186 // UnmarshalJSON deserialises a chequebook. 187 func (cb *Chequebook) UnmarshalJSON(data []byte) error { 188 var file chequebookFile 189 err := json.Unmarshal(data, &file) 190 if err != nil { 191 return err 192 } 193 _, ok := cb.balance.SetString(file.Balance, 10) 194 if !ok { 195 return fmt.Errorf("cumulative amount sent: unable to convert string to big integer: %v", file.Balance) 196 } 197 cb.contractAddr = common.HexToAddress(file.Contract) 198 for addr, sent := range file.Sent { 199 cb.sent[common.HexToAddress(addr)], ok = new(big.Int).SetString(sent, 10) 200 if !ok { 201 return fmt.Errorf("beneficiary %v cumulative amount sent: unable to convert string to big integer: %v", addr, sent) 202 } 203 } 204 return nil 205 } 206 207 // MarshalJSON serialises a chequebook. 208 func (cb *Chequebook) MarshalJSON() ([]byte, error) { 209 var file = &chequebookFile{ 210 Balance: cb.balance.String(), 211 Contract: cb.contractAddr.Hex(), 212 Owner: cb.owner.Hex(), 213 Sent: make(map[string]string), 214 } 215 for addr, sent := range cb.sent { 216 file.Sent[addr.Hex()] = sent.String() 217 } 218 return json.Marshal(file) 219 } 220 221 // Save persists the chequebook on disk, remembering balance, contract address and 222 // cumulative amount of funds sent for each beneficiary. 223 func (cb *Chequebook) Save() error { 224 data, err := json.MarshalIndent(cb, "", " ") 225 if err != nil { 226 return err 227 } 228 cb.log.Trace("Saving chequebook to disk", cb.path) 229 230 return ioutil.WriteFile(cb.path, data, os.ModePerm) 231 } 232 233 // Stop quits the autodeposit go routine to terminate 234 func (cb *Chequebook) Stop() { 235 defer cb.lock.Unlock() 236 cb.lock.Lock() 237 if cb.quit != nil { 238 close(cb.quit) 239 cb.quit = nil 240 } 241 } 242 243 // Issue creates a cheque signed by the chequebook owner's private key. The 244 // signer commits to a contract (one that they own), a beneficiary and amount. 245 func (cb *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (*Cheque, error) { 246 defer cb.lock.Unlock() 247 cb.lock.Lock() 248 249 if amount.Sign() <= 0 { 250 return nil, fmt.Errorf("amount must be greater than zero (%v)", amount) 251 } 252 var ( 253 ch *Cheque 254 err error 255 ) 256 if cb.balance.Cmp(amount) < 0 { 257 err = fmt.Errorf("insufficient funds to issue cheque for amount: %v. balance: %v", amount, cb.balance) 258 } else { 259 var sig []byte 260 sent, found := cb.sent[beneficiary] 261 if !found { 262 sent = new(big.Int) 263 cb.sent[beneficiary] = sent 264 } 265 sum := new(big.Int).Set(sent) 266 sum.Add(sum, amount) 267 268 sig, err = crypto.Sign(sigHash(cb.contractAddr, beneficiary, sum), cb.prvKey) 269 if err == nil { 270 ch = &Cheque{ 271 Contract: cb.contractAddr, 272 Beneficiary: beneficiary, 273 Amount: sum, 274 Sig: sig, 275 } 276 sent.Set(sum) 277 cb.balance.Sub(cb.balance, amount) // subtract amount from balance 278 } 279 } 280 // auto deposit if threshold is set and balance is less then threshold 281 // note this is called even if issuing cheque fails 282 // so we reattempt depositing 283 if cb.threshold != nil { 284 if cb.balance.Cmp(cb.threshold) < 0 { 285 send := new(big.Int).Sub(cb.buffer, cb.balance) 286 cb.deposit(send) 287 } 288 } 289 return ch, err 290 } 291 292 // Cash is a convenience method to cash any cheque. 293 func (cb *Chequebook) Cash(ch *Cheque) (string, error) { 294 return ch.Cash(cb.session) 295 } 296 297 // data to sign: contract address, beneficiary, cumulative amount of funds ever sent 298 func sigHash(contract, beneficiary common.Address, sum *big.Int) []byte { 299 bigamount := sum.Bytes() 300 if len(bigamount) > 32 { 301 return nil 302 } 303 var amount32 [32]byte 304 copy(amount32[32-len(bigamount):32], bigamount) 305 input := append(contract.Bytes(), beneficiary.Bytes()...) 306 input = append(input, amount32[:]...) 307 return crypto.Keccak256(input) 308 } 309 310 // Balance returns the current balance of the chequebook. 311 func (cb *Chequebook) Balance() *big.Int { 312 defer cb.lock.Unlock() 313 cb.lock.Lock() 314 return new(big.Int).Set(cb.balance) 315 } 316 317 // Owner returns the owner account of the chequebook. 318 func (cb *Chequebook) Owner() common.Address { 319 return cb.owner 320 } 321 322 // Address returns the on-chain contract address of the chequebook. 323 func (cb *Chequebook) Address() common.Address { 324 return cb.contractAddr 325 } 326 327 // Deposit deposits money to the chequebook account. 328 func (cb *Chequebook) Deposit(amount *big.Int) (string, error) { 329 defer cb.lock.Unlock() 330 cb.lock.Lock() 331 return cb.deposit(amount) 332 } 333 334 // deposit deposits amount to the chequebook account. 335 // The caller must hold lock. 336 func (cb *Chequebook) deposit(amount *big.Int) (string, error) { 337 // since the amount is variable here, we do not use sessions 338 depositTransactor := bind.NewKeyedTransactor(cb.prvKey) 339 depositTransactor.Value = amount 340 chbookRaw := &contract.ChequebookRaw{Contract: cb.contract} 341 tx, err := chbookRaw.Transfer(depositTransactor) 342 if err != nil { 343 cb.log.Warn("Failed to fund chequebook", "amount", amount, "balance", cb.balance, "target", cb.buffer, "err", err) 344 return "", err 345 } 346 // assume that transaction is actually successful, we add the amount to balance right away 347 cb.balance.Add(cb.balance, amount) 348 cb.log.Trace("Deposited funds to chequebook", "amount", amount, "balance", cb.balance, "target", cb.buffer) 349 return tx.Hash().Hex(), nil 350 } 351 352 // AutoDeposit (re)sets interval time and amount which triggers sending funds to the 353 // chequebook. Contract backend needs to be set if threshold is not less than buffer, then 354 // deposit will be triggered on every new cheque issued. 355 func (cb *Chequebook) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { 356 defer cb.lock.Unlock() 357 cb.lock.Lock() 358 cb.threshold = threshold 359 cb.buffer = buffer 360 cb.autoDeposit(interval) 361 } 362 363 // autoDeposit starts a goroutine that periodically sends funds to the chequebook 364 // contract caller holds the lock the go routine terminates if Chequebook.quit is closed. 365 func (cb *Chequebook) autoDeposit(interval time.Duration) { 366 if cb.quit != nil { 367 close(cb.quit) 368 cb.quit = nil 369 } 370 // if threshold >= balance autodeposit after every cheque issued 371 if interval == time.Duration(0) || cb.threshold != nil && cb.buffer != nil && cb.threshold.Cmp(cb.buffer) >= 0 { 372 return 373 } 374 375 ticker := time.NewTicker(interval) 376 cb.quit = make(chan bool) 377 quit := cb.quit 378 379 go func() { 380 for { 381 select { 382 case <-quit: 383 return 384 case <-ticker.C: 385 cb.lock.Lock() 386 if cb.balance.Cmp(cb.buffer) < 0 { 387 amount := new(big.Int).Sub(cb.buffer, cb.balance) 388 txhash, err := cb.deposit(amount) 389 if err == nil { 390 cb.txhash = txhash 391 } 392 } 393 cb.lock.Unlock() 394 } 395 } 396 }() 397 } 398 399 // Outbox can issue cheques from a single contract to a single beneficiary. 400 type Outbox struct { 401 chequeBook *Chequebook 402 beneficiary common.Address 403 } 404 405 // NewOutbox creates an outbox. 406 func NewOutbox(cb *Chequebook, beneficiary common.Address) *Outbox { 407 return &Outbox{cb, beneficiary} 408 } 409 410 // Issue creates cheque. 411 func (o *Outbox) Issue(amount *big.Int) (swap.Promise, error) { 412 return o.chequeBook.Issue(o.beneficiary, amount) 413 } 414 415 // AutoDeposit enables auto-deposits on the underlying chequebook. 416 func (o *Outbox) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { 417 o.chequeBook.AutoDeposit(interval, threshold, buffer) 418 } 419 420 // Stop helps satisfy the swap.OutPayment interface. 421 func (o *Outbox) Stop() {} 422 423 // String implements fmt.Stringer. 424 func (o *Outbox) String() string { 425 return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", o.chequeBook.Address().Hex(), o.beneficiary.Hex(), o.chequeBook.Balance()) 426 } 427 428 // Inbox can deposit, verify and cash cheques from a single contract to a single 429 // beneficiary. It is the incoming payment handler for peer to peer micropayments. 430 type Inbox struct { 431 lock sync.Mutex 432 contract common.Address // peer's chequebook contract 433 beneficiary common.Address // local peer's receiving address 434 sender common.Address // local peer's address to send cashing tx from 435 signer *ecdsa.PublicKey // peer's public key 436 txhash string // tx hash of last cashing tx 437 session *contract.ChequebookSession // abi contract backend with tx opts 438 quit chan bool // when closed causes autocash to stop 439 maxUncashed *big.Int // threshold that triggers autocashing 440 cashed *big.Int // cumulative amount cashed 441 cheque *Cheque // last cheque, nil if none yet received 442 log log.Logger // contextual logger with the contract address embedded 443 } 444 445 // NewInbox creates an Inbox. An Inboxes is not persisted, the cumulative sum is updated 446 // from blockchain when first cheque is received. 447 func NewInbox(prvKey *ecdsa.PrivateKey, contractAddr, beneficiary common.Address, signer *ecdsa.PublicKey, abigen bind.ContractBackend) (*Inbox, error) { 448 if signer == nil { 449 return nil, fmt.Errorf("signer is null") 450 } 451 chbook, err := contract.NewChequebook(contractAddr, abigen) 452 if err != nil { 453 return nil, err 454 } 455 transactOpts := bind.NewKeyedTransactor(prvKey) 456 transactOpts.GasLimit = gasToCash 457 session := &contract.ChequebookSession{ 458 Contract: chbook, 459 TransactOpts: *transactOpts, 460 } 461 sender := transactOpts.From 462 463 inbox := &Inbox{ 464 contract: contractAddr, 465 beneficiary: beneficiary, 466 sender: sender, 467 signer: signer, 468 session: session, 469 cashed: new(big.Int).Set(common.Big0), 470 log: log.New("contract", contractAddr), 471 } 472 inbox.log.Trace("New chequebook inbox initialized", "beneficiary", inbox.beneficiary, "signer", hexutil.Bytes(crypto.FromECDSAPub(signer))) 473 return inbox, nil 474 } 475 476 func (i *Inbox) String() string { 477 return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", i.contract.Hex(), i.beneficiary.Hex(), i.cheque.Amount) 478 } 479 480 // Stop quits the autocash goroutine. 481 func (i *Inbox) Stop() { 482 defer i.lock.Unlock() 483 i.lock.Lock() 484 if i.quit != nil { 485 close(i.quit) 486 i.quit = nil 487 } 488 } 489 490 // Cash attempts to cash the current cheque. 491 func (i *Inbox) Cash() (string, error) { 492 if i.cheque == nil { 493 return "", nil 494 } 495 txhash, err := i.cheque.Cash(i.session) 496 i.log.Trace("Cashing in chequebook cheque", "amount", i.cheque.Amount, "beneficiary", i.beneficiary) 497 i.cashed = i.cheque.Amount 498 499 return txhash, err 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 (i *Inbox) AutoCash(cashInterval time.Duration, maxUncashed *big.Int) { 505 defer i.lock.Unlock() 506 i.lock.Lock() 507 i.maxUncashed = maxUncashed 508 i.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 lock. 514 func (i *Inbox) autoCash(cashInterval time.Duration) { 515 if i.quit != nil { 516 close(i.quit) 517 i.quit = nil 518 } 519 // if maxUncashed is set to 0, then autocash on receipt 520 if cashInterval == time.Duration(0) || i.maxUncashed != nil && i.maxUncashed.Sign() == 0 { 521 return 522 } 523 524 ticker := time.NewTicker(cashInterval) 525 i.quit = make(chan bool) 526 quit := i.quit 527 528 go func() { 529 for { 530 select { 531 case <-quit: 532 return 533 case <-ticker.C: 534 i.lock.Lock() 535 if i.cheque != nil && i.cheque.Amount.Cmp(i.cashed) != 0 { 536 txhash, err := i.Cash() 537 if err == nil { 538 i.txhash = txhash 539 } 540 } 541 i.lock.Unlock() 542 } 543 } 544 }() 545 } 546 547 // Receive is called to deposit the latest cheque to the incoming Inbox. 548 // The given promise must be a *Cheque. 549 func (i *Inbox) Receive(promise swap.Promise) (*big.Int, error) { 550 ch := promise.(*Cheque) 551 552 defer i.lock.Unlock() 553 i.lock.Lock() 554 555 var sum *big.Int 556 if i.cheque == nil { 557 // the sum is checked against the blockchain once a cheque is received 558 tally, err := i.session.Sent(i.beneficiary) 559 if err != nil { 560 return nil, fmt.Errorf("inbox: error calling backend to set amount: %v", err) 561 } 562 sum = tally 563 } else { 564 sum = i.cheque.Amount 565 } 566 567 amount, err := ch.Verify(i.signer, i.contract, i.beneficiary, sum) 568 var uncashed *big.Int 569 if err == nil { 570 i.cheque = ch 571 572 if i.maxUncashed != nil { 573 uncashed = new(big.Int).Sub(ch.Amount, i.cashed) 574 if i.maxUncashed.Cmp(uncashed) < 0 { 575 i.Cash() 576 } 577 } 578 i.log.Trace("Received cheque in chequebook inbox", "amount", amount, "uncashed", uncashed) 579 } 580 581 return amount, err 582 } 583 584 // Verify verifies cheque for signer, contract, beneficiary, amount, valid signature. 585 func (ch *Cheque) Verify(signerKey *ecdsa.PublicKey, contract, beneficiary common.Address, sum *big.Int) (*big.Int, error) { 586 log.Trace("Verifying chequebook cheque", "cheque", ch, "sum", sum) 587 if sum == nil { 588 return nil, fmt.Errorf("invalid amount") 589 } 590 591 if ch.Beneficiary != beneficiary { 592 return nil, fmt.Errorf("beneficiary mismatch: %v != %v", ch.Beneficiary.Hex(), beneficiary.Hex()) 593 } 594 if ch.Contract != contract { 595 return nil, fmt.Errorf("contract mismatch: %v != %v", ch.Contract.Hex(), contract.Hex()) 596 } 597 598 amount := new(big.Int).Set(ch.Amount) 599 if sum != nil { 600 amount.Sub(amount, sum) 601 if amount.Sign() <= 0 { 602 return nil, fmt.Errorf("incorrect amount: %v <= 0", amount) 603 } 604 } 605 606 pubKey, err := crypto.SigToPub(sigHash(ch.Contract, beneficiary, ch.Amount), ch.Sig) 607 if err != nil { 608 return nil, fmt.Errorf("invalid signature: %v", err) 609 } 610 if !bytes.Equal(crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) { 611 return nil, fmt.Errorf("signer mismatch: %x != %x", crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) 612 } 613 return amount, nil 614 } 615 616 // v/r/s representation of signature 617 func sig2vrs(sig []byte) (v byte, r, s [32]byte) { 618 v = sig[64] + 27 619 copy(r[:], sig[:32]) 620 copy(s[:], sig[32:64]) 621 return 622 } 623 624 // Cash cashes the cheque by sending an Ethereum transaction. 625 func (ch *Cheque) Cash(session *contract.ChequebookSession) (string, error) { 626 v, r, s := sig2vrs(ch.Sig) 627 tx, err := session.Cash(ch.Beneficiary, ch.Amount, v, r, s) 628 if err != nil { 629 return "", err 630 } 631 return tx.Hash().Hex(), nil 632 } 633 634 // ValidateCode checks that the on-chain code at address matches the expected chequebook 635 // contract code. This is used to detect suicided chequebooks. 636 func ValidateCode(ctx context.Context, b Backend, address common.Address) (bool, error) { 637 code, err := b.CodeAt(ctx, address, nil) 638 if err != nil { 639 return false, err 640 } 641 return bytes.Equal(code, common.FromHex(contract.ContractDeployedCode)), nil 642 }