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 }