github.com/mavryk-network/mvgo@v1.19.9/contract/contract.go (about) 1 // Copyright (c) 2020-2022 Blockwatch Data Inc. 2 // Author: alex@blockwatch.cc 3 4 package contract 5 6 import ( 7 "context" 8 "fmt" 9 10 "github.com/mavryk-network/mvgo/codec" 11 "github.com/mavryk-network/mvgo/mavryk" 12 "github.com/mavryk-network/mvgo/micheline" 13 "github.com/mavryk-network/mvgo/rpc" 14 ) 15 16 type CallArguments interface { 17 WithSource(mavryk.Address) CallArguments 18 WithDestination(mavryk.Address) CallArguments 19 WithAmount(mavryk.N) CallArguments 20 Encode() *codec.Transaction 21 Parameters() *micheline.Parameters 22 } 23 24 type TxArgs struct { 25 Source mavryk.Address 26 Destination mavryk.Address 27 Amount mavryk.N 28 Params micheline.Parameters 29 } 30 31 func NewTxArgs() *TxArgs { 32 return &TxArgs{} 33 } 34 35 func (a *TxArgs) WithSource(addr mavryk.Address) CallArguments { 36 a.Source = addr.Clone() 37 return a 38 } 39 40 func (a *TxArgs) WithDestination(addr mavryk.Address) CallArguments { 41 a.Destination = addr.Clone() 42 return a 43 } 44 45 func (a *TxArgs) WithAmount(amount mavryk.N) CallArguments { 46 a.Amount = amount 47 return a 48 } 49 50 func (a *TxArgs) WithParameters(params micheline.Parameters) { 51 a.Params = params 52 } 53 54 func (a *TxArgs) Parameters() *micheline.Parameters { 55 return &a.Params 56 } 57 58 func (a *TxArgs) Encode() *codec.Transaction { 59 return &codec.Transaction{ 60 Manager: codec.Manager{ 61 Source: a.Source, 62 }, 63 Amount: a.Amount, 64 Destination: a.Destination, 65 Parameters: &a.Params, 66 } 67 } 68 69 type Contract struct { 70 addr mavryk.Address // contract address 71 script *micheline.Script // script (type info + code) 72 store *micheline.Prim // current storage value 73 meta *Tz16 // Tzip16 metadata 74 rpc *rpc.Client // the RPC client to use for queries and calls 75 } 76 77 func NewContract(addr mavryk.Address, cli *rpc.Client) *Contract { 78 return &Contract{ 79 addr: addr, 80 rpc: cli, 81 } 82 } 83 84 func NewEmptyContract(cli *rpc.Client) *Contract { 85 return &Contract{ 86 rpc: cli, 87 } 88 } 89 90 func (c *Contract) Client() *rpc.Client { 91 return c.rpc 92 } 93 94 func (c *Contract) Resolve(ctx context.Context) error { 95 // use normalized script to have the node embed global constants 96 script, err := c.rpc.GetNormalizedScript(ctx, c.addr, rpc.UnparsingModeReadable) 97 if err != nil { 98 return err 99 } 100 store, err := c.rpc.GetContractStorage(ctx, c.addr, rpc.Head) 101 if err != nil { 102 return err 103 } 104 c.script = script 105 c.store = &store 106 return nil 107 } 108 109 func (c *Contract) Reload(ctx context.Context) error { 110 store, err := c.rpc.GetContractStorage(ctx, c.addr, rpc.Head) 111 if err != nil { 112 return err 113 } 114 c.store = &store 115 return nil 116 } 117 118 func (c *Contract) ResolveMetadata(ctx context.Context) (*Tz16, error) { 119 if c.meta != nil { 120 return c.meta, nil 121 } 122 if c.script == nil { 123 if err := c.Resolve(ctx); err != nil { 124 return nil, err 125 } 126 } 127 tz16 := &Tz16{} 128 if err := c.resolveStorageUri(ctx, "mavryk-storage:", tz16, nil); err != nil { 129 return nil, err 130 } 131 c.meta = tz16 132 return tz16, nil 133 } 134 135 func (c *Contract) WithScript(script *micheline.Script) *Contract { 136 c.script = script 137 return c 138 } 139 140 func (c *Contract) DecodeScript(data []byte) error { 141 var script micheline.Script 142 if err := script.UnmarshalBinary(data); err != nil { 143 return err 144 } 145 c.script = &script 146 return nil 147 } 148 149 func (c *Contract) WithStorage(store *micheline.Prim) *Contract { 150 c.store = store 151 return c 152 } 153 154 func (c *Contract) DecodeStorage(data []byte) error { 155 var store micheline.Prim 156 if err := store.UnmarshalBinary(data); err != nil { 157 return err 158 } 159 c.store = &store 160 return nil 161 } 162 163 func (c Contract) Address() mavryk.Address { 164 return c.addr 165 } 166 167 func (c Contract) IsManagerTz() bool { 168 return c.script != nil && c.script.Implements(micheline.IManager) 169 } 170 171 func (c Contract) IsToken() bool { 172 return c.IsFA1() || c.IsFA12() || c.IsFA2() 173 } 174 175 func (c Contract) TokenKind() TokenKind { 176 switch { 177 case c.IsFA1(): 178 return TokenKindFA1 179 case c.IsFA12(): 180 return TokenKindFA1_2 181 case c.IsFA2(): 182 return TokenKindFA2 183 default: 184 return TokenKindInvalid 185 } 186 } 187 188 func (c Contract) IsFA1() bool { 189 return c.script != nil && c.script.Implements(micheline.ITzip5) 190 } 191 192 func (c Contract) IsFA12() bool { 193 return c.script != nil && c.script.Implements(micheline.ITzip7) 194 } 195 196 func (c Contract) IsFA2() bool { 197 return c.script != nil && c.script.Implements(micheline.ITzip12) 198 } 199 200 // func (c *Contract) IsNFT() bool {} 201 202 func (c *Contract) AsFA1() *FA1Token { 203 return &FA1Token{ 204 Address: c.addr, 205 contract: c, 206 } 207 } 208 209 func (c *Contract) AsFA2(id int64) *FA2Token { 210 fa2 := &FA2Token{ 211 Address: c.addr, 212 contract: c, 213 } 214 fa2.TokenId.SetInt64(id) 215 return fa2 216 } 217 218 // func (c *Contract) AsNFT() (*NFTToken, error) {} 219 220 func (c *Contract) Metadata() *Tz16 { 221 return c.meta 222 } 223 224 func (c Contract) Script() *micheline.Script { 225 return c.script 226 } 227 228 func (c Contract) Storage() *micheline.Prim { 229 return c.store 230 } 231 232 func (c Contract) StorageValue() micheline.Value { 233 return micheline.NewValue(c.script.StorageType(), *c.store) 234 } 235 236 // entrypoints and callbacks 237 func (c *Contract) Entrypoint(name string) (micheline.Entrypoint, bool) { 238 if c.script == nil { 239 return micheline.Entrypoint{}, false 240 } 241 eps, _ := c.script.Entrypoints(true) 242 ep, ok := eps[name] 243 return ep, ok 244 } 245 246 // on-chain views 247 func (c *Contract) View(name string) (micheline.View, bool) { 248 if c.script == nil { 249 return micheline.View{}, false 250 } 251 views, _ := c.script.Views(false, false) 252 view, ok := views[name] 253 return view, ok 254 } 255 256 // func (c *Contract) GetStorageValue(path string) (*micheline.Value, error) {} 257 258 func (c *Contract) GetBigmapValue(ctx context.Context, path string, args micheline.Prim) (*micheline.Value, error) { 259 store := c.StorageValue() 260 bigmap, ok := store.GetInt64(path) 261 if !ok { 262 return nil, fmt.Errorf("bigmap %q not found in storage", path) 263 } 264 typ, ok := c.script.Code.Storage.FindLabel(path) 265 if !ok { 266 return nil, fmt.Errorf("bigmap %q not found in type", path) 267 } 268 key, err := micheline.NewKey(micheline.NewType(typ.Args[0]), args) 269 if err != nil { 270 return nil, err 271 } 272 prim, err := c.rpc.GetActiveBigmapValue(ctx, bigmap, key.Hash()) 273 if err != nil { 274 return nil, err 275 } 276 // fmt.Printf("Bigmap value %s typ %s\n", prim.Dump(), typ.Args[1].Dump()) 277 val := micheline.NewValue(micheline.NewType(typ.Args[1]), prim) 278 return &val, nil 279 } 280 281 // Executes on-chain views from callback entrypoints 282 func (c *Contract) RunView(ctx context.Context, name string, args micheline.Prim) (micheline.Prim, error) { 283 req := rpc.RunViewRequest{ 284 Contract: c.addr, 285 View: name, 286 Input: args, 287 ChainId: c.rpc.ChainId, 288 Source: mavryk.ZeroAddress, 289 Payer: mavryk.ZeroAddress, 290 UnlimitedGas: true, 291 Mode: "Readable", 292 } 293 var res rpc.RunViewResponse 294 err := c.rpc.RunView(ctx, rpc.Head, &req, &res) 295 return res.Data, err 296 } 297 298 func (c *Contract) RunViewExt(ctx context.Context, name string, args micheline.Prim, source, payer mavryk.Address, gas int64) (micheline.Prim, error) { 299 req := rpc.RunViewRequest{ 300 Contract: c.addr, 301 View: name, 302 Input: args, 303 ChainId: c.rpc.ChainId, 304 Source: source, 305 Payer: payer, 306 Gas: mavryk.N(gas), 307 Mode: "Readable", 308 } 309 if gas == 0 { 310 req.UnlimitedGas = true 311 } 312 var res rpc.RunViewResponse 313 err := c.rpc.RunView(ctx, rpc.Head, &req, &res) 314 return res.Data, err 315 } 316 317 // Executes TZIP-4 callback-based views from callback entrypoints 318 func (c *Contract) RunCallback(ctx context.Context, name string, args micheline.Prim) (micheline.Prim, error) { 319 req := rpc.RunViewRequest{ 320 Contract: c.addr, 321 Entrypoint: name, 322 Input: args, 323 ChainId: c.rpc.ChainId, 324 Source: mavryk.ZeroAddress, 325 Payer: mavryk.ZeroAddress, 326 Gas: mavryk.N(1_000_000), // guess 327 Mode: "Readable", 328 } 329 var res rpc.RunViewResponse 330 err := c.rpc.RunCallback(ctx, rpc.Head, &req, &res) 331 return res.Data, err 332 } 333 334 func (c *Contract) RunCallbackExt(ctx context.Context, name string, args micheline.Prim, source, payer mavryk.Address, gas int64) (micheline.Prim, error) { 335 req := rpc.RunViewRequest{ 336 Contract: c.addr, 337 Entrypoint: name, 338 Input: args, 339 ChainId: c.rpc.ChainId, 340 Source: source, 341 Payer: payer, 342 Gas: mavryk.N(gas), 343 Mode: "Readable", 344 } 345 var res rpc.RunViewResponse 346 err := c.rpc.RunCallback(ctx, rpc.Head, &req, &res) 347 return res.Data, err 348 } 349 350 func (c *Contract) Call(ctx context.Context, args CallArguments, opts *rpc.CallOptions) (*rpc.Receipt, error) { 351 return c.CallMulti(ctx, []CallArguments{args}, opts) 352 } 353 354 func (c *Contract) CallMulti(ctx context.Context, args []CallArguments, opts *rpc.CallOptions) (*rpc.Receipt, error) { 355 if opts == nil { 356 opts = &rpc.DefaultOptions 357 } 358 359 // assemble batch transaction 360 op := codec.NewOp().WithTTL(opts.TTL) 361 for _, arg := range args { 362 if arg == nil { 363 continue 364 } 365 arg.WithDestination(c.addr) 366 op.WithContents(arg.Encode()) 367 } 368 369 // prepare, sign and broadcast 370 return c.rpc.Send(ctx, op, opts) 371 } 372 373 func (c *Contract) Deploy(ctx context.Context, opts *rpc.CallOptions) (*rpc.Receipt, error) { 374 return c.DeployExt(ctx, mavryk.Address{}, 0, opts) 375 } 376 377 func (c *Contract) DeployExt(ctx context.Context, delegate mavryk.Address, balance mavryk.N, opts *rpc.CallOptions) (*rpc.Receipt, error) { 378 if opts == nil { 379 opts = &rpc.DefaultOptions 380 } 381 382 // assemble origination op 383 orig := &codec.Origination{ 384 Script: *c.script, 385 } 386 if delegate.IsValid() { 387 orig.Delegate = delegate 388 } 389 if !balance.IsZero() { 390 orig.Balance = balance 391 } 392 op := codec.NewOp().WithTTL(opts.TTL).WithContents(orig) 393 394 // prepare, sign and broadcast 395 rcpt, err := c.rpc.Send(ctx, op, opts) 396 if err != nil { 397 return nil, err 398 } 399 400 // set contract address from deployment result if successful 401 if !rcpt.IsSuccess() { 402 return nil, rcpt.Error() 403 } 404 c.addr, _ = rcpt.OriginatedContract() 405 return rcpt, nil 406 }