Skip to content

Commit

Permalink
\## [0.1.0] - 2018-11-06
Browse files Browse the repository at this point in the history
First version of Ministro contract tool.
  • Loading branch information
DZariusz committed Nov 6, 2018
2 parents a530f05 + 3823f30 commit 81fe04d
Show file tree
Hide file tree
Showing 23 changed files with 6,137 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["env"]
}
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Unifying editor configurations for all developers:
# For details see http://editorconfig.org/

root = true

[*]
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
30 changes: 30 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"extends": "airbnb-base",
"env": {
"mocha": true
},
"globals": {
"artifacts": true,
"assert": true,
"beforeEach": true,
"contract": true,
"describe": true,
"it": true,
"web3": true
},
"rules": {
"no-console": "off",
"prefer-destructuring": ["error", {
"VariableDeclarator": {
"array": true,
"object": true
},
"AssignmentExpression": {
"array": false, // this will allow for: var bar = foo[0] <- access array with index
"object": true
}
}, {
"enforceForRenamedProperties": false
}]
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea/
node_modules/
build/
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [0.1.0] - 2018-11-06

First version of Ministro contract tool.
89 changes: 87 additions & 2 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,2 +1,87 @@
# ministro-tool
JavaScript tool created for helping in process of testing solidity smart contracts.
# Ministro Tool for Ministro Contract

JavaScript tool created for helping in process of testing solidity smart contracts.

## What MinistroTool is?

Short answer: it is a tool that helps you create ministro contract.
Now let's explain what ministro contract is.

When you testing solidity contracts, you have a lot of repeated code like:

```
it('should do something', () => {
await instance.foo()
assert(foo)
}
it('should do something again', () => {
await instance.foo()
assert(foo)
}
```

Once you tested your `foo()` in one scenario, in most cases you not checking it
next time, you using `foo()` (in some other scenarios...) - and this is not good.

*MinistroContract* allows you to write test conditions once and use them
in each case scenario with just one line of code.
Thanks to this, your tests are very strong, easy to understand
and you have clean, short code.

## How can I use ministro tool/contract?

The best way to understand how to use it, is to review the simple example in `test` directory.

Basically you need to create `ministroContract` that reflect all methods
that real smart contract has.
Each `ministroContract` must have all methods from smart contract (including public readers).
Each method must have all possible test you can perform base
**ONLY (!)** on input (arguments) and output (events, return values) data.
You also must cover scenario, when method throw (if this is a case).

When you do all that, you just execute a method on `ministroContract`
and ech time you do it in your tests scenarios, all this tests that you wrote
(in ministro method) will be executed and checked.

There are also some additional helpers mechanisms here like:
* checking is transaction was successful or throw
* read all events for transaction

### Ministro Methods

Each ministro method must have:
* parameters that are equal to smart contract instance (required)
* object with transaction parameters (optional)
* parameter that inform us, if this execution is expected to throw (optional)

## How to use it in tests?

Please review the code of example `test/ministro-contracts/ministroOwnable.js` -
its pretty simple, so you should be able to understand how to use it.

**Note:** this test might not be enough for each cases, if some external
data are present and ministro contract do not have access to them,
you need to check them directly in test file (outside ministro).

**DO NOT** modify ministro by adding additional params to the method,
because this is not how it should work.
**MinistroContract test should be the same for ALL cases** and they should
work the same each time, so anybody can use it in any scenario
and not worry abut *your special case*.


## Installation

```
git clone <this-repo>
git hf init
npm install
```

### Run test

```
npm run lint
npm run test
```
23 changes: 23 additions & 0 deletions contracts/Migrations.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
pragma solidity ^0.4.24;

contract Migrations {
address public owner;
uint public last_completed_migration;

modifier restricted() {
if (msg.sender == owner) _;
}

constructor () public {
owner = msg.sender;
}

function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}

function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
78 changes: 78 additions & 0 deletions contracts/Ownable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
pragma solidity ^0.4.24;


/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address private _owner;


event OwnershipRenounced(address indexed previousOwner);
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);


/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor() public {
_owner = msg.sender;
}

