github.com/mavryk-network/mvgo@v1.19.9/examples/rpc/main.go (about)

     1  // Copyright (c) 2020-2021 Blockwatch Data Inc.
     2  // Author: alex@blockwatch.cc
     3  
     4  // RPC examples
     5  package main
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"flag"
    11  	"fmt"
    12  	"os"
    13  	"os/signal"
    14  	"strconv"
    15  	"strings"
    16  	"syscall"
    17  
    18  	"github.com/mavryk-network/mvgo/mavryk"
    19  	"github.com/mavryk-network/mvgo/micheline"
    20  	"github.com/mavryk-network/mvgo/rpc"
    21  
    22  	"github.com/echa/log"
    23  )
    24  
    25  var (
    26  	flags   = flag.NewFlagSet("rpc", flag.ContinueOnError)
    27  	verbose bool
    28  	debug   bool
    29  	node    string
    30  )
    31  
    32  func init() {
    33  	flags.Usage = func() {}
    34  	flags.BoolVar(&verbose, "v", false, "Be verbose")
    35  	flags.BoolVar(&debug, "d", false, "Enable debug mode")
    36  	flags.StringVar(&node, "node", "https://rpc.tzpro.io", "Tezos node url")
    37  }
    38  
    39  func main() {
    40  	if err := flags.Parse(os.Args[1:]); err != nil {
    41  		if err == flag.ErrHelp {
    42  			fmt.Println("Usage: rpc [args] <cmd> [sub-args]")
    43  			fmt.Printf("\nCommands\n")
    44  			fmt.Printf("  block <hash>|head         show block info\n")
    45  			fmt.Printf("  op <block>/<list>/<pos>   show operation info\n")
    46  			fmt.Printf("  cost <block>/<list>/<pos> show operation cost\n")
    47  			fmt.Printf("  contract <hash>           show contract info\n")
    48  			fmt.Printf("  search <ops> <lvl>        output blocks containing operations in list\n")
    49  			fmt.Printf("  bootstrap                 wait until node is bootstrapped\n")
    50  			fmt.Printf("  monitor                   wait and show new heads as they are baked\n")
    51  			fmt.Printf("\nGlobal arguments\n")
    52  			flags.PrintDefaults()
    53  			os.Exit(0)
    54  		}
    55  		log.Fatal("Error:", err)
    56  	}
    57  
    58  	if err := run(); err != nil {
    59  		log.Fatal("Error:", err)
    60  	}
    61  }
    62  
    63  func run() error {
    64  	if flags.NArg() < 1 {
    65  		return fmt.Errorf("Command required")
    66  	}
    67  
    68  	switch {
    69  	case debug:
    70  		log.SetLevel(log.LevelDebug)
    71  	case verbose:
    72  		log.SetLevel(log.LevelInfo)
    73  	default:
    74  		log.SetLevel(log.LevelWarn)
    75  	}
    76  	rpc.UseLogger(log.Log)
    77  
    78  	ctx, cancel := context.WithCancel(context.Background())
    79  	defer cancel()
    80  
    81  	log.Infof("Using RPC %s", node)
    82  	c, err := rpc.NewClient(node, nil)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	c.Log = log.Log
    87  
    88  	switch flags.Arg(0) {
    89  	case "block":
    90  		h := flags.Arg(1)
    91  		if h == "" {
    92  			return fmt.Errorf("Missing block identifier")
    93  		}
    94  		if h == "head" {
    95  			return fetchHead(ctx, c)
    96  		} else {
    97  			// try parsing arg as height (i.e. integer)
    98  			if height, err := strconv.ParseInt(h, 10, 64); err == nil {
    99  				return fetchBlockHeight(ctx, c, height)
   100  			}
   101  			// otherwise, parse as block hash
   102  			h, err := mavryk.ParseBlockHash(flags.Arg(1))
   103  			if err != nil {
   104  				return err
   105  			}
   106  			return fetchBlock(ctx, c, h)
   107  		}
   108  	case "op":
   109  		h := flags.Arg(1)
   110  		if h == "" {
   111  			return fmt.Errorf("Missing operation identifier")
   112  		}
   113  		parts := strings.SplitN(h, "/", 3)
   114  		if len(parts) != 3 {
   115  			return fmt.Errorf("Invalid operation identifier (form: block-hash:list:pos)")
   116  		}
   117  		bid := rpc.BlockAlias(parts[0])
   118  		list, err := strconv.Atoi(parts[1])
   119  		if err != nil {
   120  			return fmt.Errorf("Invalid list identifier: %v", err)
   121  		}
   122  		pos, err := strconv.Atoi(parts[2])
   123  		if err != nil {
   124  			return fmt.Errorf("Invalid position identifier: %v", err)
   125  		}
   126  		return showOpInfo(ctx, c, bid, list, pos)
   127  
   128  	case "cost":
   129  		h := flags.Arg(1)
   130  		if h == "" {
   131  			return fmt.Errorf("Missing operation identifier")
   132  		}
   133  		parts := strings.SplitN(h, "/", 3)
   134  		if len(parts) != 3 {
   135  			return fmt.Errorf("Invalid operation identifier (form: block-hash:list:pos)")
   136  		}
   137  		bid := rpc.BlockAlias(parts[0])
   138  		list, err := strconv.Atoi(parts[1])
   139  		if err != nil {
   140  			return fmt.Errorf("Invalid list identifier: %v", err)
   141  		}
   142  		pos, err := strconv.Atoi(parts[2])
   143  		if err != nil {
   144  			return fmt.Errorf("Invalid position identifier: %v", err)
   145  		}
   146  		return showOpCost(ctx, c, bid, list, pos)
   147  
   148  	case "bootstrap":
   149  		return waitBootstrap(ctx, c)
   150  	case "monitor":
   151  		return monitorBlocks(ctx, c)
   152  	case "contract":
   153  		a := flags.Arg(1)
   154  		if a == "" {
   155  			return fmt.Errorf("Missing contract address")
   156  		}
   157  		addr, err := mavryk.ParseAddress(a)
   158  		if err != nil {
   159  			return err
   160  		}
   161  		if addr.Type() != mavryk.AddressTypeContract {
   162  			return fmt.Errorf("%s is not a smart contract address", a)
   163  		}
   164  		return showContractInfo(ctx, c, addr)
   165  	case "search":
   166  		height, _ := strconv.ParseInt(flags.Arg(2), 10, 64)
   167  		return searchOps(ctx, c, flags.Arg(1), height)
   168  	default:
   169  		return fmt.Errorf("unknown command %s", flags.Arg(0))
   170  	}
   171  }
   172  
   173  func fetchBlockHeight(ctx context.Context, c *rpc.Client, height int64) error {
   174  	b, err := c.GetBlockHeight(ctx, height)
   175  	if err != nil {
   176  		return err
   177  	}
   178  	printBlock(b)
   179  	return nil
   180  }
   181  
   182  func fetchBlock(ctx context.Context, c *rpc.Client, blockID mavryk.BlockHash) error {
   183  	b, err := c.GetBlock(ctx, blockID)
   184  	if err != nil {
   185  		return err
   186  	}
   187  	printBlock(b)
   188  	return nil
   189  }
   190  
   191  func fetchHead(ctx context.Context, c *rpc.Client) error {
   192  	head, err := c.GetTipHeader(ctx)
   193  	if err != nil {
   194  		return err
   195  	}
   196  	printHead(head)
   197  	return nil
   198  }
   199  
   200  func printHead(h *rpc.BlockHeader) {
   201  	fmt.Printf("Block  %d %s %s\n", h.Level, h.Hash, h.Timestamp)
   202  }
   203  
   204  func printBlock(b *rpc.Block) {
   205  	fmt.Printf("Height   %d (%d)\n", b.GetLevel(), b.GetCycle())
   206  	fmt.Printf("Block    %s\n", b.Hash)
   207  	if b.Header.PayloadHash.IsValid() {
   208  		fmt.Printf("Round    %d\n", b.Header.PayloadRound)
   209  		fmt.Printf("Payload  %s\n", b.Header.PayloadHash)
   210  	} else {
   211  		fmt.Printf("Priority %d\n", b.Header.Priority)
   212  		fmt.Printf("Payload  %s\n", b.Header.OperationsHash)
   213  	}
   214  	fmt.Printf("Parent   %s\n", b.Header.Predecessor)
   215  	fmt.Printf("Time     %s\n", b.Header.Timestamp)
   216  
   217  	// count operations and details
   218  	ops := make(map[mavryk.OpType]int)
   219  	var count int
   220  	for _, v := range b.Operations {
   221  		for _, vv := range v {
   222  			for _, op := range vv.Contents {
   223  				kind := op.Kind()
   224  				count++
   225  				if c, ok := ops[kind]; ok {
   226  					ops[kind] = c + 1
   227  				} else {
   228  					ops[kind] = 1
   229  				}
   230  			}
   231  		}
   232  	}
   233  	fmt.Printf("Ops      %d: ", count)
   234  	comma := ""
   235  	for n, c := range ops {
   236  		fmt.Printf("%s%d %s", comma, c, n)
   237  		comma = ", "
   238  	}
   239  	fmt.Println()
   240  	fmt.Println()
   241  }
   242  
   243  func waitBootstrap(ctx context.Context, c *rpc.Client) error {
   244  	mon := rpc.NewBootstrapMonitor()
   245  	defer mon.Close()
   246  	if err := c.MonitorBootstrapped(ctx, mon); err != nil {
   247  		return err
   248  	}
   249  	ctx2, cancel := context.WithCancel(ctx)
   250  	stop := make(chan os.Signal, 1)
   251  	signal.Notify(stop,
   252  		syscall.SIGHUP,
   253  		syscall.SIGINT,
   254  		syscall.SIGTERM,
   255  		syscall.SIGQUIT,
   256  	)
   257  	go func() {
   258  		select {
   259  		case <-stop:
   260  			fmt.Printf("Stopping monitor\n")
   261  			cancel()
   262  		case <-ctx.Done():
   263  		}
   264  	}()
   265  
   266  	fmt.Printf("Waiting for chain to bootstrap... (cancel with Ctrl-C)\n\n")
   267  
   268  	for {
   269  		b, err := mon.Recv(ctx2)
   270  		if err != nil {
   271  			return err
   272  		}
   273  		if err := fetchBlock(ctx, c, b.Block); err != nil {
   274  			return err
   275  		}
   276  	}
   277  }
   278  
   279  func monitorBlocks(ctx context.Context, c *rpc.Client) error {
   280  	mon := rpc.NewBlockHeaderMonitor()
   281  	defer mon.Close()
   282  	if err := c.MonitorBlockHeader(ctx, mon); err != nil {
   283  		return err
   284  	}
   285  
   286  	ctx2, cancel := context.WithCancel(ctx)
   287  	stop := make(chan os.Signal, 1)
   288  	signal.Notify(stop,
   289  		syscall.SIGHUP,
   290  		syscall.SIGINT,
   291  		syscall.SIGTERM,
   292  		syscall.SIGQUIT,
   293  	)
   294  	go func() {
   295  		select {
   296  		case <-stop:
   297  			fmt.Printf("Stopping monitor\n")
   298  			cancel()
   299  		case <-ctx.Done():
   300  		}
   301  	}()
   302  
   303  	fmt.Printf("Waiting for new blocks... (cancel with Ctrl-C)\n\n")
   304  	for {
   305  		h, err := mon.Recv(ctx2)
   306  		if err != nil {
   307  			return err
   308  		}
   309  		if err := fetchBlock(ctx, c, h.Hash); err != nil {
   310  			return err
   311  		}
   312  	}
   313  }
   314  
   315  func searchOps(ctx context.Context, c *rpc.Client, ops string, start int64) error {
   316  	if ops == "deactivated" {
   317  		return searchDeactivations(ctx, c, start)
   318  	}
   319  
   320  	// find the current blockchain tip
   321  	tips, err := c.GetTips(ctx, 1, mavryk.BlockHash{})
   322  	if err != nil {
   323  		return err
   324  	}
   325  	if len(tips) == 0 || len(tips[0]) == 0 {
   326  		return fmt.Errorf("invalid chain tip")
   327  	}
   328  	tip, err := c.GetBlock(ctx, tips[0][0])
   329  	if err != nil {
   330  		return fmt.Errorf("Block %s failed: %w", tips[0][0], err)
   331  	}
   332  
   333  	// parse ops
   334  	oplist := make([]mavryk.OpType, 0)
   335  	eplist := make([]string, 0)
   336  	for _, op := range strings.Split(ops, ",") {
   337  		if strings.HasPrefix(op, "ep:") {
   338  			eplist = append(eplist, strings.TrimPrefix(op, "ep:"))
   339  		} else {
   340  			ot := mavryk.ParseOpType(op)
   341  			if !ot.IsValid() {
   342  				return fmt.Errorf("invalid operation type '%s'", op)
   343  			}
   344  			oplist = append(oplist, ot)
   345  		}
   346  	}
   347  	if len(eplist) > 0 {
   348  		oplist = append(oplist, mavryk.OpTypeTransaction)
   349  	}
   350  
   351  	// fetching blocks forward
   352  	height := start
   353  	enc := json.NewEncoder(os.Stdout)
   354  	enc.SetIndent("", "  ")
   355  	opcount := make(map[mavryk.OpType]int)
   356  	epcount := make(map[string]int)
   357  	plog := log.NewProgressLogger(log.Log).SetEvent("block")
   358  	for {
   359  		b, err := c.GetBlockHeight(ctx, height)
   360  		if err != nil {
   361  			return fmt.Errorf("Block %d failed: %w", height, err)
   362  		}
   363  
   364  		plog.Log(1, fmt.Sprintf("height %d", height))
   365  
   366  		// clear maps
   367  		for n := range opcount {
   368  			delete(opcount, n)
   369  		}
   370  		for n := range epcount {
   371  			delete(epcount, n)
   372  		}
   373  
   374  		// count operations and details
   375  		for _, v := range b.Operations {
   376  			for _, vv := range v {
   377  				for _, op := range vv.Contents {
   378  					kind := op.Kind()
   379  					if c, ok := opcount[kind]; ok {
   380  						opcount[kind] = c + 1
   381  					} else {
   382  						opcount[kind] = 1
   383  					}
   384  					if kind == mavryk.OpTypeTransaction {
   385  						tx := op.(*rpc.Transaction)
   386  						if tx.Parameters != nil {
   387  							epcount[tx.Parameters.Entrypoint]++
   388  						}
   389  						for _, vvv := range tx.Metadata.InternalResults {
   390  							kind = vvv.Kind
   391  							opcount[kind]++
   392  						}
   393  					}
   394  				}
   395  			}
   396  		}
   397  		for _, op := range oplist {
   398  			if n, ok := opcount[op]; ok {
   399  				if len(eplist) > 0 {
   400  					for _, ep := range eplist {
   401  						if n, ok := epcount[ep]; ok {
   402  							fmt.Printf("%s level=%d contains %d %s(s)\n", b.Hash, b.GetLevel(), n, ep)
   403  						}
   404  						// output relevant ops
   405  						if !verbose {
   406  							continue
   407  						}
   408  						for _, v := range b.Operations {
   409  							for _, vv := range v {
   410  								for _, o := range vv.Contents {
   411  									if op == o.Kind() && o.(*rpc.Transaction).Parameters != nil && o.(*rpc.Transaction).Parameters.Entrypoint == ep {
   412  										enc.Encode(o)
   413  									}
   414  								}
   415  							}
   416  						}
   417  					}
   418  				} else {
   419  					fmt.Printf("%s level=%d contains %d %s(s)\n", b.Hash, b.GetLevel(), n, op)
   420  					// output relevant ops
   421  					if !verbose {
   422  						continue
   423  					}
   424  					for _, v := range b.Operations {
   425  						for _, vv := range v {
   426  							for _, o := range vv.Contents {
   427  								if op == o.Kind() {
   428  									enc.Encode(o)
   429  								}
   430  							}
   431  						}
   432  					}
   433  				}
   434  			}
   435  		}
   436  		height++
   437  
   438  		// the tip has probably advanced a lot since first fetch above,
   439  		// but this is only for illustration
   440  		if height > tip.GetLevel() {
   441  			break
   442  		}
   443  	}
   444  	return nil
   445  }
   446  
   447  func searchDeactivations(ctx context.Context, c *rpc.Client, start int64) error {
   448  	// find the current blockchain tip
   449  	tips, err := c.GetTips(ctx, 1, mavryk.BlockHash{})
   450  	if err != nil {
   451  		return err
   452  	}
   453  	if len(tips) == 0 || len(tips[0]) == 0 {
   454  		return fmt.Errorf("invalid chain tip")
   455  	}
   456  	tip, err := c.GetBlock(ctx, tips[0][0])
   457  	if err != nil {
   458  		return err
   459  	}
   460  
   461  	// fetching blocks forward
   462  	height := start
   463  	enc := json.NewEncoder(os.Stdout)
   464  	enc.SetIndent("", "  ")
   465  	for {
   466  		b, err := c.GetBlockHeight(ctx, height)
   467  		if err != nil {
   468  			return err
   469  		}
   470  
   471  		if b.Metadata.Level.Level%1000 == 0 {
   472  			fmt.Printf("Scanning blockchain at level %d\n", b.Metadata.Level.Level)
   473  		}
   474  
   475  		if len(b.Metadata.Deactivated) > 0 {
   476  			res := map[int64][]mavryk.Address{
   477  				height: b.Metadata.Deactivated,
   478  			}
   479  			enc.Encode(res)
   480  		}
   481  
   482  		height++
   483  
   484  		// the tip has probably advanced a lot since first fetch above,
   485  		// but this is only for illustration
   486  		if height > tip.Metadata.Level.Level {
   487  			break
   488  		}
   489  	}
   490  	return nil
   491  }
   492  
   493  func showContractInfo(ctx context.Context, c *rpc.Client, addr mavryk.Address) error {
   494  	fmt.Printf("Loading data for contract %s. This may take a while. Abort with Ctrl-C.\n", addr)
   495  	script, err := c.GetContractScript(ctx, addr)
   496  	if err != nil {
   497  		return err
   498  	}
   499  
   500  	// unfold Micheline storage into human-readable form
   501  	val := micheline.NewValue(script.StorageType(), script.Storage)
   502  	m, err := val.Map()
   503  	if err != nil {
   504  		return err
   505  	}
   506  	buf, err := json.MarshalIndent(m, "  ", "  ")
   507  	if err != nil {
   508  		return err
   509  	}
   510  	fmt.Printf("Storage:\n  %v\n", string(buf))
   511  
   512  	// identify bigmaps owned by the contract from contract type and storage
   513  	bm := script.Bigmaps()
   514  	if len(bm) == 0 {
   515  		fmt.Printf("Bigmaps  (none)\n")
   516  	} else {
   517  		fmt.Printf("Bigmaps:\n")
   518  	}
   519  	for name, bigid := range bm {
   520  		// load bigmap type
   521  		biginfo, err := c.GetBigmapInfo(ctx, bigid, rpc.Head)
   522  		if err != nil {
   523  			return err
   524  		}
   525  		// list all bigmap keys
   526  		bigkeys, err := c.ListBigmapKeys(ctx, bigid, rpc.Head)
   527  		if err != nil {
   528  			return err
   529  		}
   530  		fmt.Printf("  %-15s  id=%d n_keys=%d bytes=%d\n", name, bigid, len(bigkeys), biginfo.TotalBytes)
   531  		if verbose {
   532  			for i, key := range bigkeys {
   533  				// visit each key
   534  				bigval, err := c.GetBigmapValue(ctx, bigid, key, rpc.Head)
   535  				if err != nil {
   536  					return err
   537  				}
   538  				// unfold Micheline type into human readable form
   539  				val := micheline.NewValue(micheline.NewType(biginfo.ValueType), bigval)
   540  				m, err := val.Map()
   541  				if err != nil {
   542  					return err
   543  				}
   544  				buf, err := json.MarshalIndent(m, "          ", "  ")
   545  				if err != nil {
   546  					return err
   547  				}
   548  				fmt.Printf("    %03d: %s\n", i, string(buf))
   549  			}
   550  		}
   551  	}
   552  	return nil
   553  }
   554  
   555  func showOpInfo(ctx context.Context, c *rpc.Client, bh rpc.BlockID, list, pos int) error {
   556  	fmt.Printf("Loading op %s/%d/%d\n", bh, list, pos)
   557  	op, err := c.GetBlockOperation(ctx, bh, list, pos)
   558  	if err != nil {
   559  		return err
   560  	}
   561  	fmt.Printf("Hash   %s\n", op.Hash)
   562  	fmt.Printf("Parts  %d\n", len(op.Contents))
   563  	for i, o := range op.Contents {
   564  		fmt.Printf("Part   %d\n", i+1)
   565  		fmt.Printf("  Type       %s\n", o.Kind())
   566  		switch o.Kind() {
   567  		case mavryk.OpTypeTransaction:
   568  			tx := o.(*rpc.Transaction)
   569  			fmt.Printf("  Dest       %s\n", tx.Destination)
   570  			fmt.Printf("  Fee        %d\n", tx.Fee)
   571  			fmt.Printf("  Counter    %d\n", tx.Counter)
   572  			fmt.Printf("  Amount     %d\n", tx.Amount)
   573  			fmt.Printf("  Gas        %d/%d\n", tx.Metadata.Result.ConsumedGas, tx.GasLimit)
   574  			fmt.Printf("  Storage    %d/%d\n", tx.Metadata.Result.PaidStorageSizeDiff, tx.StorageLimit)
   575  			fmt.Printf("  Internal   %d (not shown)\n", len(tx.Metadata.InternalResults))
   576  			var script *micheline.Script
   577  			if tx.Destination.IsContract() {
   578  				script, err = c.GetContractScript(ctx, tx.Destination)
   579  				if err != nil {
   580  					return err
   581  				}
   582  			}
   583  			if tx.Parameters != nil {
   584  				fmt.Printf("  Entrypoint %s\n", tx.Parameters.Entrypoint)
   585  				ep, _, err := tx.Parameters.MapEntrypoint(script.ParamType())
   586  				if err != nil {
   587  					return err
   588  				}
   589  				val := micheline.NewValue(ep.Type(), tx.Parameters.Value)
   590  				m, err := val.Map()
   591  				if err != nil {
   592  					return err
   593  				}
   594  				buf, err := json.MarshalIndent(m, "  ", "  ")
   595  				if err != nil {
   596  					return err
   597  				}
   598  				fmt.Println("  " + string(buf))
   599  			}
   600  			if prim := tx.Metadata.Result.Storage; prim != nil {
   601  				val := micheline.NewValue(script.StorageType(), *prim)
   602  				m, err := val.Map()
   603  				if err != nil {
   604  					return err
   605  				}
   606  				buf, err := json.MarshalIndent(m, "  ", "  ")
   607  				if err != nil {
   608  					return err
   609  				}
   610  				fmt.Printf("Storage:\n  %s\n", string(buf))
   611  			}
   612  		}
   613  	}
   614  
   615  	return nil
   616  }
   617  
   618  func showOpCost(ctx context.Context, c *rpc.Client, bh rpc.BlockID, list, pos int) error {
   619  	fmt.Printf("Loading op %s/%d/%d\n", bh, list, pos)
   620  	op, err := c.GetBlockOperation(ctx, bh, list, pos)
   621  	if err != nil {
   622  		return err
   623  	}
   624  	fmt.Printf("Hash   %s\n", op.Hash)
   625  	total := op.TotalCosts()
   626  	fmt.Printf("Totals\n")
   627  	fmt.Printf("  Fee             %d\n", total.Fee)
   628  	fmt.Printf("  Burn            %d\n", total.Burn)
   629  	fmt.Printf("  GasUsed         %d\n", total.GasUsed)
   630  	fmt.Printf("  StorageUsed     %d\n", total.StorageUsed)
   631  	fmt.Printf("  StorageBurn     %d\n", total.StorageBurn)
   632  	fmt.Printf("  AllocationBurn  %d\n", total.AllocationBurn)
   633  
   634  	for i, o := range op.Contents {
   635  		limits := o.Limits()
   636  		costs := o.Costs()
   637  		fmt.Printf("Op %d -----------------\n", i+1)
   638  		fmt.Printf("  Type            %s\n", o.Kind())
   639  		fmt.Printf("  Gas             %d/%d\n", costs.GasUsed, limits.GasLimit)
   640  		fmt.Printf("  Storage         %d/%d\n", costs.StorageUsed, limits.StorageLimit)
   641  		fmt.Printf("  StorageBurn     %d\n", costs.StorageBurn)
   642  		fmt.Printf("  AllocationBurn  %d\n", costs.AllocationBurn)
   643  
   644  		switch o.Kind() {
   645  		case mavryk.OpTypeTransaction:
   646  			tx := o.(*rpc.Transaction)
   647  			for j, ir := range tx.Metadata.InternalResults {
   648  				costs := ir.Costs()
   649  				fmt.Printf("Internal %d -----------------\n", j+1)
   650  				fmt.Printf("  Type            %s\n", ir.Kind)
   651  				fmt.Printf("  Gas             %d\n", costs.GasUsed)
   652  				fmt.Printf("  Storage         %d\n", costs.StorageUsed)
   653  				fmt.Printf("  StorageBurn     %d\n", costs.StorageBurn)
   654  				fmt.Printf("  AllocationBurn  %d\n", costs.AllocationBurn)
   655  			}
   656  		}
   657  	}
   658  
   659  	return nil
   660  }