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 }