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 }