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

     1  import { EventFragment, Fragment, FunctionFragment, Interface, LogDescription } from '@ethersproject/abi';
     2  import { CallTx, ContractMeta } from '../../proto/payload_pb';
     3  import { Client } from '../client';
     4  import { preEncodeResult, Result, toBuffer } from '../convert';
     5  import { EventStream } from '../events';
     6  import { Address } from './abi';
     7  import { call, callFunction, CallResult, getContractMetaFromBytecode, makeCallTx } from './call';
     8  import { CompiledContract } from './compile';
     9  import { EventCallback, listen } from './event';
    10  
    11  export const meta: unique symbol = Symbol('meta');
    12  
    13  export type InstanceMetadata = {
    14    address: string;
    15    contract: Contract;
    16  };
    17  
    18  export type ContractInstance = {
    19    [key in string]?: ContractFunction | ContractEvent;
    20  } & {
    21    // Using a unique symbol as a key here means we cannot clash with any dynamic member of the contract
    22    [meta]: InstanceMetadata;
    23  };
    24  
    25  export type CallOptions = typeof defaultCallOptions;
    26  
    27  export const defaultCallOptions = {
    28    middleware: (callTx: CallTx) => callTx,
    29    handler: (result: CallResult): unknown => result.result,
    30  } as const;
    31  
    32  // Get metadata of a contract instance including its deployed address and the Contract it instantiates
    33  export function getMetadata(instance: ContractInstance): InstanceMetadata {
    34    return instance[meta];
    35  }
    36  
    37  // Since so common
    38  export function getAddress(instance: ContractInstance): Address {
    39    return instance[meta].address;
    40  }
    41  
    42  export class Contract<T extends ContractInstance | any = any> {
    43    private readonly iface: Interface;
    44  
    45    constructor(public readonly code: CompiledContract, private readonly childCode: CompiledContract[] = []) {
    46      this.iface = new Interface(this.code.abi);
    47    }
    48  
    49    at(address: Address, burrow: Client, options: CallOptions = defaultCallOptions): T {
    50      const instance: ContractInstance = {
    51        [meta]: {
    52          address,
    53          contract: this,
    54        },
    55      };
    56  
    57      for (const frag of Object.values(this.iface.functions)) {
    58        attachFunction(this.iface, frag, instance, contractFunction(this.iface, frag, burrow, options, address));
    59      }
    60  
    61      for (const frag of Object.values(this.iface.events)) {
    62        attachFunction(this.iface, frag, instance, contractEvent(this.iface, frag, burrow, address));
    63      }
    64  
    65      return instance as T;
    66    }
    67  
    68    meta(): ContractMeta[] {
    69      return [this.code, ...this.childCode]
    70        .map(({ abi, deployedBytecode }) => abi && deployedBytecode && getContractMetaFromBytecode(abi, deployedBytecode))
    71        .filter((m): m is ContractMeta => Boolean(m));
    72    }
    73  
    74    async deploy(burrow: Client, ...args: unknown[]): Promise<T> {
    75      return this.deployWith(burrow, defaultCallOptions, ...args);
    76    }
    77  
    78    async deployWith(burrow: Client, options?: Partial<CallOptions>, ...args: unknown[]): Promise<T> {
    79      const opts = { ...defaultCallOptions, ...options };
    80      if (!this.code.bytecode) {
    81        throw new Error(`cannot deploy contract without compiled bytecode`);
    82      }
    83      const { middleware } = opts;
    84      const data = Buffer.concat(
    85        [this.code.bytecode, this.iface.encodeDeploy(preEncodeResult(args, this.iface.deploy.inputs))].map(toBuffer),
    86      );
    87      const tx = middleware(makeCallTx(data, burrow.account, undefined, this.meta()));
    88      const { contractAddress } = await call(burrow.callPipe, tx);
    89      return this.at(contractAddress, burrow, opts);
    90    }
    91  }
    92  
    93  // TODO[Silas]: integrate burrow.js with static code/type generation
    94  type GenericFunction<I extends unknown[] = any[], O = any> = (...args: I) => Promise<O>;
    95  
    96  // Bare call will execute against contract
    97  type ContractFunction = GenericFunction & {
    98    sim: GenericFunction;
    99    at: (address: string) => GenericFunction;
   100    atSim: (address: string) => GenericFunction;
   101    encode: (...args: unknown[]) => string;
   102    decode: (output: Uint8Array) => Result;
   103  };
   104  
   105  export type ContractEvent = ((cb: EventCallback) => EventStream) & {
   106    at: (address: string, cb: EventCallback) => EventStream;
   107    once: () => Promise<LogDescription>;
   108  };
   109  
   110  function contractFunction(
   111    iface: Interface,
   112    frag: FunctionFragment,
   113    burrow: Client,
   114    options: CallOptions,
   115    contractAddress: string,
   116  ): ContractFunction {
   117    const func = (...args: unknown[]) =>
   118      callFunction(iface, frag, options, burrow.account, burrow.callPipe, contractAddress, args);
   119    func.sim = (...args: unknown[]) =>
   120      callFunction(iface, frag, options, burrow.account, burrow.simPipe, contractAddress, args);
   121  
   122    func.at = (address: string) => (...args: unknown[]) =>
   123      callFunction(iface, frag, options, burrow.account, burrow.callPipe, address, args);
   124    func.atSim = (address: string) => (...args: unknown[]) =>
   125      callFunction(iface, frag, options, burrow.account, burrow.simPipe, address, args);
   126  
   127    func.encode = (...args: unknown[]) => iface.encodeFunctionData(frag, args);
   128    func.decode = (output: Uint8Array) => iface.decodeFunctionResult(frag, output);
   129    return func;
   130  }
   131  
   132  function contractEvent(iface: Interface, frag: EventFragment, burrow: Client, contractAddress: string): ContractEvent {
   133    const func = (cb: EventCallback) => listen(iface, frag, contractAddress, burrow, cb);
   134    func.at = (address: string, cb: EventCallback) => listen(iface, frag, address, burrow, cb);
   135    func.once = () =>
   136      new Promise<LogDescription>((resolve, reject) =>
   137        listen(iface, frag, contractAddress, burrow, (err, result) =>
   138          err ? reject(err) : result ? resolve(result) : reject(new Error(`no EventResult received from callback`)),
   139        ),
   140      );
   141    return func;
   142  }
   143  
   144  function attachFunction(
   145    iface: Interface,
   146    frag: Fragment,
   147    instance: ContractInstance,
   148    func: ContractFunction | ContractEvent,
   149  ): void {
   150    if (!instance[frag.name]) {
   151      instance[frag.name] = func;
   152    }
   153    instance[frag.format()] = func;
   154  }