github.com/iotexproject/iotex-core@v1.14.1-rc1/tools/executiontester/blockchain/contract.go (about)

     1  // Copyright (c) 2019 IoTeX
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package blockchain
     7  
     8  import (
     9  	"context"
    10  	"encoding/hex"
    11  	"log"
    12  	"math/big"
    13  	"strconv"
    14  	"time"
    15  
    16  	"github.com/cenkalti/backoff"
    17  	"github.com/iotexproject/go-pkgs/crypto"
    18  	"github.com/iotexproject/go-pkgs/hash"
    19  	"github.com/iotexproject/iotex-address/address"
    20  	"github.com/iotexproject/iotex-proto/golang/iotexapi"
    21  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    22  	"github.com/pkg/errors"
    23  	"google.golang.org/grpc"
    24  
    25  	"github.com/iotexproject/iotex-core/action"
    26  )
    27  
    28  const (
    29  	// Producer indicates block producer's encoded address
    30  	Producer = "io13rjq2c07mqhe8sdd7nf9a4vcmnyk9mn72hu94e"
    31  	// ProducerPubKey indicates block producer's public key
    32  	ProducerPubKey = "04403d3c0dbd3270ddfc248c3df1f9aafd60f1d8e7456961c9ef26292262cc68f0ea9690263bef9e197a38f06026814fc70912c2b98d2e90a68f8ddc5328180a01"
    33  	// ProducerPrivKey indicates block producer's private key
    34  	ProducerPrivKey = "82a1556b2dbd0e3615e367edf5d3b90ce04346ec4d12ed71f67c70920ef9ac90"
    35  	// GasLimitPerByte indicates the gas limit for each byte
    36  	GasLimitPerByte uint64 = 100
    37  	// GasPrice indicates the gas price
    38  	GasPrice = 0
    39  )
    40  
    41  type (
    42  	// Contract is a basic contract
    43  	Contract interface {
    44  		Start() error
    45  		Exist(string) bool
    46  		Explorer() string
    47  		Deploy(string, ...[]byte) (string, error)
    48  		Read(string, ...[]byte) (string, error)
    49  		ReadValue(string, string, string) (int64, error)
    50  		ReadAndParseToDecimal(string, string, string) (string, error)
    51  		Call(string, ...[]byte) (string, error)
    52  		Transact([]byte, bool) (string, error)
    53  		CheckCallResult(string) (*iotextypes.Receipt, error)
    54  
    55  		Address() string
    56  		SetAddress(string) Contract
    57  		SetOwner(string, string) Contract
    58  		SetExecutor(string) Contract
    59  		SetPrvKey(string) Contract
    60  		RunAsOwner() Contract
    61  	}
    62  
    63  	contract struct {
    64  		cs       string // blockchain service endpoint
    65  		owner    string // owner of the smart contract
    66  		ownerSk  string // owner's private key
    67  		addr     string // address of the smart contract
    68  		executor string // executor of the smart contract
    69  		prvkey   string // private key of executor
    70  	}
    71  )
    72  
    73  // NewContract creates a new contract
    74  func NewContract(exp string) Contract {
    75  	return &contract{cs: exp}
    76  }
    77  
    78  func (c *contract) Start() error {
    79  	return nil
    80  }
    81  
    82  func (c *contract) Exist(addr string) bool {
    83  	// TODO: add blockchain API to query if the contract exist or not
    84  	return true
    85  }
    86  
    87  func (c *contract) Explorer() string {
    88  	return c.cs
    89  }
    90  
    91  // ReadValue reads the value
    92  func (c *contract) ReadValue(token, method, sender string) (int64, error) {
    93  	addrSender, err := address.FromString(sender)
    94  	if err != nil {
    95  		return 0, errors.Errorf("invalid account address = %s", sender)
    96  	}
    97  	// check sender balance
    98  	res, err := c.RunAsOwner().SetAddress(token).Read(method, addrSender.Bytes())
    99  	if err != nil {
   100  		return 0, errors.Wrapf(err, "failed to read bytes")
   101  	}
   102  	return strconv.ParseInt(res, 16, 64)
   103  }
   104  
   105  // ReadAndParseToDecimal reads the value and parse to decimal string
   106  func (c *contract) ReadAndParseToDecimal(contract, method, sender string) (string, error) {
   107  	addrSender, err := address.FromString(sender)
   108  	if err != nil {
   109  		return "", errors.Errorf("invalid account address = %s", sender)
   110  	}
   111  	// check sender balance
   112  	res, err := c.RunAsOwner().SetAddress(contract).Read(method, addrSender.Bytes())
   113  	if err != nil {
   114  		return "", errors.Wrapf(err, "failed to read bytes")
   115  	}
   116  	bal, err := strconv.ParseInt(res, 16, 64)
   117  	if err != nil {
   118  		return "", errors.Wrapf(err, "failed to convert bytes")
   119  	}
   120  	return strconv.FormatInt(bal, 10), nil
   121  }
   122  
   123  func (c *contract) Read(method string, args ...[]byte) (string, error) {
   124  	data, err := hex.DecodeString(method)
   125  	if err != nil {
   126  		return "", err
   127  	}
   128  	if len(data) != 4 {
   129  		return "", errors.Errorf("invalid method id format, length = %d", len(data))
   130  	}
   131  	for _, arg := range args {
   132  		if arg != nil {
   133  			if len(arg) < 32 {
   134  				value := hash.BytesToHash256(arg)
   135  				data = append(data, value[:]...)
   136  			} else {
   137  				data = append(data, arg...)
   138  			}
   139  		}
   140  	}
   141  	return c.Transact(data, true)
   142  }
   143  
   144  func (c *contract) Call(method string, args ...[]byte) (string, error) {
   145  	data, err := hex.DecodeString(method)
   146  	if err != nil {
   147  		return "", err
   148  	}
   149  	if len(data) != 4 {
   150  		return "", errors.Errorf("invalid method id format, length = %d", len(data))
   151  	}
   152  	for _, arg := range args {
   153  		if arg != nil {
   154  			if len(arg) < 32 {
   155  				value := hash.BytesToHash256(arg)
   156  				data = append(data, value[:]...)
   157  			} else {
   158  				data = append(data, arg...)
   159  			}
   160  		}
   161  	}
   162  	return c.Transact(data, false)
   163  }
   164  
   165  func (c *contract) Deploy(hexCode string, args ...[]byte) (string, error) {
   166  	data, err := hex.DecodeString(hexCode)
   167  	if err != nil {
   168  		return "", err
   169  	}
   170  	for _, arg := range args {
   171  		if arg != nil {
   172  			if len(arg) < 32 {
   173  				value := hash.BytesToHash256(arg)
   174  				data = append(data, value[:]...)
   175  			} else {
   176  				data = append(data, arg...)
   177  			}
   178  		}
   179  	}
   180  	// deploy send to empty address
   181  	return c.SetAddress("").Transact(data, false)
   182  }
   183  
   184  func (c *contract) Transact(data []byte, readOnly bool) (string, error) {
   185  	conn, err := grpc.Dial(c.cs, grpc.WithInsecure())
   186  	if err != nil {
   187  		return "", err
   188  	}
   189  	defer conn.Close()
   190  
   191  	// get executor's nounce
   192  	cli := iotexapi.NewAPIServiceClient(conn)
   193  	ctx := context.Background()
   194  	response, err := cli.GetAccount(ctx, &iotexapi.GetAccountRequest{Address: c.executor})
   195  	if err != nil {
   196  		return "", err
   197  	}
   198  	nonce := response.AccountMeta.PendingNonce
   199  	gasPrice := big.NewInt(0)
   200  	gasLimit := GasLimitPerByte*uint64(len(data)) + 5000000
   201  	tx, err := action.NewExecution(
   202  		c.addr,
   203  		nonce,
   204  		big.NewInt(0),
   205  		gasLimit,
   206  		gasPrice,
   207  		data)
   208  	if err != nil {
   209  		return "", err
   210  	}
   211  
   212  	if readOnly {
   213  		response, err := cli.ReadContract(ctx, &iotexapi.ReadContractRequest{
   214  			Execution:     tx.Proto(),
   215  			CallerAddress: c.executor,
   216  		})
   217  		if err != nil {
   218  			return "", err
   219  		}
   220  		return response.Data, nil
   221  	}
   222  
   223  	bd := &action.EnvelopeBuilder{}
   224  	elp := bd.SetNonce(nonce).
   225  		SetGasPrice(gasPrice).
   226  		SetGasLimit(gasLimit).
   227  		SetAction(tx).Build()
   228  	prvKey, err := crypto.HexStringToPrivateKey(c.prvkey)
   229  	if err != nil {
   230  		return "", crypto.ErrInvalidKey
   231  	}
   232  	defer prvKey.Zero()
   233  	selp, err := action.Sign(elp, prvKey)
   234  	prvKey.Zero()
   235  	if err != nil {
   236  		return "", err
   237  	}
   238  
   239  	_, err = cli.SendAction(ctx, &iotexapi.SendActionRequest{Action: selp.Proto()})
   240  	h, hashErr := selp.Hash()
   241  	if hashErr != nil {
   242  		return "", hashErr
   243  	}
   244  	hex := hex.EncodeToString(h[:])
   245  	if err != nil {
   246  		return hex, errors.Wrapf(err, "tx 0x%s failed to send to Blockchain", hex)
   247  	}
   248  	log.Printf("\n\ntx hash = %s\n\n", hex)
   249  	return hex, nil
   250  }
   251  
   252  func (c *contract) CheckCallResult(h string) (*iotextypes.Receipt, error) {
   253  	var rec *iotextypes.Receipt
   254  	// max retry 120 times with interval = 500ms
   255  	checkNum := 120
   256  	err := backoff.Retry(func() error {
   257  		var err error
   258  		rec, err = c.checkCallResult(h)
   259  		log.Printf("Hash: %s <= CheckNum: %d", h, checkNum)
   260  		checkNum--
   261  		return err
   262  	}, backoff.WithMaxRetries(backoff.NewConstantBackOff(time.Millisecond*500), uint64(checkNum)))
   263  	return rec, err
   264  }
   265  
   266  func (c *contract) checkCallResult(h string) (*iotextypes.Receipt, error) {
   267  	conn, err := grpc.Dial(c.cs, grpc.WithInsecure())
   268  	if err != nil {
   269  		return nil, err
   270  	}
   271  	defer conn.Close()
   272  
   273  	cli := iotexapi.NewAPIServiceClient(conn)
   274  	ctx := context.Background()
   275  	response, err := cli.GetReceiptByAction(ctx, &iotexapi.GetReceiptByActionRequest{ActionHash: h})
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  	if response.ReceiptInfo.Receipt.Status != 1 {
   280  		return nil, errors.Errorf("tx 0x%s execution on Blockchain failed", h)
   281  	}
   282  	// TODO: check topics
   283  	return response.ReceiptInfo.Receipt, nil
   284  }
   285  
   286  func (c *contract) Address() string {
   287  	return c.addr
   288  }
   289  
   290  func (c *contract) SetAddress(addr string) Contract {
   291  	c.addr = addr
   292  	return c
   293  }
   294  
   295  func (c *contract) SetOwner(owner, sk string) Contract {
   296  	c.owner = owner
   297  	c.ownerSk = sk
   298  	return c
   299  }
   300  
   301  func (c *contract) SetExecutor(exec string) Contract {
   302  	c.executor = exec
   303  	return c
   304  }
   305  
   306  func (c *contract) SetPrvKey(prvk string) Contract {
   307  	c.prvkey = prvk
   308  	return c
   309  }
   310  
   311  func (c *contract) RunAsOwner() Contract {
   312  	c.executor = c.owner
   313  	c.prvkey = c.ownerSk
   314  	return c
   315  }