github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/call/call.go (about)

     1  package call
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/abi"
     8  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
     9  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger"
    10  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/parser"
    11  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc"
    12  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc/query"
    13  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types"
    14  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/walk"
    15  )
    16  
    17  var ErrAbiNotFound = errors.New("abi not found ")
    18  
    19  type ContractCall struct {
    20  	Conn        *rpc.Connection
    21  	Address     base.Address
    22  	Method      *types.Function
    23  	Arguments   []any
    24  	BlockNumber base.Blknum
    25  	encoded     string
    26  }
    27  
    28  func NewContractCallWithAbi(conn *rpc.Connection, callAddress base.Address, theCall string, abiMap *abi.SelectorSyncMap) (*ContractCall, []string, error) {
    29  	parsed, err := parser.ParseCall(theCall)
    30  	if err != nil {
    31  		err = fmt.Errorf("the value provided --call (%s) is invalid", theCall)
    32  		return nil, []string{}, err
    33  	}
    34  
    35  	var function *types.Function
    36  	var callArguments []*parser.ContractArgument
    37  	suggestions := make([]string, 0)
    38  	if parsed.Encoded != "" {
    39  		selector := parsed.Encoded[:10]
    40  		function, _, err = FindAbiFunction(FindBySelector, selector, nil, abiMap)
    41  		if err != nil {
    42  			return nil, []string{}, err
    43  		}
    44  
    45  	} else {
    46  		// Selector or function name call
    47  		findAbiMode := FindByName // default
    48  		var identifier string
    49  
    50  		switch {
    51  		case parsed.FunctionNameCall != nil:
    52  			findAbiMode = FindByName
    53  			identifier = parsed.FunctionNameCall.Name
    54  			callArguments = parsed.FunctionNameCall.Arguments
    55  		case parsed.SelectorCall != nil:
    56  			findAbiMode = FindBySelector
    57  			identifier = parsed.SelectorCall.Selector.Value
    58  			callArguments = parsed.SelectorCall.Arguments
    59  		}
    60  
    61  		function, suggestions, err = FindAbiFunction(findAbiMode, identifier, callArguments, abiMap)
    62  		if err != nil {
    63  			return nil, suggestions, err
    64  		}
    65  	}
    66  
    67  	if function == nil {
    68  		return nil, suggestions, fmt.Errorf("abi not found for %s: %s", theCall, ErrAbiNotFound)
    69  	}
    70  
    71  	var args []any
    72  	if parsed.Encoded == "" {
    73  		args, err = convertArguments(callArguments, function)
    74  		if err != nil {
    75  			return nil, suggestions, err
    76  		}
    77  	}
    78  
    79  	contactCall := &ContractCall{
    80  		Conn:      conn,
    81  		Address:   callAddress,
    82  		Method:    function,
    83  		Arguments: args,
    84  	}
    85  	if parsed.Encoded != "" {
    86  		contactCall.forceEncoding(parsed.Encoded)
    87  	}
    88  
    89  	return contactCall, suggestions, nil
    90  }
    91  
    92  func NewContractCall(conn *rpc.Connection, callAddress base.Address, theCall string) (*ContractCall, []string, error) {
    93  	abiMap := &abi.SelectorSyncMap{}
    94  	if err := abi.LoadAbi(conn, callAddress, abiMap); err != nil {
    95  		return nil, []string{}, err
    96  	}
    97  
    98  	// TODO: Why does this not work?
    99  	// return NewContractCallWithAbis(conn, callAddress, theCall, abiMap)
   100  
   101  	parsed, err := parser.ParseCall(theCall)
   102  	if err != nil {
   103  		err = fmt.Errorf("the value provided --call (%s) is invalid", theCall)
   104  		return nil, []string{}, err
   105  	}
   106  
   107  	var function *types.Function
   108  	var callArguments []*parser.ContractArgument
   109  	suggestions := make([]string, 0)
   110  	if parsed.Encoded != "" {
   111  		selector := parsed.Encoded[:10]
   112  		function, _, err = FindAbiFunction(FindBySelector, selector, nil, abiMap)
   113  		if err != nil {
   114  			return nil, []string{}, err
   115  		}
   116  
   117  	} else {
   118  		// Selector or function name call
   119  		findAbiMode := FindByName
   120  		var identifier string
   121  
   122  		switch {
   123  		case parsed.FunctionNameCall != nil:
   124  			findAbiMode = FindByName
   125  			identifier = parsed.FunctionNameCall.Name
   126  			callArguments = parsed.FunctionNameCall.Arguments
   127  		case parsed.SelectorCall != nil:
   128  			findAbiMode = FindBySelector
   129  			identifier = parsed.SelectorCall.Selector.Value
   130  			callArguments = parsed.SelectorCall.Arguments
   131  		}
   132  
   133  		function, suggestions, err = FindAbiFunction(findAbiMode, identifier, callArguments, abiMap)
   134  		if err != nil {
   135  			return nil, suggestions, err
   136  		}
   137  	}
   138  
   139  	if function == nil {
   140  		return nil, suggestions, fmt.Errorf("abi not found for %s: %s", theCall, ErrAbiNotFound)
   141  	}
   142  
   143  	var args []any
   144  	if parsed.Encoded == "" {
   145  		args, err = convertArguments(callArguments, function)
   146  		if err != nil {
   147  			return nil, suggestions, err
   148  		}
   149  	}
   150  
   151  	contactCall := &ContractCall{
   152  		Conn:      conn,
   153  		Address:   callAddress,
   154  		Method:    function,
   155  		Arguments: args,
   156  	}
   157  	if parsed.Encoded != "" {
   158  		contactCall.forceEncoding(parsed.Encoded)
   159  	}
   160  
   161  	return contactCall, suggestions, nil
   162  }
   163  
   164  func convertArguments(callArguments []*parser.ContractArgument, function *types.Function) (args []any, err error) {
   165  	abiMethod, err := function.GetAbiMethod()
   166  	if err != nil {
   167  		return
   168  	}
   169  	if len(abiMethod.Inputs) != len(callArguments) {
   170  		return nil, fmt.Errorf("got %d argument(s), but wanted %d", len(abiMethod.Inputs), len(callArguments))
   171  	}
   172  
   173  	args = make([]any, 0, len(callArguments))
   174  	for index, arg := range callArguments {
   175  		converted, err := arg.AbiType(&abiMethod.Inputs[index].Type)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  		args = append(args, converted)
   180  	}
   181  
   182  	return
   183  }
   184  
   185  func (call *ContractCall) forceEncoding(encoding string) {
   186  	call.encoded = encoding
   187  }
   188  
   189  func (call *ContractCall) Call(artFunc func(string, *types.Function) error) (results *types.Result, err error) {
   190  	blockTs := base.Timestamp(0)
   191  	if call.Conn.StoreReadable() {
   192  		// walk.Cache_Results
   193  		results = &types.Result{
   194  			BlockNumber: call.BlockNumber,
   195  			Address:     call.Address,
   196  			Encoding:    call.Method.Encoding,
   197  		}
   198  		if err := call.Conn.Store.Read(results, nil); err == nil {
   199  			return results, nil
   200  		}
   201  		blockTs = call.Conn.GetBlockTimestamp(call.BlockNumber)
   202  	}
   203  
   204  	if artFunc == nil {
   205  		logger.Fatal("should not happen ==> implementation error: artFunc is nil")
   206  	}
   207  
   208  	var packed []byte
   209  	blockNumberHex := fmt.Sprintf("0x%x", call.BlockNumber)
   210  	if call.encoded != "" {
   211  		packed = base.Hex2Bytes(call.encoded[2:])
   212  	} else {
   213  		packed, err = call.Method.Pack(call.Arguments)
   214  		if err != nil {
   215  			return nil, err
   216  		}
   217  	}
   218  
   219  	packedHex := "0x" + base.Bytes2Hex(packed)
   220  	encodedArguments := ""
   221  	if len(packedHex) > 10 {
   222  		encodedArguments = packedHex[10:]
   223  	}
   224  
   225  	method := "eth_call"
   226  	params := query.Params{
   227  		map[string]any{
   228  			"to":   call.Address.Hex(),
   229  			"data": packedHex,
   230  		},
   231  		blockNumberHex,
   232  	}
   233  
   234  	theBytes, err := query.Query[string](call.Conn.Chain, method, params)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	function := call.Method.Clone()
   240  	// articulate it if possible
   241  	if theBytes != nil {
   242  		str := *theBytes
   243  		if err = artFunc(str, function); err != nil {
   244  			return nil, err
   245  		}
   246  	}
   247  
   248  	results = &types.Result{
   249  		BlockNumber:      call.BlockNumber,
   250  		Timestamp:        blockTs,
   251  		Address:          call.Address,
   252  		Name:             call.Method.Name,
   253  		Encoding:         call.Method.Encoding,
   254  		Signature:        call.Method.Signature,
   255  		EncodedArguments: encodedArguments,
   256  		ReturnedBytes:    *theBytes,
   257  		ArticulatedOut:   function,
   258  	}
   259  	results.Values = make(map[string]string)
   260  	for index, output := range function.Outputs {
   261  		results.Values[output.DisplayName(index)] = fmt.Sprint(output.Value)
   262  	}
   263  
   264  	conn := call.Conn
   265  	isFinal := base.IsFinal(conn.LatestBlockTimestamp, blockTs)
   266  	if isFinal && conn.StoreWritable() && conn.EnabledMap[walk.Cache_Results] {
   267  		_ = conn.Store.Write(results, nil)
   268  	}
   269  
   270  	return results, nil
   271  }