Skip to content
This repository has been archived by the owner on Sep 3, 2019. It is now read-only.

Commit

Permalink
Move construct data and validate to encoder (#72)
Browse files Browse the repository at this point in the history
* Move construct data and validate method and args

* Use new encoder funcs

* Update readme

* Update version

* Move validate tests to encoder tests

* Remove auto injecting qweb3 into window
  • Loading branch information
dwalintukan committed Aug 15, 2018
1 parent 2d765ed commit aa022d1
Show file tree
Hide file tree
Showing 8 changed files with 775 additions and 781 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ qweb3.encoder.uintToHex(num);
qweb3.encoder.stringToHex(string, maxCharLen);
qweb3.encoder.stringArrayToHex(strArray, numOfItems);
qweb3.encoder.padHexString(hexStr);
qweb3.encoder.constructData(abi, methodName, args);
```

## Decoder
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "qweb3",
"namespace": "bodhi",
"version": "1.0.0",
"version": "1.1.0",
"description": "Qtum JavaScript API comunicating to qtum node over RPC",
"main": "./src/index.js",
"repository": {
Expand Down
152 changes: 16 additions & 136 deletions src/contract.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
const _ = require('lodash');

const { initProvider } = require('./providers');
const Utils = require('./utils');
const Constants = require('./constants');
const Decoder = require('./formatters/decoder');
const Encoder = require('./formatters/encoder');
const Decoder = require('./formatters/decoder');

const DEFAULT_AMOUNT = 0;
const DEFAULT_GAS_LIMIT = 250000;
Expand All @@ -24,170 +21,53 @@ class Contract {
}

/**
* @dev Executes a callcontract on a view/pure method via the qtum-cli.
* Executes a callcontract on a view/pure method.
* @param {string} methodName Name of contract method
* @param {array} params Parameters of contract method
* @return {Promise} Promise containing result object or Error
* @param {object} params Parameters of contract method
* @return {Promise} Call result.
*/
async call(methodName, params) {
const { methodArgs, senderAddress } = params;
const { method: methodObj, args } = this.validateMethodAndArgs(methodName, methodArgs);

let result = await this.provider.rawCall('callcontract', [
this.address,
this.constructDataHex(methodObj, args),
senderAddress,
]);
// Format the result
result = Decoder.decodeCall(result, this.abi, methodName, true);
const data = Encoder.constructData(this.abi, methodName, methodArgs);
let result = await this.provider.rawCall('callcontract', [this.address, data, senderAddress]);
result = Decoder.decodeCall(result, this.abi, methodName, true); // Format the result
return result;
}

/*
* @dev Executes a sendtocontract on this contract via the qtum-cli.
* @param methodName Method name to execute as a string.
* @param params Parameters of the contract method.
* @return The transaction id of the sendtocontract.
*/
/**
* Executes a sendtocontract transaction.
* @param {string} methodName Method name to call.
* @param {object} params Parameters of the contract method.
* @return {Promise} Transaction ID of the sendtocontract.
*/
async send(methodName, params) {
// Throw if methodArgs or senderAddress is not defined in params
Utils.paramsCheck('send', params, ['methodArgs', 'senderAddress']);

const { methodArgs, amount, gasLimit, gasPrice, senderAddress } = params;
const { method: methodObj, args } = this.validateMethodAndArgs(methodName, methodArgs);
const data = Encoder.constructData(this.abi, methodName, methodArgs);
const amt = amount || DEFAULT_AMOUNT;
const limit = gasLimit || DEFAULT_GAS_LIMIT;
const price = gasPrice || DEFAULT_GAS_PRICE;

const result = await this.provider.rawCall('sendtocontract', [
this.address,
this.constructDataHex(methodObj, args),
data,
amt,
limit,
price.toFixed(8),
senderAddress,
]);

// Add request object with params used for request
// Add original request params to result obj
result.args = {
contractAddress: this.address,
amount: amt,
gasLimit: limit,
gasPrice: price,
};

return result;
}

/*
* @dev Constructs the data hex string needed for a call() or send().
* @param methodObj The json object of the method taken from the ABI.
* @param args The arguments for the method.
* @return The full hex string concatenated together.
*/
constructDataHex(methodObj, args) {
if (!methodObj) {
throw new Error('methodObj should not be undefined.');
}

// function hash
const funcHash = Encoder.objToHash(methodObj, true);

const numOfParams = methodObj.inputs.length;

// create an array of data hex strings which will be combined at the end
const dataHexArr = _.times(numOfParams, _.constant(null));

// calculate start byte for dynamic data
let dataLoc = 0;
_.each(methodObj.inputs, (item) => {
const { type } = item;
if (type.match(Constants.REGEX_STATIC_ARRAY)) {
// treat each static array as an individual slot for dynamic data location purposes
const arrCap = _.toNumber(type.match(Constants.REGEX_NUMBER)[1]);
dataLoc += arrCap;
} else {
dataLoc += 1;
}
});

_.each(methodObj.inputs, (item, index) => {
const { type } = item;
let hex;

if (type === Constants.BYTES) {
throw new Error('dynamics bytes conversion not implemented.');
} else if (type === Constants.STRING) {
throw new Error('dynamic string conversion not implemented.');
} else if (type.match(Constants.REGEX_DYNAMIC_ARRAY)) { // dynamic types
let data = '';

// set location of dynamic data
const startBytesLoc = dataLoc * 32;
hex = Encoder.uintToHex(startBytesLoc);
dataHexArr[index] = hex;

// construct data
// add length of dynamic data set
const numOfDynItems = args[index].length;
data += Encoder.uintToHex(numOfDynItems);

// add each hex converted item
_.each(args[index], (dynItem) => {
data += Encoder.encodeParam(type, dynItem);
});

// add the dynamic data to the end
dataHexArr.push(data);

// increment starting data location
// +1 for the length of data set
dataLoc += numOfDynItems + 1;
} else if (type === Constants.ADDRESS
|| type === Constants.BOOL
|| type.match(Constants.REGEX_UINT)
|| type.match(Constants.REGEX_INT)
|| type.match(Constants.REGEX_BYTES)
|| type.match(Constants.REGEX_STATIC_ARRAY)) { // static types
dataHexArr[index] = Encoder.encodeParam(type, args[index]);
} else {
console.error(`found unknown type: ${type}`);
}
});

return funcHash + dataHexArr.join('');
}

/**
* Validates arguments by ABI schema and throws errors if mismatch.
* @param {String} methodName The method name.
* @param {Array} methodArgs The method arguments.
* @return {Object} The method object in ABI and processed argument array.
*/
validateMethodAndArgs(methodName, methodArgs) {
const methodObj = _.find(this.abi, { name: methodName });

if (_.isUndefined(methodObj)) {
throw new Error(`Method ${methodName} not defined in ABI.`);
}
if (methodObj.inputs.length !== methodArgs.length) {
throw new Error('Number of arguments supplied does not match ABI method args.');
}

let args;
if (_.isUndefined(methodArgs)) {
args = [];
} else if (_.isArray(methodArgs)) {
args = methodArgs;
} else {
args = [methodArgs];
}

return {
method: methodObj,
args,
};
}
}

module.exports = Contract;
Loading

0 comments on commit aa022d1

Please sign in to comment.