Smart contract initial setup

This commit is contained in:
Ravindra Kumar 2019-03-11 18:00:45 +05:30
parent a82ac41f6f
commit 1994d5ce31
30 changed files with 852 additions and 93 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["env","es2015", "stage-2", "stage-3"]
}

37
.circleci/config.yml Normal file
View File

@ -0,0 +1,37 @@
# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: circleci/node:8.4.0
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/mongo:3.4.4
working_directory: ~/repo
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run: npm install
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
# run tests!
- run: npm run test-ci

2
.env.sample Normal file
View File

@ -0,0 +1,2 @@
RINKEBY_PRIVATE_KEY="df7ebe6c9601adf4e911faac9da547686e6453a11cf13264d895fc2979a6bec2"
ROPSTEN_PRIVATE_KEY="192f175c2f5e5a9437fdbc12043404763f96ccbcd6fc32b1d61dbb61e14e6f34"

4
.eslintignore Normal file
View File

@ -0,0 +1,4 @@
tmp/**
build/**
node_modules/**
contracts/**

102
.eslintrc.js Normal file
View File

@ -0,0 +1,102 @@
module.exports = {
parser: 'babel-eslint',
parserOptions: {
ecmaFeatures: {
generators: true,
experimentalObjectRestSpread: true
},
sourceType: 'module',
allowImportExportEverywhere: false
},
extends: [
'eslint:recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:promise/recommended',
'plugin:security/recommended'
],
plugins: [
'compat',
'prettier',
'promise',
'security'
],
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.jsx', '.json', '.css'],
paths: './src'
}
},
polyfills: ['fetch', 'promises']
},
env: {
node: true,
},
globals: {
__DEV__: true,
__dirname: true,
after: true,
afterAll: true,
afterEach: true,
artifacts: true,
assert: true,
before: true,
beforeAll: true,
beforeEach: true,
console: true,
contract: true,
describe: true,
expect: true,
fetch: true,
global: true,
it: true,
module: true,
process: true,
Promise: true,
require: true,
setTimeout: true,
test: true,
xdescribe: true,
xit: true,
web3: true
},
rules: {
'compat/compat': 'error',
'import/first': 'error',
'import/no-anonymous-default-export': 'error',
'import/no-unassigned-import': 'error',
'import/prefer-default-export': 'error',
'import/no-named-as-default': 'off',
'import/no-unresolved': 'error',
'prettier/prettier': [
'error',
{
semi: false,
singleQuote: true,
trailingComma: 'none'
}
],
'promise/avoid-new': 'off',
'security/detect-object-injection': 'off',
'arrow-body-style': 'off',
'lines-between-class-members': ['error', 'always'],
'no-console': ['warn', { allow: ['assert'] }],
'no-shadow': 'error',
'no-var': 'error',
'padding-line-between-statements': [
'error',
{ blankLine: 'always', prev: 'class', next: '*' },
{ blankLine: 'always', prev: 'do', next: '*' },
{ blankLine: 'always', prev: '*', next: 'export' },
{ blankLine: 'always', prev: 'for', next: '*' },
{ blankLine: 'always', prev: 'if', next: '*' },
{ blankLine: 'always', prev: 'switch', next: '*' },
{ blankLine: 'always', prev: 'try', next: '*' },
{ blankLine: 'always', prev: 'while', next: '*' },
{ blankLine: 'always', prev: 'with', next: '*' }
],
'prefer-const': 'error'
}
}

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.sol linguist-language=Solidity

106
.gitignore vendored Normal file
View File

@ -0,0 +1,106 @@
# Created by https://www.gitignore.io/api/solidity,soliditytruffle
### Solidity ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
### SolidityTruffle ###
# depedencies
node_modules
# testing
# production
build
build_webpack
# misc
.DS_Store
npm-debug.log
.truffle-solidity-loader
.vagrant/**
blockchain/geth/**
blockchain/keystore/**
blockchain/history
#truffle
.tern-port
yarn.lock
package-lock.json
# End of https://www.gitignore.io/api/solidity,soliditytruffle
test-results/

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
package.json

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "none"
}

6
.solcover.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
port: 9545,
testrpcOptions:
'-p 9545 -m "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"',
copyNodeModules: false
}

2
.soliumignore Normal file
View File

@ -0,0 +1,2 @@
node_modules
contracts/Migrations.sol

27
.soliumrc.json Normal file
View File

@ -0,0 +1,27 @@
{
"extends": "solium:all",
"plugins": ["security"],
"rules": {
"arg-overflow": "error",
"array-declarations": "error",
"blank-lines": "error",
"camelcase": "error",
"comma-whitespace": "error",
"deprecated-suicide": "error",
"function-whitespace": "error",
"imports-on-top": "error",
"indentation": ["error", 4],
"lbrace": "error",
"mixedcase": "error",
"no-empty-blocks": "error",
"no-unused-vars": "error",
"operator-whitespace": "error",
"pragma-on-top": "error",
"quotes": ["error", "double"],
"security/no-inline-assembly": "off",
"semicolon-whitespace": "error",
"uppercase": "off",
"variable-declarations": "error",
"whitespace": "error"
}
}

View File

@ -1,28 +0,0 @@
pragma solidity ^0.4.23;
contract AddressRegistry {
event AddressSet(string name, address addr);
mapping(bytes32 => address) registry;
constructor() public {
registry[keccak256(abi.encodePacked("admin"))] = msg.sender;
registry[keccak256(abi.encodePacked("owner"))] = msg.sender;
}
function getAddr(string memory name) public view returns(address) {
return registry[keccak256(abi.encodePacked(name))];
}
function setAddr(string memory name, address addr) public {
require(
msg.sender == getAddr("admin") ||
msg.sender == getAddr("owner"),
"Permission Denied"
);
registry[keccak256(abi.encodePacked(name))] = addr;
emit AddressSet(name, addr);
}
}

1
CONTRIBUTING.md Normal file
View File

@ -0,0 +1 @@
Contributors Guide

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Ravindra Kumar <ravidsrk@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,2 +1,59 @@
# InstaProxyContract
InstaDApp Proxy Contract.
# InstaDApp V2 Contracts
Smart contracts comprising the business logic of the InstaDApp.
## This project uses:
- [Truffle v5](https://truffleframework.com/)
- [Ganache](https://truffleframework.com/ganache)
- [Solium](https://github.com/duaraghav8/Solium)
- [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-solidity)
- [Travis CI](https://travis-ci.org/InstaDApp/InstaContract-v2) and [Circle CI](https://circleci.com/gh/InstaDApp/InstaContract-v2)
- [Coveralls](https://coveralls.io/github/InstaDApp/InstaContract-v2?branch=master)
## Installation
1. Install Truffle and Ganache CLI globally.
```javascript
npm install -g truffle@beta
npm install -g ganache-cli
```
2. Create a `.env` file in the root directory and add your private key.
## Commands:
```
Compile contracts: truffle compile
Migrate contracts: truffle migrate
Test contracts: truffle test
Run eslint: npm run lint
Run solium: npm run solium
Run solidity-coverage: npm run coverage
Run lint, solium, and truffle test: npm run test
```
## License
```
MIT License
Copyright (c) 2019 InstaDApp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

View File

@ -1,43 +1,40 @@
pragma solidity ^0.4.23;
pragma solidity ^0.5.0;
interface AddrRegistry {
function getAddr(string calldata name) external view returns(address);
function getAddr(string calldata) external view returns (address);
}
contract AddressRegistry {
address public registry;
modifier onlyAdmin() {
require(
msg.sender == getAddress("admin"),
"Permission Denied"
);
require(msg.sender == getAddress("admin"), "Permission Denied");
_;
}
function getAddress(string memory name) internal view returns(address) {
function getAddress(string memory name) internal view returns (address) {
AddrRegistry addrReg = AddrRegistry(registry);
return addrReg.getAddr(name);
}
}
contract LogicRegistry is AddressRegistry {
contract LogicRegistry is AddressRegistry {
event DefaultLogicSet(address logicAddr);
event LogicSet(address logicAddr, bool isLogic);
mapping(address => bool) public DefaultLogicProxies;
mapping(address => bool) public LogicProxies;
mapping(address => bool) public defaultLogicProxies;
mapping(address => bool) public logicProxies;
constructor(address registry_) public {
registry = registry_;
}
function getLogic(address logicAddr) public view returns(bool) {
if (DefaultLogicProxies[logicAddr]) {
function getLogic(address logicAddr) public view returns (bool) {
if (defaultLogicProxies[logicAddr]) {
return true;
} else if (LogicProxies[logicAddr]) {
} else if (logicProxies[logicAddr]) {
return true;
} else {
return false;
@ -46,13 +43,13 @@ contract LogicRegistry is AddressRegistry {
function setLogic(address logicAddr, bool isLogic) public onlyAdmin {
require(msg.sender == getAddress("admin"), "Permission Denied");
LogicProxies[logicAddr] = true;
logicProxies[logicAddr] = true;
emit LogicSet(logicAddr, isLogic);
}
function setDefaultLogic(address logicAddr) public onlyAdmin {
require(msg.sender == getAddress("admin"), "Permission Denied");
DefaultLogicProxies[logicAddr] = true;
defaultLogicProxies[logicAddr] = true;
emit DefaultLogicSet(logicAddr);
}

27
contracts/Migrations.sol Normal file
View File

@ -0,0 +1,27 @@
pragma solidity ^0.5.0;
/* solium-disable mixedcase */
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 _newAddress) public restricted {
Migrations upgraded = Migrations(_newAddress);
upgraded.setCompleted(last_completed_migration);
}
}
/* solium-enable mixedcase */

