Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Branded token and Gateway composer interact with unit tests. #99

Merged
merged 19 commits into from
Feb 22, 2019
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ install:
- npm install
- npm install -g mocha
script:
- npm run test
- npm run test
- npm run test:integration
382 changes: 382 additions & 0 deletions lib/ContractInteract/BrandedToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,382 @@
'use strict';

const Web3 = require('web3');
const Contracts = require('../Contracts');
const AbiBinProvider = require('../AbiBinProvider');
const Utils = require('../../utils/Utils');

const ContractName = 'BrandedToken';

/**
* Contract interact for Branded token.
*/
class BrandedToken {
/**
* Constructor for Anchor.
*
* @param {Object} web3 Web3 object.
* @param {string} address BrandedToken contract address.
*/
constructor(web3, address) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we call web3 => "originWeb3" as BT is deployed on origin chain?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, the BrandedToken class is not concerned with what's origin or auxiliary. It only cares that it is deployed on a chain that it can access with a web3 object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, rather moving it to web3 from where it is specified (and ambivalent)

if (!(web3 instanceof Web3)) {
throw new TypeError("Mandatory Parameter 'web3' is missing or invalid");
}
if (!Web3.utils.isAddress(address)) {
throw new TypeError(
`Mandatory Parameter 'address' is missing or invalid: ${address}`,
);
}

this.web3 = web3;
this.address = address;

this.contract = Contracts.getBrandedToken(this.web3, this.address);

if (!this.contract) {
throw new TypeError(
`Could not load utility branded token contract for: ${this.address}`,
);
}
}

/**
* Deploys a Branded token contract.
*
* @param web3 Origin chain web3 object.
* @param valueToken The value to which valueToken is set.
* @param symbol The value to which tokenSymbol, defined in EIP20Token,
* is set.
* @param name The value to which tokenName, defined in EIP20Token,
* is set.
* @param decimals The value to which tokenDecimals, defined in EIP20Token,
* is set.
* @param conversionRate The value to which conversionRate is set.
* @param conversionRateDecimals The value to which
* conversionRateDecimals is set.
* @param organization Organization contract address.
* @param {Object} txOptions Transaction options.
*
* @returns {Promise<BrandedToken>} Promise containing the Branded token
* instance that has been deployed.
*/
static async deploy(
web3,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unit test for deploy and deployRawTx method is missing 👁

valueToken,
symbol,
name,
decimals,
conversionRate,
conversionRateDecimals,
organization,
txOptions,
) {
if (!txOptions) {
const err = new TypeError('Invalid transaction options.');
return Promise.reject(err);
}
if (!Web3.utils.isAddress(txOptions.from)) {
Copy link
Contributor

@abhayks1 abhayks1 Feb 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the validations for typechecks are repetitive, I think we should create Validation library/util where we can refactor the code. Validation library/util can also be used in mosaic.js and openst.js also. @schemar What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could help 👍

const err = new TypeError(`Invalid from address: ${txOptions.from}.`);
return Promise.reject(err);
}

const tx = BrandedToken.deployRawTx(
web3,
valueToken,
symbol,
name,
decimals,
conversionRate,
conversionRateDecimals,
organization,
);

return Utils.sendTransaction(tx, txOptions).then((txReceipt) => {
const address = txReceipt.contractAddress;
return new BrandedToken(web3, address);
});
}

