diff --git a/contracts/chequebook/api.go b/contracts/chequebook/api.go new file mode 100644 index 000000000..b2b2365f3 --- /dev/null +++ b/contracts/chequebook/api.go @@ -0,0 +1,68 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package chequebook + +import ( + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +const Version = "1.0" + +var errNoChequebook = errors.New("no chequebook") + +type Api struct { + chequebookf func() *Chequebook +} + +func NewApi(ch func() *Chequebook) *Api { + return &Api{ch} +} + +func (self *Api) Balance() (string, error) { + ch := self.chequebookf() + if ch == nil { + return "", errNoChequebook + } + return ch.Balance().String(), nil +} + +func (self *Api) Issue(beneficiary common.Address, amount *big.Int) (cheque *Cheque, err error) { + ch := self.chequebookf() + if ch == nil { + return nil, errNoChequebook + } + return ch.Issue(beneficiary, amount) +} + +func (self *Api) Cash(cheque *Cheque) (txhash string, err error) { + ch := self.chequebookf() + if ch == nil { + return "", errNoChequebook + } + return ch.Cash(cheque) +} + +func (self *Api) Deposit(amount *big.Int) (txhash string, err error) { + ch := self.chequebookf() + if ch == nil { + return "", errNoChequebook + } + return ch.Deposit(amount) +} diff --git a/contracts/chequebook/cheque.go b/contracts/chequebook/cheque.go new file mode 100644 index 000000000..8199904ef --- /dev/null +++ b/contracts/chequebook/cheque.go @@ -0,0 +1,643 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package chequebook package wraps the 'chequebook' Ethereum smart contract. +// +// The functions in this package allow using chequebook for +// issuing, receiving, verifying cheques in ether; (auto)cashing cheques in ether +// as well as (auto)depositing ether to the chequebook contract. +package chequebook + +//go:generate abigen --sol contract/chequebook.sol --pkg contract --out contract/chequebook.go +//go:generate go run ./gencode.go + +import ( + "bytes" + "crypto/ecdsa" + "encoding/json" + "fmt" + "io/ioutil" + "math/big" + "os" + "sync" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/contracts/chequebook/contract" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "golang.org/x/net/context" +) + +// TODO(zelig): watch peer solvency and notify of bouncing cheques +// TODO(zelig): enable paying with cheque by signing off + +// Some functionality require interacting with the blockchain: +// * setting current balance on peer's chequebook +// * sending the transaction to cash the cheque +// * depositing ether to the chequebook +// * watching incoming ether + +var ( + gasToCash = big.NewInt(2000000) // gas cost of a cash transaction using chequebook + // gasToDeploy = big.NewInt(3000000) +) + +// Backend wraps all methods required for chequebook operation. +type Backend interface { + bind.ContractBackend + TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) + BalanceAt(ctx context.Context, address common.Address, blockNum *big.Int) (*big.Int, error) +} + +// Cheque represents a payment promise to a single beneficiary. +type Cheque struct { + Contract common.Address // address of chequebook, needed to avoid cross-contract submission + Beneficiary common.Address + Amount *big.Int // cumulative amount of all funds sent + Sig []byte // signature Sign(Keccak256(contract, beneficiary, amount), prvKey) +} + +func (self *Cheque) String() string { + return fmt.Sprintf("contract: %s, beneficiary: %s, amount: %v, signature: %x", self.Contract.Hex(), self.Beneficiary.Hex(), self.Amount, self.Sig) +} + +type Params struct { + ContractCode, ContractAbi string +} + +var ContractParams = &Params{contract.ChequebookBin, contract.ChequebookABI} + +// Chequebook can create and sign cheques from a single contract to multiple beneficiaries. +// It is the outgoing payment handler for peer to peer micropayments. +type Chequebook struct { + path string // path to chequebook file + prvKey *ecdsa.PrivateKey // private key to sign cheque with + lock sync.Mutex // + backend Backend // blockchain API + quit chan bool // when closed causes autodeposit to stop + owner common.Address // owner address (derived from pubkey) + contract *contract.Chequebook // abigen binding + session *contract.ChequebookSession // abigen binding with Tx Opts + + // persisted fields + balance *big.Int // not synced with blockchain + contractAddr common.Address // contract address + sent map[common.Address]*big.Int //tallies for beneficiarys + + txhash string // tx hash of last deposit tx + threshold *big.Int // threshold that triggers autodeposit if not nil + buffer *big.Int // buffer to keep on top of balance for fork protection +} + +func (self *Chequebook) String() string { + return fmt.Sprintf("contract: %s, owner: %s, balance: %v, signer: %x", self.contractAddr.Hex(), self.owner.Hex(), self.balance, self.prvKey.PublicKey) +} + +// NewChequebook creates a new Chequebook. +func NewChequebook(path string, contractAddr common.Address, prvKey *ecdsa.PrivateKey, backend Backend) (self *Chequebook, err error) { + balance := new(big.Int) + sent := make(map[common.Address]*big.Int) + + chbook, err := contract.NewChequebook(contractAddr, backend) + if err != nil { + return nil, err + } + transactOpts := bind.NewKeyedTransactor(prvKey) + session := &contract.ChequebookSession{ + Contract: chbook, + TransactOpts: *transactOpts, + } + + self = &Chequebook{ + prvKey: prvKey, + balance: balance, + contractAddr: contractAddr, + sent: sent, + path: path, + backend: backend, + owner: transactOpts.From, + contract: chbook, + session: session, + } + + if (contractAddr != common.Address{}) { + self.setBalanceFromBlockChain() + glog.V(logger.Detail).Infof("new chequebook initialised from %s (owner: %v, balance: %s)", contractAddr.Hex(), self.owner.Hex(), self.balance.String()) + } + return +} + +func (self *Chequebook) setBalanceFromBlockChain() { + balance, err := self.backend.BalanceAt(context.TODO(), self.contractAddr, nil) + if err != nil { + glog.V(logger.Error).Infof("can't get balance: %v", err) + } else { + self.balance.Set(balance) + } +} + +// LoadChequebook loads a chequebook from disk (file path). +func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend, checkBalance bool) (self *Chequebook, err error) { + var data []byte + data, err = ioutil.ReadFile(path) + if err != nil { + return + } + + self, _ = NewChequebook(path, common.Address{}, prvKey, backend) + + err = json.Unmarshal(data, self) + if err != nil { + return nil, err + } + if checkBalance { + self.setBalanceFromBlockChain() + } + + glog.V(logger.Detail).Infof("loaded chequebook (%s, owner: %v, balance: %v) initialised from %v", self.contractAddr.Hex(), self.owner.Hex(), self.balance, path) + + return +} + +// chequebookFile is the JSON representation of a chequebook. +type chequebookFile struct { + Balance string + Contract string + Owner string + Sent map[string]string +} + +// UnmarshalJSON deserialises a chequebook. +func (self *Chequebook) UnmarshalJSON(data []byte) error { + var file chequebookFile + err := json.Unmarshal(data, &file) + if err != nil { + return err + } + _, ok := self.balance.SetString(file.Balance, 10) + if !ok { + return fmt.Errorf("cumulative amount sent: unable to convert string to big integer: %v", file.Balance) + } + self.contractAddr = common.HexToAddress(file.Contract) + for addr, sent := range file.Sent { + self.sent[common.HexToAddress(addr)], ok = new(big.Int).SetString(sent, 10) + if !ok { + return fmt.Errorf("beneficiary %v cumulative amount sent: unable to convert string to big integer: %v", addr, sent) + } + } + return nil +} + +// MarshalJSON serialises a chequebook. +func (self *Chequebook) MarshalJSON() ([]byte, error) { + var file = &chequebookFile{ + Balance: self.balance.String(), + Contract: self.contractAddr.Hex(), + Owner: self.owner.Hex(), + Sent: make(map[string]string), + } + for addr, sent := range self.sent { + file.Sent[addr.Hex()] = sent.String() + } + return json.Marshal(file) +} + +// Save persists the chequebook on disk, remembering balance, contract address and +// cumulative amount of funds sent for each beneficiary. +func (self *Chequebook) Save() (err error) { + data, err := json.MarshalIndent(self, "", " ") + if err != nil { + return err + } + glog.V(logger.Detail).Infof("saving chequebook (%s) to %v", self.contractAddr.Hex(), self.path) + + return ioutil.WriteFile(self.path, data, os.ModePerm) +} + +// Stop quits the autodeposit go routine to terminate +func (self *Chequebook) Stop() { + defer self.lock.Unlock() + self.lock.Lock() + if self.quit != nil { + close(self.quit) + self.quit = nil + } +} + +// Issue creates a cheque signed by the chequebook owner's private key. The +// signer commits to a contract (one that they own), a beneficiary and amount. +func (self *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (ch *Cheque, err error) { + defer self.lock.Unlock() + self.lock.Lock() + + if amount.Cmp(common.Big0) <= 0 { + return nil, fmt.Errorf("amount must be greater than zero (%v)", amount) + } + if self.balance.Cmp(amount) < 0 { + err = fmt.Errorf("insufficent funds to issue cheque for amount: %v. balance: %v", amount, self.balance) + } else { + var sig []byte + sent, found := self.sent[beneficiary] + if !found { + sent = new(big.Int) + self.sent[beneficiary] = sent + } + sum := new(big.Int).Set(sent) + sum.Add(sum, amount) + + sig, err = crypto.Sign(sigHash(self.contractAddr, beneficiary, sum), self.prvKey) + if err == nil { + ch = &Cheque{ + Contract: self.contractAddr, + Beneficiary: beneficiary, + Amount: sum, + Sig: sig, + } + sent.Set(sum) + self.balance.Sub(self.balance, amount) // subtract amount from balance + } + } + + // auto deposit if threshold is set and balance is less then threshold + // note this is called even if issueing cheque fails + // so we reattempt depositing + if self.threshold != nil { + if self.balance.Cmp(self.threshold) < 0 { + send := new(big.Int).Sub(self.buffer, self.balance) + self.deposit(send) + } + } + + return +} + +// Cash is a convenience method to cash any cheque. +func (self *Chequebook) Cash(ch *Cheque) (txhash string, err error) { + return ch.Cash(self.session) +} + +// data to sign: contract address, beneficiary, cumulative amount of funds ever sent +func sigHash(contract, beneficiary common.Address, sum *big.Int) []byte { + bigamount := sum.Bytes() + if len(bigamount) > 32 { + return nil + } + var amount32 [32]byte + copy(amount32[32-len(bigamount):32], bigamount) + input := append(contract.Bytes(), beneficiary.Bytes()...) + input = append(input, amount32[:]...) + return crypto.Keccak256(input) +} + +// Balance returns the current balance of the chequebook. +func (self *Chequebook) Balance() *big.Int { + defer self.lock.Unlock() + self.lock.Lock() + return new(big.Int).Set(self.balance) +} + +// Owner returns the owner account of the chequebook. +func (self *Chequebook) Owner() common.Address { + return self.owner +} + +// Address returns the on-chain contract address of the chequebook. +func (self *Chequebook) Address() common.Address { + return self.contractAddr +} + +// Deposit deposits money to the chequebook account. +func (self *Chequebook) Deposit(amount *big.Int) (string, error) { + defer self.lock.Unlock() + self.lock.Lock() + return self.deposit(amount) +} + +// deposit deposits amount to the chequebook account. +// The caller must hold self.lock. +func (self *Chequebook) deposit(amount *big.Int) (string, error) { + // since the amount is variable here, we do not use sessions + depositTransactor := bind.NewKeyedTransactor(self.prvKey) + depositTransactor.Value = amount + chbookRaw := &contract.ChequebookRaw{Contract: self.contract} + tx, err := chbookRaw.Transfer(depositTransactor) + if err != nil { + 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) + return "", err + } + // assume that transaction is actually successful, we add the amount to balance right away + self.balance.Add(self.balance, amount) + glog.V(logger.Detail).Infof("deposited %d wei to chequebook (%s, balance: %v, target: %v)", amount, self.contractAddr.Hex(), self.balance, self.buffer) + return tx.Hash().Hex(), nil +} + +// AutoDeposit (re)sets interval time and amount which triggers sending funds to the +// chequebook. Contract backend needs to be set if threshold is not less than buffer, then +// deposit will be triggered on every new cheque issued. +func (self *Chequebook) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { + defer self.lock.Unlock() + self.lock.Lock() + self.threshold = threshold + self.buffer = buffer + self.autoDeposit(interval) +} + +// autoDeposit starts a goroutine that periodically sends funds to the chequebook +// contract caller holds the lock the go routine terminates if Chequebook.quit is closed. +func (self *Chequebook) autoDeposit(interval time.Duration) { + if self.quit != nil { + close(self.quit) + self.quit = nil + } + // if threshold >= balance autodeposit after every cheque issued + if interval == time.Duration(0) || self.threshold != nil && self.buffer != nil && self.threshold.Cmp(self.buffer) >= 0 { + return + } + + ticker := time.NewTicker(interval) + self.quit = make(chan bool) + quit := self.quit + go func() { + FOR: + for { + select { + case <-quit: + break FOR + case <-ticker.C: + self.lock.Lock() + if self.balance.Cmp(self.buffer) < 0 { + amount := new(big.Int).Sub(self.buffer, self.balance) + txhash, err := self.deposit(amount) + if err == nil { + self.txhash = txhash + } + } + self.lock.Unlock() + } + } + }() + return +} + +// Outbox can issue cheques from a single contract to a single beneficiary. +type Outbox struct { + chequeBook *Chequebook + beneficiary common.Address +} + +// NewOutbox creates an outbox. +func NewOutbox(chbook *Chequebook, beneficiary common.Address) *Outbox { + return &Outbox{chbook, beneficiary} +} + +// Issue creates cheque. +func (self *Outbox) Issue(amount *big.Int) (interface{}, error) { + // TODO(fjl): the return type should be more descriptive. + return self.chequeBook.Issue(self.beneficiary, amount) +} + +// AutoDeposit enables auto-deposits on the underlying chequebook. +func (self *Outbox) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { + self.chequeBook.AutoDeposit(interval, threshold, buffer) +} + +// Stop helps satisfy the swap.OutPayment interface. +func (self *Outbox) Stop() {} + +// String implements fmt.Stringer. +func (self *Outbox) String() string { + return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.chequeBook.Address().Hex(), self.beneficiary.Hex(), self.chequeBook.Balance()) +} + +// Inbox can deposit, verify and cash cheques from a single contract to a single +// beneficiary. It is the incoming payment handler for peer to peer micropayments. +type Inbox struct { + lock sync.Mutex + contract common.Address // peer's chequebook contract + beneficiary common.Address // local peer's receiving address + sender common.Address // local peer's address to send cashing tx from + signer *ecdsa.PublicKey // peer's public key + txhash string // tx hash of last cashing tx + abigen bind.ContractBackend // blockchain API + session *contract.ChequebookSession // abi contract backend with tx opts + quit chan bool // when closed causes autocash to stop + maxUncashed *big.Int // threshold that triggers autocashing + cashed *big.Int // cumulative amount cashed + cheque *Cheque // last cheque, nil if none yet received +} + +// NewInbox creates an Inbox. An Inboxes is not persisted, the cumulative sum is updated +// from blockchain when first cheque is received. +func NewInbox(prvKey *ecdsa.PrivateKey, contractAddr, beneficiary common.Address, signer *ecdsa.PublicKey, abigen bind.ContractBackend) (self *Inbox, err error) { + if signer == nil { + return nil, fmt.Errorf("signer is null") + } + chbook, err := contract.NewChequebook(contractAddr, abigen) + if err != nil { + return nil, err + } + transactOpts := bind.NewKeyedTransactor(prvKey) + transactOpts.GasLimit = gasToCash + session := &contract.ChequebookSession{ + Contract: chbook, + TransactOpts: *transactOpts, + } + sender := transactOpts.From + + self = &Inbox{ + contract: contractAddr, + beneficiary: beneficiary, + sender: sender, + signer: signer, + session: session, + cashed: new(big.Int).Set(common.Big0), + } + glog.V(logger.Detail).Infof("initialised inbox (%s -> %s) expected signer: %x", self.contract.Hex(), self.beneficiary.Hex(), crypto.FromECDSAPub(signer)) + return +} + +func (self *Inbox) String() string { + return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.contract.Hex(), self.beneficiary.Hex(), self.cheque.Amount) +} + +// Stop quits the autocash goroutine. +func (self *Inbox) Stop() { + defer self.lock.Unlock() + self.lock.Lock() + if self.quit != nil { + close(self.quit) + self.quit = nil + } +} + +// Cash attempts to cash the current cheque. +func (self *Inbox) Cash() (txhash string, err error) { + if self.cheque != nil { + txhash, err = self.cheque.Cash(self.session) + glog.V(logger.Detail).Infof("cashing cheque (total: %v) on chequebook (%s) sending to %v", self.cheque.Amount, self.contract.Hex(), self.beneficiary.Hex()) + self.cashed = self.cheque.Amount + } + return +} + +// AutoCash (re)sets maximum time and amount which triggers cashing of the last uncashed +// cheque if maxUncashed is set to 0, then autocash on receipt. +func (self *Inbox) AutoCash(cashInterval time.Duration, maxUncashed *big.Int) { + defer self.lock.Unlock() + self.lock.Lock() + self.maxUncashed = maxUncashed + self.autoCash(cashInterval) +} + +// autoCash starts a loop that periodically clears the last check +// if the peer is trusted. Clearing period could be 24h or a week. +// +// The caller must hold self.lock. +func (self *Inbox) autoCash(cashInterval time.Duration) { + if self.quit != nil { + close(self.quit) + self.quit = nil + } + // if maxUncashed is set to 0, then autocash on receipt + if cashInterval == time.Duration(0) || self.maxUncashed != nil && self.maxUncashed.Cmp(common.Big0) == 0 { + return + } + + ticker := time.NewTicker(cashInterval) + self.quit = make(chan bool) + quit := self.quit + go func() { + FOR: + for { + select { + case <-quit: + break FOR + case <-ticker.C: + self.lock.Lock() + if self.cheque != nil && self.cheque.Amount.Cmp(self.cashed) != 0 { + txhash, err := self.Cash() + if err == nil { + self.txhash = txhash + } + } + self.lock.Unlock() + } + } + }() + return +} + +// Receive is called to deposit the latest cheque to the incoming Inbox. +// The given promise must be a *Cheque. +func (self *Inbox) Receive(promise interface{}) (*big.Int, error) { + // TODO(fjl): the type of promise should be safer + ch := promise.(*Cheque) + + defer self.lock.Unlock() + self.lock.Lock() + + var sum *big.Int + if self.cheque == nil { + // the sum is checked against the blockchain once a check is received + tally, err := self.session.Sent(self.beneficiary) + if err != nil { + return nil, fmt.Errorf("inbox: error calling backend to set amount: %v", err) + } + sum = tally + } else { + sum = self.cheque.Amount + } + + amount, err := ch.Verify(self.signer, self.contract, self.beneficiary, sum) + var uncashed *big.Int + if err == nil { + self.cheque = ch + + if self.maxUncashed != nil { + uncashed = new(big.Int).Sub(ch.Amount, self.cashed) + if self.maxUncashed.Cmp(uncashed) < 0 { + self.Cash() + } + } + glog.V(logger.Detail).Infof("received cheque of %v wei in inbox (%s, uncashed: %v)", amount, self.contract.Hex(), uncashed) + } + + return amount, err +} + +// Verify verifies cheque for signer, contract, beneficiary, amount, valid signature. +func (self *Cheque) Verify(signerKey *ecdsa.PublicKey, contract, beneficiary common.Address, sum *big.Int) (*big.Int, error) { + glog.V(logger.Detail).Infof("verify cheque: %v - sum: %v", self, sum) + if sum == nil { + return nil, fmt.Errorf("invalid amount") + } + + if self.Beneficiary != beneficiary { + return nil, fmt.Errorf("beneficiary mismatch: %v != %v", self.Beneficiary.Hex(), beneficiary.Hex()) + } + if self.Contract != contract { + return nil, fmt.Errorf("contract mismatch: %v != %v", self.Contract.Hex(), contract.Hex()) + } + + amount := new(big.Int).Set(self.Amount) + if sum != nil { + amount.Sub(amount, sum) + if amount.Cmp(common.Big0) <= 0 { + return nil, fmt.Errorf("incorrect amount: %v <= 0", amount) + } + } + + pubKey, err := crypto.SigToPub(sigHash(self.Contract, beneficiary, self.Amount), self.Sig) + if err != nil { + return nil, fmt.Errorf("invalid signature: %v", err) + } + if !bytes.Equal(crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) { + return nil, fmt.Errorf("signer mismatch: %x != %x", crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) + } + return amount, nil +} + +// v/r/s representation of signature +func sig2vrs(sig []byte) (v byte, r, s [32]byte) { + v = sig[64] + 27 + copy(r[:], sig[:32]) + copy(s[:], sig[32:64]) + return +} + +// Cash cashes the cheque by sending an Ethereum transaction. +func (self *Cheque) Cash(session *contract.ChequebookSession) (string, error) { + v, r, s := sig2vrs(self.Sig) + tx, err := session.Cash(self.Beneficiary, self.Amount, v, r, s) + if err != nil { + return "", err + } + return tx.Hash().Hex(), nil +} + +// ValidateCode checks that the on-chain code at address matches the expected chequebook +// contract code. This is used to detect suicided chequebooks. +func ValidateCode(ctx context.Context, b Backend, address common.Address) (ok bool, err error) { + code, err := b.CodeAt(ctx, address, nil) + if err != nil { + return false, err + } + return bytes.Equal(code, common.FromHex(contract.ContractDeployedCode)), nil +} diff --git a/contracts/chequebook/cheque_test.go b/contracts/chequebook/cheque_test.go new file mode 100644 index 000000000..e35a21cc5 --- /dev/null +++ b/contracts/chequebook/cheque_test.go @@ -0,0 +1,528 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package chequebook + +import ( + "crypto/ecdsa" + "math/big" + "os" + "path/filepath" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/contracts/chequebook/contract" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" +) + +var ( + key0, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key1, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + key2, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + addr0 = crypto.PubkeyToAddress(key0.PublicKey) + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) +) + +func newTestBackend() *backends.SimulatedBackend { + return backends.NewSimulatedBackend( + core.GenesisAccount{Address: addr0, Balance: big.NewInt(1000000000)}, + core.GenesisAccount{Address: addr1, Balance: big.NewInt(1000000000)}, + core.GenesisAccount{Address: addr2, Balance: big.NewInt(1000000000)}, + ) +} + +func deploy(prvKey *ecdsa.PrivateKey, amount *big.Int, backend *backends.SimulatedBackend) (common.Address, error) { + deployTransactor := bind.NewKeyedTransactor(prvKey) + deployTransactor.Value = amount + addr, _, _, err := contract.DeployChequebook(deployTransactor, backend) + if err != nil { + return common.Address{}, err + } + backend.Commit() + return addr, nil +} + +func TestIssueAndReceive(t *testing.T) { + path := filepath.Join(os.TempDir(), "chequebook-test.json") + backend := newTestBackend() + addr0, err := deploy(key0, big.NewInt(0), backend) + if err != nil { + t.Fatalf("deploy contract: expected no error, got %v", err) + } + chbook, err := NewChequebook(path, addr0, key0, backend) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + chbook.sent[addr1] = new(big.Int).SetUint64(42) + amount := common.Big1 + ch, err := chbook.Issue(addr1, amount) + if err == nil { + t.Fatalf("expected insufficient funds error, got none") + } + + chbook.balance = new(big.Int).Set(common.Big1) + if chbook.Balance().Cmp(common.Big1) != 0 { + t.Fatalf("expected: %v, got %v", "0", chbook.Balance()) + } + + ch, err = chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if chbook.Balance().Cmp(common.Big0) != 0 { + t.Errorf("expected: %v, got %v", "0", chbook.Balance()) + } + + chbox, err := NewInbox(key1, addr0, addr1, &key0.PublicKey, backend) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + received, err := chbox.Receive(ch) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if received.Cmp(big.NewInt(43)) != 0 { + t.Errorf("expected: %v, got %v", "43", received) + } + +} + +func TestCheckbookFile(t *testing.T) { + path := filepath.Join(os.TempDir(), "chequebook-test.json") + backend := newTestBackend() + chbook, err := NewChequebook(path, addr0, key0, backend) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + chbook.sent[addr1] = new(big.Int).SetUint64(42) + chbook.balance = new(big.Int).Set(common.Big1) + + chbook.Save() + + chbook, err = LoadChequebook(path, key0, backend, false) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if chbook.Balance().Cmp(common.Big1) != 0 { + t.Errorf("expected: %v, got %v", "0", chbook.Balance()) + } + + ch, err := chbook.Issue(addr1, common.Big1) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if ch.Amount.Cmp(new(big.Int).SetUint64(43)) != 0 { + t.Errorf("expected: %v, got %v", "0", ch.Amount) + } + + err = chbook.Save() + if err != nil { + t.Fatalf("expected no error, got %v", err) + } +} + +func TestVerifyErrors(t *testing.T) { + path0 := filepath.Join(os.TempDir(), "chequebook-test-0.json") + backend := newTestBackend() + contr0, err := deploy(key0, common.Big2, backend) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + chbook0, err := NewChequebook(path0, contr0, key0, backend) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + path1 := filepath.Join(os.TempDir(), "chequebook-test-1.json") + contr1, err := deploy(key1, common.Big2, backend) + chbook1, err := NewChequebook(path1, contr1, key1, backend) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + chbook0.sent[addr1] = new(big.Int).SetUint64(42) + chbook0.balance = new(big.Int).Set(common.Big2) + chbook1.balance = new(big.Int).Set(common.Big1) + amount := common.Big1 + ch0, err := chbook0.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + time.Sleep(5) + chbox, err := NewInbox(key1, contr0, addr1, &key0.PublicKey, backend) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + received, err := chbox.Receive(ch0) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if received.Cmp(big.NewInt(43)) != 0 { + t.Errorf("expected: %v, got %v", "43", received) + } + + ch1, err := chbook0.Issue(addr2, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + received, err = chbox.Receive(ch1) + t.Logf("correct error: %v", err) + if err == nil { + t.Fatalf("expected receiver error, got none") + } + + ch2, err := chbook1.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + received, err = chbox.Receive(ch2) + t.Logf("correct error: %v", err) + if err == nil { + t.Fatalf("expected sender error, got none") + } + + _, err = chbook1.Issue(addr1, new(big.Int).SetInt64(-1)) + t.Logf("correct error: %v", err) + if err == nil { + t.Fatalf("expected incorrect amount error, got none") + } + + received, err = chbox.Receive(ch0) + t.Logf("correct error: %v", err) + if err == nil { + t.Fatalf("expected incorrect amount error, got none") + } + +} + +func TestDeposit(t *testing.T) { + path0 := filepath.Join(os.TempDir(), "chequebook-test-0.json") + backend := newTestBackend() + contr0, err := deploy(key0, new(big.Int), backend) + chbook, err := NewChequebook(path0, contr0, key0, backend) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + balance := new(big.Int).SetUint64(42) + chbook.Deposit(balance) + backend.Commit() + if chbook.balance.Cmp(balance) != 0 { + t.Fatalf("expected balance %v, got %v", balance, chbook.balance) + } + + amount := common.Big1 + _, err = chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + exp := new(big.Int).SetUint64(41) + if chbook.balance.Cmp(exp) != 0 { + t.Fatalf("expected balance %v, got %v", exp, chbook.balance) + } + + // autodeposit on each issue + chbook.AutoDeposit(0, balance, balance) + _, err = chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + _, err = chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + if chbook.balance.Cmp(balance) != 0 { + t.Fatalf("expected balance %v, got %v", balance, chbook.balance) + } + + // autodeposit off + chbook.AutoDeposit(0, common.Big0, balance) + _, err = chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + _, err = chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + + exp = new(big.Int).SetUint64(40) + if chbook.balance.Cmp(exp) != 0 { + t.Fatalf("expected balance %v, got %v", exp, chbook.balance) + } + + // autodeposit every 10ms if new cheque issued + interval := 30 * time.Millisecond + chbook.AutoDeposit(interval, common.Big1, balance) + _, err = chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + _, err = chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + + exp = new(big.Int).SetUint64(38) + if chbook.balance.Cmp(exp) != 0 { + t.Fatalf("expected balance %v, got %v", exp, chbook.balance) + } + + time.Sleep(3 * interval) + backend.Commit() + if chbook.balance.Cmp(balance) != 0 { + t.Fatalf("expected balance %v, got %v", balance, chbook.balance) + } + + exp = new(big.Int).SetUint64(40) + chbook.AutoDeposit(4*interval, exp, balance) + _, err = chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + _, err = chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + time.Sleep(3 * interval) + backend.Commit() + if chbook.balance.Cmp(exp) != 0 { + t.Fatalf("expected balance %v, got %v", exp, chbook.balance) + } + + _, err = chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + time.Sleep(1 * interval) + backend.Commit() + + if chbook.balance.Cmp(balance) != 0 { + t.Fatalf("expected balance %v, got %v", balance, chbook.balance) + } + + chbook.AutoDeposit(1*interval, common.Big0, balance) + chbook.Stop() + + _, err = chbook.Issue(addr1, common.Big1) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + + _, err = chbook.Issue(addr1, common.Big2) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + time.Sleep(1 * interval) + backend.Commit() + + exp = new(big.Int).SetUint64(39) + if chbook.balance.Cmp(exp) != 0 { + t.Fatalf("expected balance %v, got %v", exp, chbook.balance) + } + +} + +func TestCash(t *testing.T) { + path := filepath.Join(os.TempDir(), "chequebook-test.json") + backend := newTestBackend() + contr0, err := deploy(key0, common.Big2, backend) + chbook, err := NewChequebook(path, contr0, key0, backend) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + chbook.sent[addr1] = new(big.Int).SetUint64(42) + amount := common.Big1 + chbook.balance = new(big.Int).Set(common.Big1) + ch, err := chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + chbox, err := NewInbox(key1, contr0, addr1, &key0.PublicKey, backend) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + // cashing latest cheque + _, err = chbox.Receive(ch) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + _, err = ch.Cash(chbook.session) + backend.Commit() + + chbook.balance = new(big.Int).Set(common.Big3) + ch0, err := chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + ch1, err := chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + + interval := 10 * time.Millisecond + // setting autocash with interval of 10ms + chbox.AutoCash(interval, nil) + _, err = chbox.Receive(ch0) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + _, err = chbox.Receive(ch1) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + // expBalance := big.NewInt(2) + // gotBalance := backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } + // after 3x interval time and 2 cheques received, exactly one cashing tx is sent + time.Sleep(4 * interval) + backend.Commit() + + // expBalance = big.NewInt(4) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } + + // after stopping autocash no more tx are sent + ch2, err := chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + chbox.Stop() + _, err = chbox.Receive(ch2) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + time.Sleep(2 * interval) + backend.Commit() + // expBalance = big.NewInt(4) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } + + // autocash below 1 + chbook.balance = big.NewInt(2) + chbox.AutoCash(0, common.Big1) + + ch3, err := chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + // expBalance = big.NewInt(4) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } + + ch4, err := chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + + _, err = chbox.Receive(ch3) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + _, err = chbox.Receive(ch4) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + + // 2 checks of amount 1 received, exactly 1 tx is sent + // expBalance = big.NewInt(6) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } + + // autochash on receipt when maxUncashed is 0 + chbook.balance = new(big.Int).Set(common.Big2) + chbox.AutoCash(0, common.Big0) + + ch5, err := chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + // expBalance = big.NewInt(5) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } + + ch6, err := chbook.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + _, err = chbox.Receive(ch5) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + // expBalance = big.NewInt(4) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } + + _, err = chbox.Receive(ch6) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + backend.Commit() + // expBalance = big.NewInt(6) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } + +} diff --git a/contracts/chequebook/contract/chequebook.go b/contracts/chequebook/contract/chequebook.go new file mode 100644 index 000000000..1204f78da --- /dev/null +++ b/contracts/chequebook/contract/chequebook.go @@ -0,0 +1,554 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package contract + +import ( + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// ChequebookABI is the input ABI used to generate the binding from. +const ChequebookABI = `[{"constant":false,"inputs":[],"name":"kill","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"sent","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"beneficiary","type":"address"},{"name":"amount","type":"uint256"},{"name":"sig_v","type":"uint8"},{"name":"sig_r","type":"bytes32"},{"name":"sig_s","type":"bytes32"}],"name":"cash","outputs":[],"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"deadbeat","type":"address"}],"name":"Overdraft","type":"event"}]` + +// ChequebookBin is the compiled bytecode used for deploying new contracts. +const ChequebookBin = `0x606060405260008054600160a060020a03191633179055610223806100246000396000f3606060405260e060020a600035046341c0e1b581146100315780637bf786f814610059578063fbf788d614610071575b005b61002f60005433600160a060020a03908116911614156100b957600054600160a060020a0316ff5b6100a760043560016020526000908152604090205481565b61002f600435602435604435606435608435600160a060020a03851660009081526001602052604081205485116100bb576101cf565b60408051918252519081900360200190f35b565b50604080516c0100000000000000000000000030600160a060020a0390811682028352881602601482015260288101869052815190819003604801812080825260ff861660208381019190915282840186905260608301859052925190926001926080818101939182900301816000866161da5a03f11561000257505060405151600054600160a060020a03908116911614610156576101cf565b600160a060020a038681166000908152600160205260409020543090911631908603106101d75760406000818120549151600160a060020a0389169288039082818181858883f19350505050156101cf57846001600050600088600160a060020a03168152602001908152602001600020600050819055505b505050505050565b60005460408051600160a060020a03929092168252517f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f9789181900360200190a185600160a060020a0316ff` + +// DeployChequebook deploys a new Ethereum contract, binding an instance of Chequebook to it. +func DeployChequebook(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Chequebook, error) { + parsed, err := abi.JSON(strings.NewReader(ChequebookABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ChequebookBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}}, nil +} + +// Chequebook is an auto generated Go binding around an Ethereum contract. +type Chequebook struct { + ChequebookCaller // Read-only binding to the contract + ChequebookTransactor // Write-only binding to the contract +} + +// ChequebookCaller is an auto generated read-only Go binding around an Ethereum contract. +type ChequebookCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ChequebookTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ChequebookTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ChequebookSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ChequebookSession struct { + Contract *Chequebook // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ChequebookCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ChequebookCallerSession struct { + Contract *ChequebookCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ChequebookTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ChequebookTransactorSession struct { + Contract *ChequebookTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ChequebookRaw is an auto generated low-level Go binding around an Ethereum contract. +type ChequebookRaw struct { + Contract *Chequebook // Generic contract binding to access the raw methods on +} + +// ChequebookCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ChequebookCallerRaw struct { + Contract *ChequebookCaller // Generic read-only contract binding to access the raw methods on +} + +// ChequebookTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ChequebookTransactorRaw struct { + Contract *ChequebookTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewChequebook creates a new instance of Chequebook, bound to a specific deployed contract. +func NewChequebook(address common.Address, backend bind.ContractBackend) (*Chequebook, error) { + contract, err := bindChequebook(address, backend, backend) + if err != nil { + return nil, err + } + return &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}}, nil +} + +// NewChequebookCaller creates a new read-only instance of Chequebook, bound to a specific deployed contract. +func NewChequebookCaller(address common.Address, caller bind.ContractCaller) (*ChequebookCaller, error) { + contract, err := bindChequebook(address, caller, nil) + if err != nil { + return nil, err + } + return &ChequebookCaller{contract: contract}, nil +} + +// NewChequebookTransactor creates a new write-only instance of Chequebook, bound to a specific deployed contract. +func NewChequebookTransactor(address common.Address, transactor bind.ContractTransactor) (*ChequebookTransactor, error) { + contract, err := bindChequebook(address, nil, transactor) + if err != nil { + return nil, err + } + return &ChequebookTransactor{contract: contract}, nil +} + +// bindChequebook binds a generic wrapper to an already deployed contract. +func bindChequebook(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ChequebookABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Chequebook *ChequebookRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Chequebook.Contract.ChequebookCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Chequebook *ChequebookRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Chequebook.Contract.ChequebookTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Chequebook *ChequebookRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Chequebook.Contract.ChequebookTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Chequebook *ChequebookCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Chequebook.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Chequebook *ChequebookTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Chequebook.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Chequebook *ChequebookTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Chequebook.Contract.contract.Transact(opts, method, params...) +} + +// Sent is a free data retrieval call binding the contract method 0x7bf786f8. +// +// Solidity: function sent( address) constant returns(uint256) +func (_Chequebook *ChequebookCaller) Sent(opts *bind.CallOpts, arg0 common.Address) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _Chequebook.contract.Call(opts, out, "sent", arg0) + return *ret0, err +} + +// Sent is a free data retrieval call binding the contract method 0x7bf786f8. +// +// Solidity: function sent( address) constant returns(uint256) +func (_Chequebook *ChequebookSession) Sent(arg0 common.Address) (*big.Int, error) { + return _Chequebook.Contract.Sent(&_Chequebook.CallOpts, arg0) +} + +// Sent is a free data retrieval call binding the contract method 0x7bf786f8. +// +// Solidity: function sent( address) constant returns(uint256) +func (_Chequebook *ChequebookCallerSession) Sent(arg0 common.Address) (*big.Int, error) { + return _Chequebook.Contract.Sent(&_Chequebook.CallOpts, arg0) +} + +// Cash is a paid mutator transaction binding the contract method 0xfbf788d6. +// +// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns() +func (_Chequebook *ChequebookTransactor) Cash(opts *bind.TransactOpts, beneficiary common.Address, amount *big.Int, sig_v uint8, sig_r [32]byte, sig_s [32]byte) (*types.Transaction, error) { + return _Chequebook.contract.Transact(opts, "cash", beneficiary, amount, sig_v, sig_r, sig_s) +} + +// Cash is a paid mutator transaction binding the contract method 0xfbf788d6. +// +// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns() +func (_Chequebook *ChequebookSession) Cash(beneficiary common.Address, amount *big.Int, sig_v uint8, sig_r [32]byte, sig_s [32]byte) (*types.Transaction, error) { + return _Chequebook.Contract.Cash(&_Chequebook.TransactOpts, beneficiary, amount, sig_v, sig_r, sig_s) +} + +// Cash is a paid mutator transaction binding the contract method 0xfbf788d6. +// +// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns() +func (_Chequebook *ChequebookTransactorSession) Cash(beneficiary common.Address, amount *big.Int, sig_v uint8, sig_r [32]byte, sig_s [32]byte) (*types.Transaction, error) { + return _Chequebook.Contract.Cash(&_Chequebook.TransactOpts, beneficiary, amount, sig_v, sig_r, sig_s) +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Chequebook *ChequebookTransactor) Kill(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Chequebook.contract.Transact(opts, "kill") +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Chequebook *ChequebookSession) Kill() (*types.Transaction, error) { + return _Chequebook.Contract.Kill(&_Chequebook.TransactOpts) +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Chequebook *ChequebookTransactorSession) Kill() (*types.Transaction, error) { + return _Chequebook.Contract.Kill(&_Chequebook.TransactOpts) +} + +// MortalABI is the input ABI used to generate the binding from. +const MortalABI = `[{"constant":false,"inputs":[],"name":"kill","outputs":[],"type":"function"}]` + +// MortalBin is the compiled bytecode used for deploying new contracts. +const MortalBin = `0x606060405260008054600160a060020a03191633179055605c8060226000396000f3606060405260e060020a600035046341c0e1b58114601a575b005b60186000543373ffffffffffffffffffffffffffffffffffffffff90811691161415605a5760005473ffffffffffffffffffffffffffffffffffffffff16ff5b56` + +// DeployMortal deploys a new Ethereum contract, binding an instance of Mortal to it. +func DeployMortal(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Mortal, error) { + parsed, err := abi.JSON(strings.NewReader(MortalABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(MortalBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Mortal{MortalCaller: MortalCaller{contract: contract}, MortalTransactor: MortalTransactor{contract: contract}}, nil +} + +// Mortal is an auto generated Go binding around an Ethereum contract. +type Mortal struct { + MortalCaller // Read-only binding to the contract + MortalTransactor // Write-only binding to the contract +} + +// MortalCaller is an auto generated read-only Go binding around an Ethereum contract. +type MortalCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MortalTransactor is an auto generated write-only Go binding around an Ethereum contract. +type MortalTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MortalSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type MortalSession struct { + Contract *Mortal // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MortalCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type MortalCallerSession struct { + Contract *MortalCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// MortalTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type MortalTransactorSession struct { + Contract *MortalTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MortalRaw is an auto generated low-level Go binding around an Ethereum contract. +type MortalRaw struct { + Contract *Mortal // Generic contract binding to access the raw methods on +} + +// MortalCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type MortalCallerRaw struct { + Contract *MortalCaller // Generic read-only contract binding to access the raw methods on +} + +// MortalTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type MortalTransactorRaw struct { + Contract *MortalTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewMortal creates a new instance of Mortal, bound to a specific deployed contract. +func NewMortal(address common.Address, backend bind.ContractBackend) (*Mortal, error) { + contract, err := bindMortal(address, backend, backend) + if err != nil { + return nil, err + } + return &Mortal{MortalCaller: MortalCaller{contract: contract}, MortalTransactor: MortalTransactor{contract: contract}}, nil +} + +// NewMortalCaller creates a new read-only instance of Mortal, bound to a specific deployed contract. +func NewMortalCaller(address common.Address, caller bind.ContractCaller) (*MortalCaller, error) { + contract, err := bindMortal(address, caller, nil) + if err != nil { + return nil, err + } + return &MortalCaller{contract: contract}, nil +} + +// NewMortalTransactor creates a new write-only instance of Mortal, bound to a specific deployed contract. +func NewMortalTransactor(address common.Address, transactor bind.ContractTransactor) (*MortalTransactor, error) { + contract, err := bindMortal(address, nil, transactor) + if err != nil { + return nil, err + } + return &MortalTransactor{contract: contract}, nil +} + +// bindMortal binds a generic wrapper to an already deployed contract. +func bindMortal(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(MortalABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Mortal *MortalRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Mortal.Contract.MortalCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Mortal *MortalRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Mortal.Contract.MortalTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Mortal *MortalRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Mortal.Contract.MortalTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Mortal *MortalCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Mortal.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Mortal *MortalTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Mortal.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Mortal *MortalTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Mortal.Contract.contract.Transact(opts, method, params...) +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Mortal *MortalTransactor) Kill(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Mortal.contract.Transact(opts, "kill") +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Mortal *MortalSession) Kill() (*types.Transaction, error) { + return _Mortal.Contract.Kill(&_Mortal.TransactOpts) +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Mortal *MortalTransactorSession) Kill() (*types.Transaction, error) { + return _Mortal.Contract.Kill(&_Mortal.TransactOpts) +} + +// OwnedABI is the input ABI used to generate the binding from. +const OwnedABI = `[{"inputs":[],"type":"constructor"}]` + +// OwnedBin is the compiled bytecode used for deploying new contracts. +const OwnedBin = `0x606060405260008054600160a060020a0319163317905560068060226000396000f3606060405200` + +// DeployOwned deploys a new Ethereum contract, binding an instance of Owned to it. +func DeployOwned(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Owned, error) { + parsed, err := abi.JSON(strings.NewReader(OwnedABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(OwnedBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Owned{OwnedCaller: OwnedCaller{contract: contract}, OwnedTransactor: OwnedTransactor{contract: contract}}, nil +} + +// Owned is an auto generated Go binding around an Ethereum contract. +type Owned struct { + OwnedCaller // Read-only binding to the contract + OwnedTransactor // Write-only binding to the contract +} + +// OwnedCaller is an auto generated read-only Go binding around an Ethereum contract. +type OwnedCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OwnedTransactor is an auto generated write-only Go binding around an Ethereum contract. +type OwnedTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OwnedSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type OwnedSession struct { + Contract *Owned // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OwnedCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type OwnedCallerSession struct { + Contract *OwnedCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// OwnedTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type OwnedTransactorSession struct { + Contract *OwnedTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OwnedRaw is an auto generated low-level Go binding around an Ethereum contract. +type OwnedRaw struct { + Contract *Owned // Generic contract binding to access the raw methods on +} + +// OwnedCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type OwnedCallerRaw struct { + Contract *OwnedCaller // Generic read-only contract binding to access the raw methods on +} + +// OwnedTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type OwnedTransactorRaw struct { + Contract *OwnedTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewOwned creates a new instance of Owned, bound to a specific deployed contract. +func NewOwned(address common.Address, backend bind.ContractBackend) (*Owned, error) { + contract, err := bindOwned(address, backend, backend) + if err != nil { + return nil, err + } + return &Owned{OwnedCaller: OwnedCaller{contract: contract}, OwnedTransactor: OwnedTransactor{contract: contract}}, nil +} + +// NewOwnedCaller creates a new read-only instance of Owned, bound to a specific deployed contract. +func NewOwnedCaller(address common.Address, caller bind.ContractCaller) (*OwnedCaller, error) { + contract, err := bindOwned(address, caller, nil) + if err != nil { + return nil, err + } + return &OwnedCaller{contract: contract}, nil +} + +// NewOwnedTransactor creates a new write-only instance of Owned, bound to a specific deployed contract. +func NewOwnedTransactor(address common.Address, transactor bind.ContractTransactor) (*OwnedTransactor, error) { + contract, err := bindOwned(address, nil, transactor) + if err != nil { + return nil, err + } + return &OwnedTransactor{contract: contract}, nil +} + +// bindOwned binds a generic wrapper to an already deployed contract. +func bindOwned(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(OwnedABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Owned *OwnedRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Owned.Contract.OwnedCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Owned *OwnedRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Owned.Contract.OwnedTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Owned *OwnedRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Owned.Contract.OwnedTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Owned *OwnedCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Owned.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Owned *OwnedTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Owned.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Owned *OwnedTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Owned.Contract.contract.Transact(opts, method, params...) +} diff --git a/contracts/chequebook/contract/chequebook.sol b/contracts/chequebook/contract/chequebook.sol new file mode 100644 index 000000000..cb19d0b27 --- /dev/null +++ b/contracts/chequebook/contract/chequebook.sol @@ -0,0 +1,43 @@ +import "mortal"; + +/// @title Chequebook for Ethereum micropayments +/// @author Daniel A. Nagy +contract chequebook is mortal { + // Cumulative paid amount in wei to each beneficiary + mapping (address => uint256) public sent; + + /// @notice Overdraft event + event Overdraft(address deadbeat); + + /// @notice Cash cheque + /// + /// @param beneficiary beneficiary address + /// @param amount cumulative amount in wei + /// @param sig_v signature parameter v + /// @param sig_r signature parameter r + /// @param sig_s signature parameter s + /// The digital signature is calculated on the concatenated triplet of contract address, beneficiary address and cumulative amount + function cash(address beneficiary, uint256 amount, + uint8 sig_v, bytes32 sig_r, bytes32 sig_s) { + // Check if the cheque is old. + // Only cheques that are more recent than the last cashed one are considered. + if(amount <= sent[beneficiary]) return; + // Check the digital signature of the cheque. + bytes32 hash = sha3(address(this), beneficiary, amount); + if(owner != ecrecover(hash, sig_v, sig_r, sig_s)) return; + // Attempt sending the difference between the cumulative amount on the cheque + // and the cumulative amount on the last cashed cheque to beneficiary. + if (amount - sent[beneficiary] >= this.balance) { + if (beneficiary.send(amount - sent[beneficiary])) { + // Upon success, update the cumulative amount. + sent[beneficiary] = amount; + } + } else { + // Upon failure, punish owner for writing a bounced cheque. + // owner.sendToDebtorsPrison(); + Overdraft(owner); + // Compensate beneficiary. + suicide(beneficiary); + } + } +} diff --git a/contracts/chequebook/contract/code.go b/contracts/chequebook/contract/code.go new file mode 100644 index 000000000..3f11ebfdb --- /dev/null +++ b/contracts/chequebook/contract/code.go @@ -0,0 +1,21 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package contract + +// ContractDeployedCode is used to detect suicides. This constant needs to be +// updated when the contract code is changed. +const ContractDeployedCode = "0x606060405260e060020a600035046341c0e1b581146100315780637bf786f814610059578063fbf788d614610071575b005b61002f60005433600160a060020a03908116911614156100b957600054600160a060020a0316ff5b6100a760043560016020526000908152604090205481565b61002f600435602435604435606435608435600160a060020a03851660009081526001602052604081205485116100bb576101cf565b60408051918252519081900360200190f35b565b50604080516c0100000000000000000000000030600160a060020a0390811682028352881602601482015260288101869052815190819003604801812080825260ff861660208381019190915282840186905260608301859052925190926001926080818101939182900301816000866161da5a03f11561000257505060405151600054600160a060020a03908116911614610156576101cf565b600160a060020a038681166000908152600160205260409020543090911631908603106101d75760406000818120549151600160a060020a0389169288039082818181858883f19350505050156101cf57846001600050600088600160a060020a03168152602001908152602001600020600050819055505b505050505050565b60005460408051600160a060020a03929092168252517f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f9789181900360200190a185600160a060020a0316ff" diff --git a/contracts/chequebook/gencode.go b/contracts/chequebook/gencode.go new file mode 100644 index 000000000..f52c351e4 --- /dev/null +++ b/contracts/chequebook/gencode.go @@ -0,0 +1,73 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build none + +// This program generates contract/code.go, which contains the chequebook code +// after deployment. +package main + +import ( + "fmt" + "io/ioutil" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/contracts/chequebook/contract" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" +) + +var ( + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testAccount = core.GenesisAccount{ + Address: crypto.PubkeyToAddress(testKey.PublicKey), + Balance: big.NewInt(500000000000), + } +) + +func main() { + backend := backends.NewSimulatedBackend(testAccount) + auth := bind.NewKeyedTransactor(testKey) + + // Deploy the contract, get the code. + addr, _, _, err := contract.DeployChequebook(auth, backend) + if err != nil { + panic(err) + } + backend.Commit() + code, err := backend.CodeAt(nil, addr, nil) + if err != nil { + panic(err) + } + if len(code) == 0 { + panic("empty code") + } + + // Write the output file. + content := fmt.Sprintf(`// TODO: insert license header + +package contract + +// ContractDeployedCode is used to detect suicides. This constant needs to be +// updated when the contract code is changed. +const ContractDeployedCode = "%#x" +`, code) + if err := ioutil.WriteFile("contract/code.go", []byte(content), 0644); err != nil { + panic(err) + } +}