github.com/digdeepmining/go-atheios@v1.5.13-0.20180902133602-d5687a2e6f43/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 --pkg contract --out contract/chequebook.go 25 //go:generate go run ./gencode.go 26 27 import ( 28 "bytes" 29 "crypto/ecdsa" 30 "encoding/json" 31 "fmt" 32 "io/ioutil" 33 "math/big" 34 "os" 35 "sync" 36 "time" 37 38 "github.com/atheioschain/go-atheios/accounts/abi/bind" 39 "github.com/atheioschain/go-atheios/common" 40 "github.com/atheioschain/go-atheios/contracts/chequebook/contract" 41 "github.com/atheioschain/go-atheios/core/types" 42 "github.com/atheioschain/go-atheios/crypto" 43 "github.com/atheioschain/go-atheios/logger" 44 "github.com/atheioschain/go-atheios/logger/glog" 45 "github.com/atheioschain/go-atheios/swarm/services/swap/swap" 46 "golang.org/x/net/context" 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 require 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 = big.NewInt(2000000) // gas cost of a cash transaction using chequebook 60 // gasToDeploy = big.NewInt(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 (self *Cheque) String() string { 79 return fmt.Sprintf("contract: %s, beneficiary: %s, amount: %v, signature: %x", self.Contract.Hex(), self.Beneficiary.Hex(), 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 beneficiarys 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 110 func (self *Chequebook) String() string { 111 return fmt.Sprintf("contract: %s, owner: %s, balance: %v, signer: %x", self.contractAddr.Hex(), self.owner.Hex(), self.balance, self.prvKey.PublicKey) 112 } 113 114 // NewChequebook creates a new Chequebook. 115 func NewChequebook(path string, contractAddr common.Address, prvKey *ecdsa.PrivateKey, backend Backend) (self *Chequebook, err error) { 116 balance := new(big.Int) 117 sent := make(map[common.Address]*big.Int) 118 119 chbook, err := contract.NewChequebook(contractAddr, backend) 120 if err != nil { 121 return nil, err 122 } 123 transactOpts := bind.NewKeyedTransactor(prvKey) 124 session := &contract.ChequebookSession{ 125 Contract: chbook, 126 TransactOpts: *transactOpts, 127 } 128 129 self = &Chequebook{ 130 prvKey: prvKey, 131 balance: balance, 132 contractAddr: contractAddr, 133 sent: sent, 134 path: path, 135 backend: backend, 136 owner: transactOpts.From, 137 contract: chbook, 138 session: session, 139 } 140 141 if (contractAddr != common.Address{}) { 142 self.setBalanceFromBlockChain() 143 glog.V(logger.Detail).Infof("new chequebook initialised from %s (owner: %v, balance: %s)", contractAddr.Hex(), self.owner.Hex(), self.balance.String()) 144 } 145 return 146 } 147 148 func (self *Chequebook) setBalanceFromBlockChain() { 149 balance, err := self.backend.BalanceAt(context.TODO(), self.contractAddr, nil) 150 if err != nil { 151 glog.V(logger.Error).Infof("can't get balance: %v", err) 152 } else { 153 self.balance.Set(balance) 154 } 155 } 156 157 // LoadChequebook loads a chequebook from disk (file path). 158 func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend, checkBalance bool) (self *Chequebook, err error) { 159 var data []byte 160 data, err = ioutil.ReadFile(path) 161 if err != nil { 162 return 163 } 164 165 self, _ = NewChequebook(path, common.Address{}, prvKey, backend) 166 167 err = json.Unmarshal(data, self) 168 if err != nil { 169 return nil, err 170 } 171 if checkBalance { 172 self.setBalanceFromBlockChain() 173 } 174 175 glog.V(logger.Detail).Infof("loaded chequebook (%s, owner: %v, balance: %v) initialised from %v", self.contractAddr.Hex(), self.owner.Hex(), self.balance, path) 176 177 return 178 } 179 180 // chequebookFile is the JSON representation of a chequebook. 181 type chequebookFile struct { 182 Balance string 183 Contract string 184 Owner string 185 Sent map[string]string 186 } 187 188 // UnmarshalJSON deserialises a chequebook. 189 func (self *Chequebook) UnmarshalJSON(data []byte) error { 190 var file chequebookFile 191 err := json.Unmarshal(data, &file) 192 if err != nil { 193 return err 194 } 195 _, ok := self.balance.SetString(file.Balance, 10) 196 if !ok { 197 return fmt.Errorf("cumulative amount sent: unable to convert string to big integer: %v", file.Balance) 198 } 199 self.contractAddr = common.HexToAddress(file.Contract) 200 for addr, sent := range file.Sent { 201 self.sent[common.HexToAddress(addr)], ok = new(big.Int).SetString(sent, 10) 202 if !ok { 203 return fmt.Errorf("beneficiary %v cumulative amount sent: unable to convert string to big integer: %v", addr, sent) 204 } 205 } 206 return nil 207 } 208 209 // MarshalJSON serialises a chequebook. 210 func (self *Chequebook) MarshalJSON() ([]byte, error) { 211 var file = &chequebookFile{ 212 Balance: self.balance.String(), 213 Contract: self.contractAddr.Hex(), 214 Owner: self.owner.Hex(), 215 Sent: make(map[string]string), 216 } 217 for addr, sent := range self.sent { 218 file.Sent[addr.Hex()] = sent.String() 219 } 220 return json.Marshal(file) 221 } 222 223 // Save persists the chequebook on disk, remembering balance, contract address and 224 // cumulative amount of funds sent for each beneficiary. 225 func (self *Chequebook) Save() (err error) { 226 data, err := json.MarshalIndent(self, "", " ") 227 if err != nil { 228 return err 229 } 230 glog.V(logger.Detail).Infof("saving chequebook (%s) to %v", self.contractAddr.Hex(), self.path) 231 232 return ioutil.WriteFile(self.path, data, os.ModePerm) 233 } 234 235 // Stop quits the autodeposit go routine to terminate 236 func (self *Chequebook) Stop() { 237 defer self.lock.Unlock() 238 self.lock.Lock() 239 if self.quit != nil { 240 close(self.quit) 241 self.quit = nil 242 } 243 } 244 245 // Issue creates a cheque signed by the chequebook owner's private key. The 246 // signer commits to a contract (one that they own), a beneficiary and amount. 247 func (self *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (ch *Cheque, err error) { 248 defer self.lock.Unlock() 249 self.lock.Lock() 250 251 if amount.Cmp(common.Big0) <= 0 { 252 return nil, fmt.Errorf("amount must be greater than zero (%v)", amount) 253 } 254 if self.balance.Cmp(amount) < 0 { 255 err = fmt.Errorf("insufficient funds to issue cheque for amount: %v. balance: %v", amount, self.balance) 256 } else { 257 var sig []byte 258 sent, found := self.sent[beneficiary] 259 if !found { 260 sent = new(big.Int) 261 self.sent[beneficiary] = sent 262 } 263 sum := new(big.Int).Set(sent) 264 sum.Add(sum, amount) 265 266 sig, err = crypto.Sign(sigHash(self.contractAddr, beneficiary, sum), self.prvKey) 267 if err == nil { 268 ch = &Cheque{ 269 Contract: self.contractAddr, 270 Beneficiary: beneficiary, 271 Amount: sum, 272 Sig: sig, 273 } 274 sent.Set(sum) 275 self.balance.Sub(self.balance, amount) // subtract amount from balance 276 } 277 } 278 279 // auto deposit if threshold is set and balance is less then threshold 280 // note this is called even if issuing cheque fails 281 // so we reattempt depositing 282 if self.threshold != nil { 283 if self.balance.Cmp(self.threshold) < 0 { 284 send := new(big.Int).Sub(self.buffer, self.balance) 285 self.deposit(send) 286 } 287 } 288 289 return 290 } 291 292 // Cash is a convenience method to cash any cheque. 293 func (self *Chequebook) Cash(ch *Cheque) (txhash string, err error) { 294 return ch.Cash(self.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 (self *Chequebook) Balance() *big.Int { 312 defer self.lock.Unlock() 313 self.lock.Lock() 314 return new(big.Int).Set(self.balance) 315 } 316 317 // Owner returns the owner account of the chequebook. 318 func (self *Chequebook) Owner() common.Address { 319 return self.owner 320 } 321 322 // Address returns the on-chain contract address of the chequebook. 323 func (self *Chequebook) Address() common.Address { 324 return self.contractAddr 325 } 326 327 // Deposit deposits money to the chequebook account. 328 func (self *Chequebook) Deposit(amount *big.Int) (string, error) { 329 defer self.lock.Unlock() 330 self.lock.Lock() 331 return self.deposit(amount) 332 } 333 334 // deposit deposits amount to the chequebook account. 335 // The caller must hold self.lock. 336 func (self *Chequebook) deposit(amount *big.Int) (string, error) { 337 // since the amount is variable here, we do not use sessions 338 depositTransactor := bind.NewKeyedTransactor(self.prvKey) 339 depositTransactor.Value = amount 340 chbookRaw := &contract.ChequebookRaw{Contract: self.contract} 341 tx, err := chbookRaw.Transfer(depositTransactor) 342 if err != nil { 343 glog.V(logger.Warn).Infof("error depositing %d wei to chequebook (%s, balance: %v, target: %v): %v", amount, self.contractAddr.Hex(), self.balance, self.buffer, err) 344 return "", err 345 } 346 // assume that transaction is actually successful, we add the amount to balance right away 347 self.balance.Add(self.balance, amount) 348 glog.V(logger.Detail).Infof("deposited %d wei to chequebook (%s, balance: %v, target: %v)", amount, self.contractAddr.Hex(), self.balance, self.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 (self *Chequebook) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { 356 defer self.lock.Unlock() 357 self.lock.Lock() 358 self.threshold = threshold 359 self.buffer = buffer 360 self.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 (self *Chequebook) autoDeposit(interval time.Duration) { 366 if self.quit != nil { 367 close(self.quit) 368 self.quit = nil 369 } 370 // if threshold >= balance autodeposit after every cheque issued 371 if interval == time.Duration(0) || self.threshold != nil && self.buffer != nil && self.threshold.Cmp(self.buffer) >= 0 { 372 return 373 } 374 375 ticker := time.NewTicker(interval) 376 self.quit = make(chan bool) 377 quit := self.quit 378 go func() { 379 FOR: 380 for { 381 select { 382 case <-quit: 383 break FOR 384 case <-ticker.C: 385 self.lock.Lock() 386 if self.balance.Cmp(self.buffer) < 0 { 387 amount := new(big.Int).Sub(self.buffer, self.balance) 388 txhash, err := self.deposit(amount) 389 if err == nil { 390 self.txhash = txhash 391 } 392 } 393 self.lock.Unlock() 394 } 395 } 396 }() 397 return 398 } 399 400 // Outbox can issue cheques from a single contract to a single beneficiary. 401 type Outbox struct { 402 chequeBook *Chequebook 403 beneficiary common.Address 404 } 405 406 // NewOutbox creates an outbox. 407 func NewOutbox(chbook *Chequebook, beneficiary common.Address) *Outbox { 408 return &Outbox{chbook, beneficiary} 409 } 410 411 // Issue creates cheque. 412 func (self *Outbox) Issue(amount *big.Int) (swap.Promise, error) { 413 return self.chequeBook.Issue(self.beneficiary, amount) 414 } 415 416 // AutoDeposit enables auto-deposits on the underlying chequebook. 417 func (self *Outbox) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { 418 self.chequeBook.AutoDeposit(interval, threshold, buffer) 419 } 420 421 // Stop helps satisfy the swap.OutPayment interface. 422 func (self *Outbox) Stop() {} 423 424 // String implements fmt.Stringer. 425 func (self *Outbox) String() string { 426 return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.chequeBook.Address().Hex(), self.beneficiary.Hex(), 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 abigen bind.ContractBackend // blockchain API 439 session *contract.ChequebookSession // abi contract backend with tx opts 440 quit chan bool // when closed causes autocash to stop 441 maxUncashed *big.Int // threshold that triggers autocashing 442 cashed *big.Int // cumulative amount cashed 443 cheque *Cheque // last cheque, nil if none yet received 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 } 472 glog.V(logger.Detail).Infof("initialised inbox (%s -> %s) expected signer: %x", self.contract.Hex(), self.beneficiary.Hex(), crypto.FromECDSAPub(signer)) 473 return 474 } 475 476 func (self *Inbox) String() string { 477 return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.contract.Hex(), self.beneficiary.Hex(), self.cheque.Amount) 478 } 479 480 // Stop quits the autocash goroutine. 481 func (self *Inbox) Stop() { 482 defer self.lock.Unlock() 483 self.lock.Lock() 484 if self.quit != nil { 485 close(self.quit) 486 self.quit = nil 487 } 488 } 489 490 // Cash attempts to cash the current cheque. 491 func (self *Inbox) Cash() (txhash string, err error) { 492 if self.cheque != nil { 493 txhash, err = self.cheque.Cash(self.session) 494 glog.V(logger.Detail).Infof("cashing cheque (total: %v) on chequebook (%s) sending to %v", self.cheque.Amount, self.contract.Hex(), self.beneficiary.Hex()) 495 self.cashed = self.cheque.Amount 496 } 497 return 498 } 499 500 // AutoCash (re)sets maximum time and amount which triggers cashing of the last uncashed 501 // cheque if maxUncashed is set to 0, then autocash on receipt. 502 func (self *Inbox) AutoCash(cashInterval time.Duration, maxUncashed *big.Int) { 503 defer self.lock.Unlock() 504 self.lock.Lock() 505 self.maxUncashed = maxUncashed 506 self.autoCash(cashInterval) 507 } 508 509 // autoCash starts a loop that periodically clears the last check 510 // if the peer is trusted. Clearing period could be 24h or a week. 511 // 512 // The caller must hold self.lock. 513 func (self *Inbox) autoCash(cashInterval time.Duration) { 514 if self.quit != nil { 515 close(self.quit) 516 self.quit = nil 517 } 518 // if maxUncashed is set to 0, then autocash on receipt 519 if cashInterval == time.Duration(0) || self.maxUncashed != nil && self.maxUncashed.Cmp(common.Big0) == 0 { 520 return 521 } 522 523 ticker := time.NewTicker(cashInterval) 524 self.quit = make(chan bool) 525 quit := self.quit 526 go func() { 527 FOR: 528 for { 529 select { 530 case <-quit: 531 break FOR 532 case <-ticker.C: 533 self.lock.Lock() 534 if self.cheque != nil && self.cheque.Amount.Cmp(self.cashed) != 0 { 535 txhash, err := self.Cash() 536 if err == nil { 537 self.txhash = txhash 538 } 539 } 540 self.lock.Unlock() 541 } 542 } 543 }() 544 return 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 (self *Inbox) Receive(promise swap.Promise) (*big.Int, error) { 550 ch := promise.(*Cheque) 551 552 defer self.lock.Unlock() 553 self.lock.Lock() 554 555 var sum *big.Int 556 if self.cheque == nil { 557 // the sum is checked against the blockchain once a check is received 558 tally, err := self.session.Sent(self.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 = self.cheque.Amount 565 } 566 567 amount, err := ch.Verify(self.signer, self.contract, self.beneficiary, sum) 568 var uncashed *big.Int 569 if err == nil { 570 self.cheque = ch 571 572 if self.maxUncashed != nil { 573 uncashed = new(big.Int).Sub(ch.Amount, self.cashed) 574 if self.maxUncashed.Cmp(uncashed) < 0 { 575 self.Cash() 576 } 577 } 578 glog.V(logger.Detail).Infof("received cheque of %v wei in inbox (%s, uncashed: %v)", amount, self.contract.Hex(), uncashed) 579 } 580 581 return amount, err 582 } 583 584 // Verify verifies cheque for signer, contract, beneficiary, amount, valid signature. 585 func (self *Cheque) Verify(signerKey *ecdsa.PublicKey, contract, beneficiary common.Address, sum *big.Int) (*big.Int, error) { 586 glog.V(logger.Detail).Infof("verify cheque: %v - sum: %v", self, sum) 587 if sum == nil { 588 return nil, fmt.Errorf("invalid amount") 589 } 590 591 if self.Beneficiary != beneficiary { 592 return nil, fmt.Errorf("beneficiary mismatch: %v != %v", self.Beneficiary.Hex(), beneficiary.Hex()) 593 } 594 if self.Contract != contract { 595 return nil, fmt.Errorf("contract mismatch: %v != %v", self.Contract.Hex(), contract.Hex()) 596 } 597 598 amount := new(big.Int).Set(self.Amount) 599 if sum != nil { 600 amount.Sub(amount, sum) 601 if amount.Cmp(common.Big0) <= 0 { 602 return nil, fmt.Errorf("incorrect amount: %v <= 0", amount) 603 } 604 } 605 606 pubKey, err := crypto.SigToPub(sigHash(self.Contract, beneficiary, self.Amount), self.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 (self *Cheque) Cash(session *contract.ChequebookSession) (string, error) { 626 v, r, s := sig2vrs(self.Sig) 627 tx, err := session.Cash(self.Beneficiary, self.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) (ok bool, err 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 }