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  }