github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/js/src/contracts/call.ts (about)

     1  import { AbiCoder, FunctionFragment, Interface, Result as DecodedResult } from '@ethersproject/abi';
     2  import * as grpc from '@grpc/grpc-js';
     3  import { Metadata } from '@grpc/grpc-js';
     4  import { callErrorFromStatus } from '@grpc/grpc-js/build/src/call';
     5  import { Keccak } from 'sha3';
     6  import { SolidityFunction } from 'solc';
     7  import { Result } from '../../proto/exec_pb';
     8  import { CallTx, ContractMeta, TxInput } from '../../proto/payload_pb';
     9  import { Envelope } from '../../proto/txs_pb';
    10  import { Pipe } from '../client';
    11  import { postDecodeResult, preEncodeResult, toBuffer } from '../convert';
    12  import { ABI, Address } from './abi';
    13  import { CallOptions } from './contract';
    14  
    15  export const DEFAULT_GAS = 1111111111;
    16  
    17  const WasmMagic = Buffer.from('\0asm');
    18  
    19  const coder = new AbiCoder();
    20  
    21  export function makeCallTx(
    22    data: Uint8Array,
    23    inputAddress: Address,
    24    contractAddress?: Address,
    25    contractMeta: ContractMeta[] = [],
    26  ): CallTx {
    27    const input = new TxInput();
    28    input.setAddress(Buffer.from(inputAddress, 'hex'));
    29    input.setAmount(0);
    30  
    31    const payload = new CallTx();
    32    payload.setInput(input);
    33    if (contractAddress) {
    34      payload.setAddress(Buffer.from(contractAddress, 'hex'));
    35    }
    36    payload.setGaslimit(DEFAULT_GAS);
    37    payload.setFee(0);
    38    // if we are deploying and it looks like wasm, it must be wasm. Note that
    39    // evm opcode 0 is stop, so this would not make any sense.
    40    if (!contractAddress && !Buffer.compare(data.slice(0, 4), WasmMagic)) {
    41      payload.setWasm(data);
    42    } else {
    43      payload.setData(data);
    44    }
    45    payload.setContractmetaList(contractMeta);
    46  
    47    return payload;
    48  }
    49  
    50  export function getContractMetaFromBytecode(abi: ABI, deployedBytecode: string): ContractMeta {
    51    const hasher = new Keccak(256);
    52    const codeHash = hasher.update(deployedBytecode, 'hex').digest();
    53    return getContractMeta(abi, codeHash);
    54  }
    55  
    56  export function getContractMeta(abi: ABI | string, codeHash: Uint8Array): ContractMeta {
    57    const meta = new ContractMeta();
    58    if (typeof abi !== 'string') {
    59      abi = JSON.stringify({ Abi: abi });
    60    }
    61    meta.setMeta(abi);
    62    meta.setCodehash(codeHash);
    63    return meta;
    64  }
    65  
    66  export type TransactionResult = {
    67    contractAddress: string;
    68    height: number;
    69    index: number;
    70    hash: string;
    71    type: number;
    72    result: Result.AsObject;
    73    resultBytes: Uint8Array;
    74    tx: Envelope.AsObject;
    75    caller: string | string[];
    76  };
    77  
    78  // export function encodeFunction(abi: SolidityFunction, ...args: unknown[]): string {
    79  //   const abiInputs = abi.inputs.map((arg) => arg.type);
    80  //   return functionSignature(abi) + coder.encode(abiInputs, convert.burrowToAbi(abiInputs, args));
    81  // }
    82  
    83  export function encodeConstructor(abi: SolidityFunction | void, bytecode: string, ...args: unknown[]): string {
    84    const abiInputs = abi ? abi.inputs.map((arg) => arg.type) : [];
    85    return bytecode + coder.encode(abiInputs, args);
    86  }
    87  
    88  export function decode(abi: SolidityFunction, output: Uint8Array): DecodedResult {
    89    if (!abi.outputs) {
    90      return [];
    91    }
    92    return coder.decode(abi.outputs as any, Buffer.from(output));
    93  }
    94  
    95  export function call(pipe: Pipe, payload: CallTx): Promise<TransactionResult> {
    96    return new Promise((resolve, reject) => {
    97      pipe(payload, (err, txe) => {
    98        if (err) {
    99          return reject(err);
   100        }
   101  
   102        if (!txe) {
   103          return reject(new Error(`call received no result after passing tx ${JSON.stringify(payload.toObject())}`));
   104        }
   105  
   106        // Handle execution reversions
   107        const result = txe.getResult();
   108        const header = txe.getHeader();
   109        if (!result) {
   110          return reject(new Error(`tx ${header?.getTxhash_asU8().toString()} has no result`));
   111        }
   112        if (txe.hasException()) {
   113          // Decode error message if there is one otherwise default
   114          if (result.getReturn().length === 0) {
   115            return reject(
   116              callErrorFromStatus({
   117                code: grpc.status.ABORTED,
   118                metadata: new Metadata(),
   119                details: 'Execution Reverted',
   120              }),
   121            );
   122          } else {
   123            // Strip first 4 bytes(function signature) the decode as a string
   124            return reject(
   125              callErrorFromStatus({
   126                code: grpc.status.ABORTED,
   127                metadata: new Metadata(),
   128                details: coder.decode(['string'], Buffer.from(result.getReturn_asU8().slice(4)))[0],
   129              }),
   130            );
   131          }
   132        }
   133  
   134        // Meta Data (address, caller, height, etc)
   135        const envelope = txe.getEnvelope();
   136        const receipt = txe.getReceipt();
   137        if (!header || !envelope || !receipt) {
   138          return reject(new Error(``));
   139        }
   140        let caller: string | string[] = envelope.getSignatoriesList().map((sig) => sig.getAddress_asB64().toUpperCase());
   141        if (caller.length === 1) {
   142          caller = caller[0];
   143        }
   144        return resolve({
   145          contractAddress: Buffer.from(receipt.getContractaddress_asU8()).toString('hex').toUpperCase(),
   146          height: header.getHeight(),
   147          index: header.getIndex(),
   148          hash: Buffer.from(header.getTxhash_asU8()).toString('hex').toUpperCase(),
   149          type: header.getTxtype(),
   150          result: result.toObject(),
   151          resultBytes: result.getReturn_asU8(),
   152          tx: envelope.toObject(),
   153          caller: caller,
   154        });
   155      });
   156    });
   157  }
   158  
   159  export type CallResult = {
   160    transactionResult: TransactionResult;
   161    result: DecodedResult;
   162  };
   163  
   164  export async function callFunction(
   165    iface: Interface,
   166    frag: FunctionFragment,
   167    { handler, middleware }: CallOptions,
   168    input: Address,
   169    pipe: Pipe,
   170    callee: Address,
   171    args: unknown[],
   172  ): Promise<unknown> {
   173    const data = toBuffer(iface.encodeFunctionData(frag, preEncodeResult(args, frag.inputs)));
   174    const transactionResult = await call(pipe, middleware(makeCallTx(data, input, callee)));
   175    // Unpack return arguments
   176    const result = postDecodeResult(iface.decodeFunctionResult(frag, transactionResult.resultBytes), frag.outputs);
   177    return handler({ transactionResult, result });
   178  }