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 }