/**
* Raw transaction for {@link BrandedToken#deploy}.
*
* @param web3 Origin chain web3 object.
* @param valueToken The value to which valueToken is set.
* @param symbol The value to which tokenSymbol, defined in EIP20Token,
* is set.
* @param name The value to which tokenName, defined in EIP20Token,
* is set.
* @param decimals The value to which tokenDecimals, defined in EIP20Token,
* is set.
* @param conversionRate The value to which conversionRate is set.
* @param conversionRateDecimals The value to which
* conversionRateDecimals is set.
* @param organization Organization contract address.
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"A value to which ... is set" is not a very valuable documentation. Instead, it should explain what the parameters are for/explain them.

Copy link
Contributor Author

@0xsarvesh 0xsarvesh Feb 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can add a @dev note in the document explaining conversion rate and decimal.
Does this work for you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't find @dev as a valid JSDoc annotation. It would also not be correct as this info is not intended for developers, but for consumers of the function and should therefore not be hidden behind @dev.
Why don't you explain the parameters where they're supposed to be explained?

* @returns {Object} Raw transaction.
*/
static deployRawTx(
web3,
valueToken,
symbol,
name,
decimals,
conversionRate,
conversionRateDecimals,
organization,
) {
if (!(web3 instanceof Web3)) {
throw new TypeError(
`Mandatory Parameter 'web3' is missing or invalid: ${web3}`,
);
}
if (!Web3.utils.isAddress(valueToken)) {
throw new TypeError(`Invalid valueToken address: ${valueToken}.`);
}
if (!Web3.utils.isAddress(organization)) {
throw new TypeError(`Invalid organization address: ${organization}.`);
}
if (!(conversionRate > 0)) {
throw new TypeError(`Invalid conversion rate: ${conversionRate}. It should be more than zero`);
}
if (!(conversionRateDecimals < 5)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check should be "<=" because ConversionRateDecimals can be less/equal to 5.

conversionRateDecimals <= 5

throw new TypeError(`Invalid conversion rate decimal: ${conversionRateDecimals}. It should be less than 5`);
}

const abiBinProvider = new AbiBinProvider();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be injected instead of hard coded? Probably in the constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a static method, so we can't pass via constructor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then pass it as an argument to the static method. Alternatively pass the bin.

const bin = abiBinProvider.getBIN(ContractName);

const args = [
valueToken,
symbol,
name,
decimals,
conversionRate,
conversionRateDecimals,
organization,
];

const contract = Contracts.getBrandedToken(web3, null, null);

return contract.deploy(
{
data: bin,
arguments: args,
},
);
}

/**
* This calculates branded tokens equivalent to given value tokens.
*
* @param valueTokens Amount of value token.
*
* @return {Promise<string>} Promise that resolves to amount of branded token.
*/
convertToBrandedTokens(valueTokens) {
return this.contract.methods
.convertToBrandedTokens(valueTokens)
.call();
}

/**
* Request stake for given amount. Approval for stake amount to branded
* token is required before calling this method.
*
* @param stakeAmount Stake amount.
* @param txOptions Transaction options.
*
* @return {Promise<Object>} Promise that resolves to transaction receipt.
*/
async requestStake(stakeAmount, txOptions) {
if (!txOptions) {
const err = new TypeError(`Invalid transaction options: ${txOptions}.`);
return Promise.reject(err);
}
if (!Web3.utils.isAddress(txOptions.from)) {
const err = new TypeError(
`Invalid from address ${txOptions.from} in transaction options.`,
);
return Promise.reject(err);
}

const mintedAmount = await this.convertToBrandedTokens(stakeAmount);
Copy link
Contributor

@abhayks1 abhayks1 Feb 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe requestStake interface should be aligned with BrandedToken.requestStake.
So mintAmount should not be calculated in this function. It causes an extra geth call and updates the interfaces. Staker can call convertToBrandedTokens to know the mint amount.

const tx = await this.requestStakeRawTx(stakeAmount, mintedAmount);
return Utils.sendTransaction(tx, txOptions);
}

/**
* Raw tx for request stake.
*
* @param stakeAmount Stake amount.
* @param mintAmount Amount that will be minted after staking.
*
* @return Promise<Object> Raw transaction object.
*/
requestStakeRawTx(stakeAmount, mintAmount) {
return Promise.resolve(this.contract.methods.requestStake(stakeAmount, mintAmount));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Promise.resolve and not simply:

Suggested change
return Promise.resolve(this.contract.methods.requestStake(stakeAmount, mintAmount));
return this.contract.methods.requestStake(stakeAmount, mintAmount);

}

/**
* Accept open stake request identified by request hash.
*
* @param stakeRequestHash Stake request hash.
* @param r R of signature received from worker.
* @param s s of signature received from worker.
* @param v v of signature received from worker.
* @param txOptions Transaction options
*
* @return {Promise<Object>} Promise that resolves to transaction receipt.
*/
async acceptStakeRequest(stakeRequestHash, r, s, v, txOptions) {
if (!txOptions) {
const err = new TypeError(`Invalid transaction options: ${txOptions}.`);
return Promise.reject(err);
}
if (!Web3.utils.isAddress(txOptions.from)) {
const err = new TypeError(
`Invalid from address ${txOptions.from} in transaction options.`,
);
return Promise.reject(err);
}

const tx = await this.acceptStakeRequestRawTx(stakeRequestHash, r, s, v);
return Utils.sendTransaction(tx, txOptions);
}

