iuretempore
Version:
aelf-sdk js library
251 lines (221 loc) • 7.62 kB
JavaScript
/**
* @file contract method
* @author atom-yang
*/
import {
getTransaction,
Transaction
} from '../util/proto';
import {
transformArrayToMap,
transformMapToArray,
transform,
INPUT_TRANSFORMERS,
OUTPUT_TRANSFORMERS
} from '../util/transform';
import {
isBoolean,
isFunction,
noop,
uint8ArrayToHex
} from '../util/utils';
import wallet from '../wallet';
const unpackSpecifiedTypeData = ({
data,
dataType
}) => {
const buffer = Buffer.from(data, 'hex');
const decoded = dataType.decode(buffer);
const result = dataType.toObject(decoded, {
enums: String, // enums as string names
longs: String, // longs as strings (requires long.js)
bytes: String, // bytes as base64 encoded strings
defaults: true, // includes default values
arrays: true, // populates empty arrays (repeated fields) even if defaults=false
objects: true, // populates empty objects (map fields) even if defaults=false
oneofs: true // includes virtual oneof fields set to the present field's name
});
return result;
};
export default class ContractMethod {
constructor(chain, method, contractAddress, walletInstance) {
this._chain = chain;
this._method = method;
const { resolvedRequestType, resolvedResponseType } = method;
this._inputType = resolvedRequestType;
this._outputType = resolvedResponseType;
this._name = method.name;
this._contractAddress = contractAddress;
this._wallet = walletInstance;
this.sendTransaction = this.sendTransaction.bind(this);
this.unpackPackedInput = this.unpackPackedInput.bind(this);
this.packInput = this.packInput.bind(this);
this.unpackOutput = this.unpackOutput.bind(this);
this.bindMethodToContract = this.bindMethodToContract.bind(this);
this.run = this.run.bind(this);
this.request = this.request.bind(this);
this.callReadOnly = this.callReadOnly.bind(this);
this.getSignedTx = this.getSignedTx.bind(this);
this.getRawTx = this.getRawTx.bind(this);
}
packInput(input) {
if (!input) {
return null;
}
let params = transformMapToArray(this._inputType, input);
params = transform(this._inputType, params, INPUT_TRANSFORMERS);
const message = this._inputType.fromObject(params);
return this._inputType.encode(message).finish();
}
unpackPackedInput(inputPacked) {
if (!inputPacked) {
return null;
}
const result = unpackSpecifiedTypeData({
data: inputPacked,
dataType: this._inputType
});
let params = transform(this._inputType, result, OUTPUT_TRANSFORMERS);
params = transformArrayToMap(this._inputType, params);
return params;
}
unpackOutput(output) {
if (!output) {
return null;
}
let result = unpackSpecifiedTypeData({
data: output,
dataType: this._outputType
});
result = transform(this._outputType, result, OUTPUT_TRANSFORMERS);
result = transformArrayToMap(this._outputType, result);
return result;
}
handleTransaction(height, hash, encoded) {
const rawTx = this.getRawTx(height, hash, encoded);
let tx = wallet.signTransaction(rawTx, this._wallet.keyPair);
tx = Transaction.encode(tx).finish();
if (tx instanceof Buffer) {
return tx.toString('hex');
}
return uint8ArrayToHex(tx);
}
prepareParametersAsync(args) {
const filterArgs = args.filter(arg => !isFunction(arg) && !isBoolean(arg.sync));
const encoded = this.packInput(filterArgs[0]);
return this._chain.getChainStatus().then(status => {
const { BestChainHeight, BestChainHash } = status;
return this.handleTransaction(BestChainHeight, BestChainHash, encoded);
});
}
prepareParameters(args) {
const filterArgs = args.filter(arg => !isFunction(arg) && !isBoolean(arg.sync));
const encoded = this.packInput(filterArgs[0]);
const { BestChainHeight, BestChainHash } = this._chain.getChainStatus({
sync: true
});
return this.handleTransaction(BestChainHeight, BestChainHash, encoded);
}
prepareParametersWithBlockInfo(args) {
const filterArgs = args.filter(arg => !isFunction(arg) && !isBoolean(arg.sync));
const encoded = this.packInput(filterArgs[0]);
const { height, hash } = filterArgs[1]; // blockInfo
return this.handleTransaction(height, hash, encoded);
}
sendTransaction(...args) {
const argsObject = this.extractArgumentsIntoObject(args);
if (argsObject.isSync) {
const parameters = this.prepareParameters(args);
return this._chain.sendTransaction(parameters, {
sync: true
});
}
// eslint-disable-next-line arrow-body-style
return this.prepareParametersAsync(args).then(parameters => {
return this._chain.sendTransaction(parameters, argsObject.callback);
});
}
callReadOnly(...args) {
const argsObject = this.extractArgumentsIntoObject(args);
if (argsObject.isSync) {
const parameters = this.prepareParameters(args);
return this.unpackOutput(this._chain.callReadOnly(parameters, {
sync: true
}));
}
// eslint-disable-next-line arrow-body-style
return this.prepareParametersAsync(args).then(parameters => {
return this._chain.callReadOnly(parameters, (error, result) => {
argsObject.callback(error, this.unpackOutput(result));
}).then(this.unpackOutput);
});
}
extractArgumentsIntoObject(args) {
const result = {
callback: noop,
isSync: false
};
if (args.length === 0) {
// has no callback, default to be async mode
return result;
}
if (isFunction(args[args.length - 1])) {
result.callback = args[args.length - 1];
}
args.forEach(arg => {
if (isBoolean((arg.sync))) {
result.isSync = arg.sync;
}
});
return result;
}
// getData(...args) {
getSignedTx(...args) {
const filterArgs = args.filter(arg => !isFunction(arg) && !isBoolean(arg.sync));
if (filterArgs[1]) {
const { height, hash } = filterArgs[1]; // blockInfo
if (hash && height) {
return this.prepareParametersWithBlockInfo(args);
}
throw Error('The second param is the height & hash of a block');
}
return this.prepareParameters(args);
}
getRawTx(blockHeightInput, blockHashInput, packedInput) {
const rawTx = getTransaction(this._wallet.address, this._contractAddress, this._name, packedInput);
rawTx.refBlockNumber = blockHeightInput;
const blockHash = blockHashInput.match(/^0x/) ? blockHashInput.substring(2) : blockHashInput;
rawTx.refBlockPrefix = (Buffer.from(blockHash, 'hex')).slice(0, 4);
return rawTx;
}
request(...args) {
const { callback } = this.extractArgumentsIntoObject(args);
const params = this.prepareParameters(args);
return {
method: 'broadcast_tx',
callback,
params,
format: this.unpackOutput
};
}
run(...args) {
return this.sendTransaction(...args);
}
bindMethodToContract(contract) {
const { run } = this;
run.request = this.request;
run.call = this.callReadOnly;
run.inputTypeInfo = this._inputType.toJSON();
run.inputType = this._inputType;
run.outputTypeInfo = this._outputType.toJSON();
run.outputType = this._outputType;
run.unpackPackedInput = this.unpackPackedInput;
run.packInput = this.packInput;
run.sendTransaction = this.sendTransaction;
run.getSignedTx = this.getSignedTx;
run.getRawTx = this.getRawTx;
run.unpackOutput = this.unpackOutput;
// eslint-disable-next-line no-param-reassign
contract[this._name] = run;
}
}