/**
* @return the address of the owner.
*/
function owner() public view returns(address) {
return _owner;
}

/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner());
_;
}

/**
* @return true if `msg.sender` is the owner of the contract.
*/
function isOwner() public view returns(bool) {
return msg.sender == _owner;
}

/**
* @dev Allows the current owner to relinquish control of the contract.
* @notice Renouncing to ownership will leave the contract without an owner.
* It will not be possible to call the functions with the `onlyOwner`
* modifier anymore.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipRenounced(_owner);
_owner = address(0);
}

/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}

/**
* @dev Transfers control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0));
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
5 changes: 5 additions & 0 deletions migrations/1_initial_migration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Migrations = artifacts.require('./Migrations.sol');

module.exports = (deployer) => {
deployer.deploy(Migrations);
};
5 changes: 5 additions & 0 deletions migrations/2_deploy_Ownable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Ownable = artifacts.require('./Ownable.sol');

module.exports = (deployer) => {
deployer.deploy(Ownable);
};
55 changes: 55 additions & 0 deletions ministro-utils/expectedExceptionPromise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @param {!Function.<!Promise>} action.
* @param {!Number | !string | !BigNumber} gasToUse.
* @returns {!Promise} which throws unless it hit a valid error.
*/
module.exports = function expectedExceptionPromise(action, gasToUse) {
let didThrow = false;
return new Promise(((resolve, reject) => {
try {
resolve(action());
} catch (e) {
didThrow = true;
reject(e);
}
}))
.then((txObj) => {
if (didThrow === false) throw new Error(`Action should throw: ${action.toString()}`);

if (typeof txn === 'string') { return web3.eth.getTransactionReceiptMined(txObj); } // regular tx hash

if (typeof txObj.receipt !== 'undefined') { return txObj.receipt; } // truffle-contract function call

if (typeof txObj.transactionHash === 'string') { return web3.eth.getTransactionReceiptMined(txObj.transactionHash); } // deployment

return txObj; // Unknown last case
})
.then(
(receipt) => {
// We are in Geth
if (typeof receipt.status !== 'undefined') {
// Byzantium
assert.strictEqual(parseInt(receipt.status, 10), 0, 'should have reverted');
} else {
// Pre Byzantium
assert.equal(receipt.gasUsed, gasToUse, 'should have used all the gas');
}
},
(e) => {
if ((`${e}`).indexOf('invalid JUMP') > -1
|| (`${e}`).indexOf('out of gas') > -1
|| (`${e}`).indexOf('invalid opcode') > -1
|| (`${e}`).indexOf('revert') > -1) {
// We are in TestRPC
} else if ((`${e}`).indexOf('please check your gas amount') > -1) {
// We are in Geth for a deployment
} else if ((`${e}`).indexOf('Cannot send value to non-payable function') > -1) {
// We are in ganache
} else if ((`${e}`).indexOf('account not recognized') > -1) {
// Check if msg.sender exists and its not generated by random numbers
} else {
throw e;
}
},
);
}
26 changes: 26 additions & 0 deletions ministro-utils/getTransactionReceiptMined.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = function getTransactionReceiptMined(txHash, interval) {
const self = this;
const transactionReceiptAsync = (resolve, reject) => {
self.getTransactionReceipt(txHash, (error, receipt) => {
if (error) {
reject(error);
} else if (receipt === null) {
setTimeout(
() => transactionReceiptAsync(resolve, reject),
interval || 500,
);
} else {
resolve(receipt);
}
});
};

if (Array.isArray(txHash)) {
return Promise.all(txHash.map(oneTxHash => getTransactionReceiptMined(oneTxHash, interval)));
} if (typeof txHash === 'string') {
return new Promise(transactionReceiptAsync);
}
throw new Error(`Invalid Type: ${txHash}`);
};

web3.eth.getTransactionReceiptMined = getTransactionReceiptMined;
4 changes: 4 additions & 0 deletions ministro-utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
require('babel-register');
require('babel-polyfill');

module.exports = require('./ministroExecute');
Loading

0 comments on commit 81fe04d

Please sign in to comment.