mirror of
https://github.com/Instadapp/smart-contract.git
synced 2024-07-29 22:08:07 +00:00
Smart contract initial setup
This commit is contained in:
parent
a82ac41f6f
commit
1994d5ce31
3
.babelrc
Normal file
3
.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": ["env","es2015", "stage-2", "stage-3"]
|
||||
}
|
37
.circleci/config.yml
Normal file
37
.circleci/config.yml
Normal 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
2
.env.sample
Normal file
|
@ -0,0 +1,2 @@
|
|||
RINKEBY_PRIVATE_KEY="df7ebe6c9601adf4e911faac9da547686e6453a11cf13264d895fc2979a6bec2"
|
||||
ROPSTEN_PRIVATE_KEY="192f175c2f5e5a9437fdbc12043404763f96ccbcd6fc32b1d61dbb61e14e6f34"
|
4
.eslintignore
Normal file
4
.eslintignore
Normal file
|
@ -0,0 +1,4 @@
|
|||
tmp/**
|
||||
build/**
|
||||
node_modules/**
|
||||
contracts/**
|
102
.eslintrc.js
Normal file
102
.eslintrc.js
Normal 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
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.sol linguist-language=Solidity
|
106
.gitignore
vendored
Normal file
106
.gitignore
vendored
Normal 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
1
.prettierignore
Normal file
|
@ -0,0 +1 @@
|
|||
package.json
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none"
|
||||
}
|
6
.solcover.js
Normal file
6
.solcover.js
Normal 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
2
.soliumignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
contracts/Migrations.sol
|
27
.soliumrc.json
Normal file
27
.soliumrc.json
Normal 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"
|
||||
}
|
||||
}
|
|
@ -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
1
CONTRIBUTING.md
Normal file
|
@ -0,0 +1 @@
|
|||
Contributors Guide
|
21
LICENSE
Normal file
21
LICENSE
Normal 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.
|
61
README.md
61
README.md
|
@ -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.
|
||||
```
|
||||
|
|
|
@ -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,14 +43,14 @@ 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
27
contracts/Migrations.sol
Normal 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
39
contracts/Ownable.sol
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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,9 +25,10 @@ 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);
|
||||
proxies[owner] = proxy;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
pragma solidity ^0.4.23;
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
|
||||
library SafeMath {
|
||||
|
@ -9,11 +9,11 @@ library SafeMath {
|
|||
}
|
||||
}
|
||||
|
||||
contract UserAuth {
|
||||
|
||||
contract UserAuth {
|
||||
using SafeMath for uint;
|
||||
using SafeMath for uint256;
|
||||
|
||||
|
||||
event LogSetOwner(address indexed owner, bool isGuardian);
|
||||
event LogSetGuardian(address indexed guardian);
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
migrations/1_initial_migration.js
Normal file
5
migrations/1_initial_migration.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
const Migrations = artifacts.require('./Migrations.sol')
|
||||
|
||||
module.exports = async deployer => {
|
||||
await deployer.deploy(Migrations)
|
||||
}
|
5
migrations/2_contract_migrations.js
Normal file
5
migrations/2_contract_migrations.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
const ownableFactory = artifacts.require('Ownable.sol')
|
||||
|
||||
module.exports = async deployer => {
|
||||
await deployer.deploy(ownableFactory)
|
||||
}
|
10
mocha-smart-contracts-config.json
Normal file
10
mocha-smart-contracts-config.json
Normal 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
68
package.json
Normal 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
31
test/Ownable.test.js
Normal 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
158
test/helpers/general.js
Normal 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
18
truffle-box.json
Executable 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
56
truffle.js
Normal 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'
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user