/**
* Raw transaction for accept stake request.
*
* @param stakeRequestHash Stake request hash.
* @param r R of signature received from worker.
* @param s s of signature received from worker.
* @param v v of signature received from worker.
*
* @return Promise<Object> Raw transaction object.
*/
acceptStakeRequestRawTx(stakeRequestHash, r, s, v) {
if (!stakeRequestHash) {
const err = new TypeError(`Invalid stakeRequestHash: ${stakeRequestHash}.`);
return Promise.reject(err);
}
if (!r) {
const err = new TypeError(`Invalid r of signature: ${r}.`);
return Promise.reject(err);
}
if (!s) {
const err = new TypeError(`Invalid s of signature: ${s}.`);
return Promise.reject(err);
}
if (!v) {
const err = new TypeError(`Invalid v of signature: ${v}.`);
return Promise.reject(err);
}

return Promise.resolve(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again why Promise.resolve?

this.contract.methods.acceptStakeRequest(
stakeRequestHash,
r,
s,
v,
),
);
}

/**
* Lift restriction for given list of addresses.
*
* @param addresses Addresses for which to lift restrictions.
* @param txOptions Transaction options.
*
* @return {Promise<Object>} Promise that resolves to transaction receipt.
*/
async liftRestriction(addresses, txOptions) {
if (!txOptions) {
const err = new TypeError(`Invalid transaction options: ${txOptions}.`);
return Promise.reject(err);
}
if (!Web3.utils.isAddress(txOptions.from)) {
const err = new TypeError(
`Invalid from address ${txOptions.from} in transaction options.`,
);
return Promise.reject(err);
}

const tx = await this.liftRestrictionRawTx(addresses);
return Utils.sendTransaction(tx, txOptions);
}

/**
* Raw tx for lift restriction.
*
* @param addresses Addresses for which to lift restrictions.
*
* @return Promise<Object> Raw transaction object.
*/
liftRestrictionRawTx(addresses) {
if (!addresses || addresses.length === 0) {
const err = new TypeError(
`At least one addresses must be defined : ${addresses}`,
);
return Promise.reject(err);
}
return Promise.resolve(this.contract.methods.liftRestriction(addresses));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Promise?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to make it consistent with mosaic.js.
In mosaic.js all RawTx methods returns a promise, all repos should be same I think 🤔

Refer this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we invent Promises when there are none? That just makes usability harder for the consumer. It seems we still have quite some mess in mosaic.js as well, where we did not review properly I assume. Same for AsyncAssert and Spy ...

}

/**
* Checks if given address is unrestricted.
*
* @param address Address needs to be checked.
*
* @returns {Promise<boolean>} Promise that resolves to `true` if
* unrestricted..
*/
isUnrestricted(address) {
return this.contract.methods
.isUnrestricted(address)
.call();
}

/**
* This rejects stake request, should be called by organization worker.
*
* @param stakeRequestHash Stake request hash.
* @return Promise<Object> Raw transaction object.
*
* @return {Promise<Object>} Promise that resolves to transaction receipt.
*/
async rejectStakeRequest(stakeRequestHash, txOptions) {
if (!txOptions) {
const err = new TypeError(`Invalid transaction options: ${txOptions}.`);
return Promise.reject(err);
}
if (!Web3.utils.isAddress(txOptions.from)) {
const err = new TypeError(
`Invalid from address ${txOptions.from} in transaction options.`,
);
return Promise.reject(err);
}

const tx = await this.rejectStakeRequestRawTx(stakeRequestHash);
return Utils.sendTransaction(tx, txOptions);
}

/**
* This returns raw tx for reject stake request.
*
* @param stakeRequestHash Stake request hash.
*
* @return Promise<Object> Raw transaction object.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @return Promise<Object> Raw transaction object.
* @return {Promise<Object>} Raw transaction object.

*/
rejectStakeRequestRawTx(stakeRequestHash) {
if (!stakeRequestHash) {
const err = new TypeError(`Invalid stakeRequestHash: ${stakeRequestHash}.`);
return Promise.reject(err);
}

return Promise.resolve(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again why Promise?

this.contract.methods.rejectStakeRequest(stakeRequestHash),
);
}
}

module.exports = BrandedToken;
Loading