39
contracts/Ownable.sol Normal file
View File

@ -0,0 +1,39 @@
pragma solidity ^0.5.0;
/**
* @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 public owner;
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;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner, "Only owner accessible");
_;
}
/**
* @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 {
require(newOwner != address(0), "Address not equal to zero");
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}

View File

@ -1,5 +1,4 @@
pragma solidity ^0.4.23;
pragma solidity ^0.5.0;
interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
@ -9,15 +8,19 @@ interface ICDP {
function give(bytes32 cup, address guy) external;
}
contract ProxyTest {
contract ProxyTest {
event LogTransferETH(address dest, uint amount);
event LogTransferERC20(address token, address dest, uint amount);
event LogTransferCDP(address dest, uint num);
function transferETH(address dest, uint amount) public payable {
dest.transfer(amount);
emit LogTransferETH(dest, amount);
emit LogTransferETH(
dest,
amount
);
}
function transferERC20(address tokenAddr, address dest, address amount) public {
@ -31,5 +34,4 @@ contract ProxyTest {
loanMaster.give(bytes32(num), dest);
emit LogTransferCDP(dest, num);
}
}

View File

@ -1,9 +1,8 @@
pragma solidity ^0.4.23;
pragma solidity ^0.5.0;
import "./UserProxy.sol";
// ProxyRegistry
contract ProxyRegistry {
event Created(address indexed sender, address indexed owner, address proxy);
mapping(address => UserProxy) public proxies;
@ -26,6 +25,7 @@ contract ProxyRegistry {
proxies[owner] == UserProxy(0) || proxies[owner].owner() != owner,
"multiple-proxy-per-user-not-allowed"
); // Not allow new proxy if the user already has one and remains being the owner
proxy = new UserProxy(logicProxyAddr, activeDays);
emit Created(msg.sender, owner, address(proxy));
proxy.setOwner(owner);

View File

@ -1,4 +1,4 @@
pragma solidity ^0.4.23;
pragma solidity ^0.5.0;
library SafeMath {
@ -9,8 +9,8 @@ library SafeMath {
}
}
contract UserAuth {
contract UserAuth {
using SafeMath for uint;
using SafeMath for uint256;
@ -33,16 +33,6 @@ contract UserAuth {
_;
}
function isAuth(address src) internal view returns (bool) {
if (src == address(this)) {
return true;
} else if (src == owner) {
return true;
} else {
return false;
}
}
function setOwner(address owner_) public auth {
owner = owner_;
emit LogSetOwner(owner, false);
@ -61,18 +51,20 @@ contract UserAuth {
emit LogSetGuardian(guardian_);
}
function isAuth(address src) internal view returns (bool) {
if (src == address(this)) {
return true;
} else if (src == owner) {
return true;
} else {
return false;
}
}
}
contract UserNote {
event LogNote(
bytes4 indexed sig,
address indexed guy,
bytes32 indexed foo,
bytes32 indexed bar,
uint wad,
bytes fax
) anonymous;
event LogNote(bytes4 indexed sig, address indexed guy, bytes32 indexed foo, bytes32 indexed bar, uint wad, bytes fax);
modifier note {
bytes32 foo;
@ -81,31 +73,41 @@ contract UserNote {
foo := calldataload(4)
bar := calldataload(36)
}
emit LogNote(msg.sig, msg.sender, foo, bar, msg.value, msg.data);
emit LogNote(
msg.sig,
msg.sender,
foo,
bar,
msg.value,
msg.data
);
_;
}
}
interface LogicRegistry {
function getLogic(address logicAddr) external view returns(bool);
function getLogic(address logicAddr) external view returns (bool);
}
// checking if the logic proxy is authorised
contract UserLogic {
address public logicProxyAddr;
function isAuthorisedLogic(address logicAddr) internal view returns(bool) {
function isAuthorisedLogic(address logicAddr) internal view returns (bool) {
LogicRegistry logicProxy = LogicRegistry(logicProxyAddr);
return logicProxy.getLogic(logicAddr);
}
}
// UserProxy
// Allows code execution using a persistant identity This can be very
// useful to execute a sequence of atomic actions. Since the owner of
// the proxy can be changed, this allows for dynamic ownership models
// i.e. a multisig
contract UserProxy is UserAuth, UserNote, UserLogic {
constructor(address logicProxyAddr_, uint activePeriod_) public {
logicProxyAddr = logicProxyAddr_;
lastActivity = block.timestamp;
@ -114,13 +116,7 @@ contract UserProxy is UserAuth, UserNote, UserLogic {
function() external payable {}
function execute(address _target, bytes memory _data)
public
auth
note
payable
returns (bytes memory response)
{
function execute(address _target, bytes memory _data) public payable auth note returns (bytes memory response) {
require(_target != address(0), "user-proxy-target-address-required");
require(isAuthorisedLogic(_target), "logic-proxy-address-not-allowed");
lastActivity = block.timestamp;
@ -135,10 +131,10 @@ contract UserProxy is UserAuth, UserNote, UserLogic {
returndatacopy(add(response, 0x20), 0, size)
switch iszero(succeeded)
case 1 {
// throw if delegatecall failed
revert(add(response, 0x20), size)
}
case 1 {
// throw if delegatecall failed
revert(add(response, 0x20), size)
}
}
}
}

View File

@ -0,0 +1,5 @@
const Migrations = artifacts.require('./Migrations.sol')
module.exports = async deployer => {
await deployer.deploy(Migrations)
}

View File

@ -0,0 +1,5 @@
const ownableFactory = artifacts.require('Ownable.sol')
module.exports = async deployer => {
await deployer.deploy(ownableFactory)
}

View File

@ -0,0 +1,10 @@
{
"reporterEnabled": "mocha-junit-reporter, eth-gas-reporter",
"mochaJunitReporterReporterOptions": {
"mochaFile": "./test-results/test-contract-results.xml"
},
"reporterOptions": {
"currency": "USD",
"gasPrice": 21
}
}

68
package.json Normal file
View File

@ -0,0 +1,68 @@
{
"name": "smart-contract-starter",
"description": "Boilerplate for your next Smart Contract, made simple.",
"version": "0.0.1",
"author": "Ravindra Kumar <ravidsrk@gmail.com>",
"license": "MIT",
"main": "truffle.js",
"directories": {
"test": "test"
},
"scripts": {
"ganache": "ganache-cli -e 300 -p 9545 -m 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat' --accounts 30 > /dev/null &",
"stop": "sudo kill `sudo lsof -t -i:9545`",
"test": "npm run ganache sleep 5 && npm run lint ./ && npm run solium && truffle test && npm run stop",
"test:gas-reporter": "GAS_REPORTER=true npm run test",
"test-ci": "GAS_REPORTER=true npm run ganache sleep 5 && npm run lint ./ && npm run solium && truffle test",
"coverage": "./node_modules/.bin/solidity-coverage",
"lint": "eslint ./test",
"lint:fix": "eslint ./ --fix",
"solium": "solium -d contracts/",
"solium:fix": "solium -d contracts/ --fix",
"build": "npm run clean:contracts && truffle compile"
},
"dependencies": {
"bn.js": "^4.11.8",
"dotenv": "^6.2.0",
"ethereumjs-wallet": "^0.6.3",
"npm-check-updates": "^2.15.0",
"openzeppelin-solidity": "^2.0.0",
"truffle": "^5.0.0",
"truffle-hdwallet-provider": "^1.0.1",
"web3": "^1.0.0-beta.37",
"webpack": "^4.28.1"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-eslint": "10.0.1",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "6.24.1",
"babel-preset-stage-2": "6.24.1",
"babel-preset-stage-3": "6.24.1",
"babel-register": "6.26.0",
"chai": "4.2.0",
"chai-as-promised": "7.1.1",
"chai-bignumber": "3.0.0",
"coveralls": "3.0.2",
"eslint": "5.10.0",
"eslint-config-prettier": "^3.3.0",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-compat": "^2.6.3",
"eslint-plugin-import": "2.14.0",
"eslint-plugin-node": "8.0.0",
"eslint-plugin-prettier": "^3.0.0",
"eslint-plugin-promise": "4.0.1",
"eslint-plugin-security": "^1.4.0",
"eslint-plugin-standard": "^4.0.0",
"eth-gas-reporter": "^0.1.12",
"ganache-cli": "^6.2.5",
"mocha-junit-reporter": "^1.18.0",
"mocha-multi-reporters": "^1.1.7",
"prettier": "^1.16.4",
"prettier-plugin-solidity-refactor": "^1.0.0-alpha.14",
"solidity-coverage": "0.5.11",
"solium": "1.1.8"
}
}

31
test/Ownable.test.js Normal file
View File

@ -0,0 +1,31 @@
const { assertRevert } = require('./helpers/general')
const Ownable = artifacts.require('Ownable')
contract('Ownable', accounts => {
let ownable
beforeEach(async () => {
ownable = await Ownable.new()
})
it('should have an owner', async () => {
const owner = await ownable.owner()
assert.isTrue(owner !== 0)
})
it('changes owner after transfer', async () => {
const other = accounts[1]
await ownable.transferOwnership(other)
const owner = await ownable.owner()
assert.isTrue(owner === other)
})
it('should prevent non-owners from transfering', async () => {
const other = accounts[2]
const owner = await ownable.owner.call()
assert.isTrue(owner !== other)
await assertRevert(ownable.transferOwnership(other, { from: other }))
})
})

158
test/helpers/general.js Normal file
View File

@ -0,0 +1,158 @@
const { BN } = web3.utils
const decimals18 = new BN(10).pow(new BN(18))
const bigZero = new BN(0)
const addressZero = `0x${'0'.repeat(40)}`
const bytes32Zero = '0x' + '00'.repeat(32)
const gasPrice = new BN(5e9)
const assertRevert = async promise => {
try {
await promise
assert.fail('Expected revert not received')
} catch (error) {
const revertFound = error.message.search('revert') >= 0
assert(revertFound, `Expected "revert", got ${error} instead`)
}
}
const assertJump = async promise => {
try {
await promise
assert.fail('Expected invalid opcode not received')
} catch (error) {
const invalidOpcodeReceived = error.message.search('invalid opcode') >= 0
assert(
invalidOpcodeReceived,
`Expected "invalid opcode", got ${error} instead`
)
}
}
const assertThrow = async promise => {
try {
await promise
} catch (error) {
// TODO: Check jump destination to destinguish between a throw
// and an actual invalid jump.
const invalidOpcode = error.message.search('invalid opcode') >= 0
// TODO: When we contract A calls contract B, and B throws, instead
// of an 'invalid jump', we get an 'out of gas' error. How do
// we distinguish this from an actual out of gas event? (The
// testrpc log actually show an 'invalid jump' event.)
const outOfGas = error.message.search('out of gas') >= 0
const revert = error.message.search('revert') >= 0
const exception =
error.message.search(
'VM Exception while processing transaction: revert'
) >= 0
assert(
invalidOpcode || exception || outOfGas || revert,
"Expected throw, got '" + error + "' instead"
)
return
}
assert.fail('Expected throw not received')
}
const waitForEvent = (contract, event, optTimeout) =>
new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
clearTimeout(timeout)
return reject(new Error('Timeout waiting for contractEvent'))
}, optTimeout || 5000)
const eventEmitter = contract.contract.events[event]()
eventEmitter
.on('data', data => {
eventEmitter.unsubscribe()
clearTimeout(timeout)
resolve(data)
})
.on('changed', data => {
clearTimeout()
eventEmitter.unsubscribe()
resolve(data)
})
.on('error', err => {
eventEmitter.unsubscribe()
reject(err)
})
})
const areInRange = (num1, num2, range) => {
const bigNum1 = new BN(num1.toString())
const bigNum2 = new BN(num2.toString())
const bigRange = new BN(range.toString())
if (bigNum1.equals(bigNum2)) {
return true
}
const larger = bigNum1.gt(bigNum2) ? bigNum1 : bigNum2
const smaller = bigNum1.lt(bigNum2) ? bigNum1 : bigNum2
return larger.sub(smaller).lt(bigRange)
}
const getNowInSeconds = () => new BN(Date.now()).div(1000).floor(0)
const trimBytes32Array = bytes32Array =>
bytes32Array.filter(bytes32 => bytes32 != bytes32Zero)
const getEtherBalance = address => {
return new Promise((resolve, reject) => {
web3.eth.getBalance(address, (err, res) => {
if (err) reject(err)
resolve(res)
})
})
}
const getTxInfo = txHash => {
if (typeof txHash === 'object') {
return txHash.receipt
}
return new Promise((resolve, reject) => {
web3.eth.getTransactionReceipt(txHash, (err, res) => {
if (err) {
reject(err)
}
resolve(res)
})
})
}
const sendTransaction = args => {
return new Promise(function(resolve, reject) {
web3.eth.sendTransaction(args, (err, res) => {
if (err) {
reject(err)
} else {
resolve(res)
}
})
})
}
module.exports = {
decimals18,
bigZero,
addressZero,
bytes32Zero,
gasPrice,
assertRevert,
assertJump,
assertThrow,
waitForEvent,
areInRange,
getNowInSeconds,
trimBytes32Array,
getEtherBalance,
getTxInfo,
sendTransaction
}

18
truffle-box.json Executable file
View File

@ -0,0 +1,18 @@
{
"ignore": [
"README.md",
"package-lock.json"
],
"commands": {
"Compile contracts": "truffle compile",
"Migrate contracts": "truffle migrate",
"Test contracts": "truffle test",
"Run eslint": "npm run lint",
"Run solium": "npm run solium",
"Run solidity-coverage": "npm run coverage",
"Run lint, solium, and truffle test": "npm run test"
},
"hooks": {
"post-unpack": "npm install"
}
}

56
truffle.js Normal file
View File

@ -0,0 +1,56 @@
require('dotenv').config()
const HDWalletProvider = require('truffle-hdwallet-provider')
const rinkebyWallet =
'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'
const rinkebyProvider = new HDWalletProvider(
rinkebyWallet,
'https://rinkeby.infura.io/'
)
const ropstenWallet =
'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'
const ropstenProvider = new HDWalletProvider(
ropstenWallet,
'https://ropsten.infura.io/'
)
module.exports = {
migrations_directory: './migrations',
networks: {
test: {
host: 'localhost',
port: 9545,
network_id: '*',
gas: 6.5e6,
gasPrice: 5e9,
websockets: true
},
ropsten: {
network_id: 3,
gas: 6.5e6,
gasPrice: 5e9,
provider: () => ropstenProvider
},
rinkeby: {
network_id: 4,
gas: 6.5e6,
gasPrice: 5e9,
provider: () => rinkebyProvider
}
},
solc: {
optimizer: {
enabled: true,
runs: 500
}
},
mocha: {
reporter: 'mocha-multi-reporters',
useColors: true,
enableTimeouts: false,
reporterOptions: {
configFile: './mocha-smart-contracts-config.json'
}
}
}