From 43ce8f77c4e2aaf5990632c17c60759fe8278b1a Mon Sep 17 00:00:00 2001 From: Jarrad Hope Date: Sat, 2 Feb 2019 23:04:55 +0700 Subject: [PATCH] initial commit --- .gitignore | 5 + README.md | 30 + app/components/blockchain.js | 87 +++ app/dapp.css | 57 ++ app/dapp.js | 90 +++ app/images/.gitkeep | 0 app/index.html | 12 + chains.json | 78 +++ config/blockchain.js | 99 ++++ config/contracts.js | 101 ++++ config/privatenet/genesis.json | 18 + config/privatenet/password | 1 + config/testnet/password | 1 + config/webserver.js | 6 + contracts/Meritocracy.sol | 277 +++++++++ contracts/common/Controlled.sol | 22 + contracts/token/ApproveAndCallFallBack.sol | 5 + contracts/token/ERC20Receiver.sol | 90 +++ contracts/token/ERC20Token.sol | 53 ++ contracts/token/MiniMeToken.sol | 637 +++++++++++++++++++++ contracts/token/MiniMeTokenFactory.sol | 49 ++ contracts/token/MiniMeTokenInterface.sol | 108 ++++ contracts/token/TokenController.sol | 33 ++ embark.json | 23 + package-lock.json | 212 +++++++ package.json | 17 + test/Meritocracy_spec.js | 57 ++ 27 files changed, 2168 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 app/components/blockchain.js create mode 100644 app/dapp.css create mode 100644 app/dapp.js create mode 100644 app/images/.gitkeep create mode 100644 app/index.html create mode 100644 chains.json create mode 100644 config/blockchain.js create mode 100644 config/contracts.js create mode 100644 config/privatenet/genesis.json create mode 100644 config/privatenet/password create mode 100644 config/testnet/password create mode 100644 config/webserver.js create mode 100644 contracts/Meritocracy.sol create mode 100644 contracts/common/Controlled.sol create mode 100644 contracts/token/ApproveAndCallFallBack.sol create mode 100644 contracts/token/ERC20Receiver.sol create mode 100644 contracts/token/ERC20Token.sol create mode 100644 contracts/token/MiniMeToken.sol create mode 100644 contracts/token/MiniMeTokenFactory.sol create mode 100644 contracts/token/MiniMeTokenInterface.sol create mode 100644 contracts/token/TokenController.sol create mode 100644 embark.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 test/Meritocracy_spec.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13dd958 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.embark/ +node_modules/ +dist/ +config/production/password +config/livenet/password diff --git a/README.md b/README.md new file mode 100644 index 0000000..6a3b1a9 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# Status Meritocracy + +The Status Meritocracy allows `Contributors` to show their appreciation of other `Contributors` efforts in Status. + +### Summary + +The Status Meritocracy is a SNT Reward System that allows a `Contributor` in the registry to award allocated SNT, along with praise, to other `Contributors`. + +The DApp will also display a leaderboard of `Contributors` who have been awarded the most and have partcipated the most in the Meritocracy, along with their praise + +### Roles +#### Contributor +Abilities: +- can send SNT to the Meritocracy contract, which is allocated evenly over `Contributors` +- can `award` allocated SNT to `Contributors` +- can withdraw SNT awarded to them, only when they have awarded all their allocatable SNT (or it has been forfeited by `Admins`) + +#### Admin +Abilities: +- add/remove `Contributors` +- set upper limit of `Contributor` `registry` +- forfeit all `Contributors` allocatable SNT, can only be called once a week at maximum. + +#### Owner +Abilities: +- is Admin +- can add/remove `Admins`, +- can changeOwner +- can change ERC20Token contract address +- can recover funds diff --git a/app/components/blockchain.js b/app/components/blockchain.js new file mode 100644 index 0000000..d976f83 --- /dev/null +++ b/app/components/blockchain.js @@ -0,0 +1,87 @@ +import EmbarkJS from 'Embark/EmbarkJS'; +import Meritocracy from 'Embark/contracts/Meritocracy'; +import React from 'react'; +import { Form, FormGroup, FormControl, HelpBlock, Button } from 'react-bootstrap'; + +class Blockchain extends React.Component { + + constructor(props) { + super(props); + + this.state = { + valueSet: 10, + valueGet: "", + logs: [] + } + } + + handleChange(e) { + this.setState({ valueSet: e.target.value }); + } + + checkEnter(e, func) { + if (e.key !== 'Enter') { + return; + } + e.preventDefault(); + func.apply(this, [e]); + } + + setValue(e) { + e.preventDefault(); + + var value = parseInt(this.state.valueSet, 10); + + SimpleStorage.methods.set(value).send(); + this._addToLog("SimpleStorage.methods.set(value).send()"); + } + + getValue(e) { + e.preventDefault(); + + SimpleStorage.methods.get().call().then(_value => this.setState({ valueGet: _value })); + this._addToLog("SimpleStorage.methods.get(console.log)"); + } + + _addToLog(txt) { + this.state.logs.push(txt); + this.setState({ logs: this.state.logs }); + } + + render() { + return ( +

1. Set the value in the blockchain

+
this.checkEnter(e, this.setValue)}> + + this.handleChange(e)}/> + + Once you set the value, the transaction will need to be mined and then the value will be updated + on the blockchain. + +
+ +

2. Get the current value

+
+ + current value is {this.state.valueGet} + + Click the button to get the current value. The initial value is 100. + +
+ +

3. Contract Calls

+

Javascript calls being made:

+
+ { + this.state.logs.map((item, i) =>

{item}

) + } +
+
+ ); + } +} + +export default Blockchain; diff --git a/app/dapp.css b/app/dapp.css new file mode 100644 index 0000000..4ec8faa --- /dev/null +++ b/app/dapp.css @@ -0,0 +1,57 @@ + +div { + margin: 15px; +} + +.logs { + background-color: black; + font-size: 14px; + color: white; + font-weight: bold; + padding: 10px; + border-radius: 8px; +} + +.tab-content { + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + border-bottom: 1px solid #ddd; + padding: 10px; + margin: 0px; +} + +.nav-tabs { + margin-bottom: 0; +} + +.status-offline { + vertical-align: middle; + margin-left: 5px; + margin-top: 4px; + width: 12px; + height: 12px; + background: red; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + border-radius: 10px; +} + +.status-online { + vertical-align: middle; + margin-left: 5px; + margin-top: 4px; + width: 12px; + height: 12px; + background: mediumseagreen; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + border-radius: 10px; +} + +input.form-control { + margin-right: 5px; +} + +.alert-result { + margin-left: 0; +} diff --git a/app/dapp.js b/app/dapp.js new file mode 100644 index 0000000..8b9e964 --- /dev/null +++ b/app/dapp.js @@ -0,0 +1,90 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import {Tabs, Tab} from 'react-bootstrap'; + +import EmbarkJS from 'Embark/EmbarkJS'; +import Blockchain from './components/blockchain'; + +import './dapp.css'; + +class App extends React.Component { + + constructor(props) { + super(props); + + this.handleSelect = this.handleSelect.bind(this); + + this.state = { + error: null, + activeKey: 1, + whisperEnabled: false, + storageEnabled: false, + blockchainEnabled: false + }; + } + + componentDidMount() { + EmbarkJS.onReady((err) => { + this.setState({blockchainEnabled: true}); + if (err) { + // If err is not null then it means something went wrong connecting to ethereum + // you can use this to ask the user to enable metamask for e.g + return this.setState({error: err.message || err}); + } + + EmbarkJS.Messages.Providers.whisper.getWhisperVersion((err, _version) => { + if (err) { + return console.log(err); + } + this.setState({whisperEnabled: true}); + }); + + EmbarkJS.Storage.isAvailable().then((result) => { + this.setState({storageEnabled: result}); + }).catch(() => { + this.setState({storageEnabled: false}); + }); + }); + } + + _renderStatus(title, available) { + let className = available ? 'pull-right status-online' : 'pull-right status-offline'; + return + {title} + + ; + } + + handleSelect(key) { + this.setState({ activeKey: key }); + } + + render() { + const ensEnabled = EmbarkJS.Names.currentNameSystems && EmbarkJS.Names.isAvailable(); + if (this.state.error) { + return (
+
Something went wrong connecting to ethereum. Please make sure you have a node running or are using metamask to connect to the ethereum network:
+
{this.state.error}
+
); + } + return (
+

Embark - Usage Example

+ + + + + + + + + + + + + + +
); + } +} + +ReactDOM.render(, document.getElementById('app')); diff --git a/app/images/.gitkeep b/app/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..194a628 --- /dev/null +++ b/app/index.html @@ -0,0 +1,12 @@ + + + Embark - SimpleStorage Demo + + + + +
+
+ + + diff --git a/chains.json b/chains.json new file mode 100644 index 0000000..44b9cb7 --- /dev/null +++ b/chains.json @@ -0,0 +1,78 @@ +{ + "0x0399343ea5cbf9b93edc98e29ddf86d385c0997b8378ee60afc11cd14484edfb": { + "contracts": { + "0xb84d125645e94e6329e6494db63830d1c68a719c328036dce55337f79a512e27": { + "name": "SimpleStorage", + "address": "0x0139F5bD92760b0535A3BF503ff20755B56120Fe" + }, + "0x3043b04ad856d169c8f0b0509c0bc63192dc7edd92d6933c58708298a0e381be": { + "name": "ENSRegistry", + "address": "0x56c916c5229e7fc0dcc2b8dffa50A0BB7C1803D0" + }, + "0x4b412c3f4bab69e90e9938e89bf0b74320f421fc0725592bf465068b26201402": { + "name": "Resolver", + "address": "0x545CFAC6f642b8752BE942a8f4e6cD41fFd3AeEe" + }, + "0xe6d2b6a71cbbd055e47b30467a9d898344c3fad1485e895eded0244453841cfe": { + "name": "FIFSRegistrar", + "address": "0x988E079CC0D04Ed01c2c5dDca09B25DDD1F4aFE4" + }, + "0xc920172104d0372dfa1375d4c9ef05ae15569b94b88fd4b0d5a834965dc7420b": { + "name": "SimpleStorage", + "address": "0x38a6Bb08E64c2EceB89aC0d0635bD543AD4e60B8" + }, + "0xfb4295b67b9ca6e0992ff331dbd64c98b6cdb4bd896e033995691356b504077c": { + "name": "ERC20Receiver", + "address": "0xd3C99B684b4ff3E9eC0eEb8AC046Dfe1e1b309a4" + }, + "0x6ef111d7ff114790dccb0eb991ffc66c0717516aa3cebfd356736581b75b0402": { + "name": "MiniMeTokenFactory", + "address": "0x5022174e5cC5264FBD67BC3AB055ab6109158D62" + } + } + }, + "0xc855132a1ab94dc31b77189ca76fe861dede5013d46445d9b5ad8a6da5ffb508": { + "contracts": { + "0xfb4295b67b9ca6e0992ff331dbd64c98b6cdb4bd896e033995691356b504077c": { + "name": "ERC20Receiver", + "address": "0x44bbA839B4b9B4f80Bfe016E9f9BAF7e3266c48b" + }, + "0x6ef111d7ff114790dccb0eb991ffc66c0717516aa3cebfd356736581b75b0402": { + "name": "MiniMeTokenFactory", + "address": "0x4D155a4c68196092907E6C1CD06F08D4F8D0A4e4" + }, + "0xc6744ff8ae4483600e95a4f52780424cbf1719b99009b3ed3397c42ab683350d": { + "name": "SNT", + "address": "0x32dbB8258C4F19c72770C8f926d169e0Dff656f7" + }, + "0xfd7ba1d8638b477661cbd597414b0b4a5b6e277d2fc2947e08e417fc2453becb": { + "name": "Meritocracy", + "address": "0x868C59B9FF6361Fe66d47a907BbF670F86efE455" + }, + "0x91a7635ace87d0dd04a63f8cd62203bd1da3258132f899dcc9e26ab3012f616d": { + "name": "Meritocracy", + "address": "0x82F6d95A2CbbaCe7CE7BD37e036443276Cc1c3A3" + } + } + }, + "0x9ed7a625c779501885ef8376379ed5492745c30cfcd919bed3cc3cc4e01d522f": { + "contracts": { + "0xfb4295b67b9ca6e0992ff331dbd64c98b6cdb4bd896e033995691356b504077c": { + "name": "ERC20Receiver", + "address": "0x135d5ec78e34F71451594a5755513fA440A361DB" + }, + "0x6ef111d7ff114790dccb0eb991ffc66c0717516aa3cebfd356736581b75b0402": { + "name": "MiniMeTokenFactory", + "address": "0x000F6f673b33Be7d4Ce7256654205C7482686fBf" + }, + "0x7a531eedff91102db0ffd706a0021b770fba77517d5ab6cc4ae19c6d22c287e7": { + "name": "SNT", + "address": "0x97b991b76Eb93D52808715C52F5bF0a6EA81167B" + }, + "0x380d1fdd01cac4effd59d4a2b82246f9971f65ce4b0abbd0c8158a84375cc36a": { + "name": "Meritocracy", + "address": "0x755f52Afe8166011B978aE142465c25b9D3F9ab3" + } + } + } +} diff --git a/config/blockchain.js b/config/blockchain.js new file mode 100644 index 0000000..cd703db --- /dev/null +++ b/config/blockchain.js @@ -0,0 +1,99 @@ +module.exports = { + // applies to all environments + default: { + enabled: true, + rpcHost: "localhost", // HTTP-RPC server listening interface (default: "localhost") + rpcPort: 8545, // HTTP-RPC server listening port (default: 8545) + rpcCorsDomain: "auto", // Comma separated list of domains from which to accept cross origin requests (browser enforced) + // When set to "auto", Embark will automatically set the cors to the address of the webserver + wsRPC: true, // Enable the WS-RPC server + wsOrigins: "auto", // Origins from which to accept websockets requests + // When set to "auto", Embark will automatically set the cors to the address of the webserver + wsHost: "localhost", // WS-RPC server listening interface (default: "localhost") + wsPort: 8546 // WS-RPC server listening port (default: 8546) + }, + + // default environment, merges with the settings in default + // assumed to be the intended environment by `embark run` and `embark blockchain` + development: { + networkType: "custom", // Can be: testnet, rinkeby, livenet or custom, in which case, it will use the specified networkId + networkId: "1337", // Network id used when networkType is custom + isDev: true, // Uses and ephemeral proof-of-authority network with a pre-funded developer account, mining enabled + datadir: ".embark/development/datadir", // Data directory for the databases and keystore + mineWhenNeeded: true, // Uses our custom script (if isDev is false) to mine only when needed + nodiscover: true, // Disables the peer discovery mechanism (manual peer addition) + maxpeers: 0, // Maximum number of network peers (network disabled if set to 0) (default: 25) + proxy: true, // Proxy is used to present meaningful information about transactions + targetGasLimit: 8000000, // Target gas limit sets the artificial target gas floor for the blocks to mine + simulatorMnemonic: "example exile argue silk regular smile grass bomb merge arm assist farm", // Mnemonic used by the simulator to generate a wallet + simulatorBlocktime: 0, // Specify blockTime in seconds for automatic mining. Default is 0 and no auto-mining. + account: { + // numAccounts: 3, // When specified, creates accounts for use in the dapp. This option only works in the development environment, and can be used as a quick start option that bypasses the need for MetaMask in development. These accounts are unlocked and funded with the below settings. + // password: "config/development/password", // Password for the created accounts (as specified in the `numAccounts` setting). If `mineWhenNeeded` is enabled (and isDev is not), this password is used to create a development account controlled by the node. + // balance: "5 ether" // Balance to be given to the created accounts (as specified in the `numAccounts` setting) + } + }, + + // merges with the settings in default + // used with "embark run privatenet" and/or "embark blockchain privatenet" + privatenet: { + networkType: "custom", + networkId: "1337", + isDev: false, + datadir: ".embark/privatenet/datadir", + // -- mineWhenNeeded -- + // This options is only valid when isDev is false. + // Enabling this option uses our custom script to mine only when needed. + // Embark creates a development account for you (using `geth account new`) and funds the account. This account can be used for + // development (and even imported in to MetaMask). To enable correct usage, a password for this account must be specified + // in the `account > password` setting below. + // NOTE: once `mineWhenNeeded` is enabled, you must run an `embark reset` on your dApp before running + // `embark blockchain` or `embark run` for the first time. + mineWhenNeeded: true, + // -- genesisBlock -- + // This option is only valid when mineWhenNeeded is true (which is only valid if isDev is false). + // When enabled, geth uses POW to mine transactions as it would normally, instead of using POA as it does in --dev mode. + // On the first `embark blockchain or embark run` after this option is enabled, geth will create a new chain with a + // genesis block, which can be configured using the `genesisBlock` configuration option below. + genesisBlock: "config/privatenet/genesis.json", // Genesis block to initiate on first creation of a development node + nodiscover: true, + maxpeers: 0, + proxy: true, + account: { + // "address": "", // When specified, uses that address instead of the default one for the network + password: "config/privatenet/password" // Password to unlock the account + }, + targetGasLimit: 8000000, + wsHost: "localhost", + wsPort: 8546, + simulatorMnemonic: "example exile argue silk regular smile grass bomb merge arm assist farm", + simulatorBlocktime: 0 + }, + + // merges with the settings in default + // used with "embark run testnet" and/or "embark blockchain testnet" + testnet: { + networkType: "testnet", + syncMode: "light", + account: { + password: "config/testnet/password" + } + }, + + // merges with the settings in default + // used with "embark run livenet" and/or "embark blockchain livenet" + livenet: { + networkType: "livenet", + syncMode: "light", + rpcCorsDomain: "http://localhost:8000", + wsOrigins: "http://localhost:8000", + account: { + password: "config/livenet/password" + } + }, + + // you can name an environment with specific settings and then specify with + // "embark run custom_name" or "embark blockchain custom_name" + //custom_name: { + //} +}; diff --git a/config/contracts.js b/config/contracts.js new file mode 100644 index 0000000..cd2b0ff --- /dev/null +++ b/config/contracts.js @@ -0,0 +1,101 @@ +module.exports = { + // default applies to all environments + default: { + // Blockchain node to deploy the contracts + deployment: { + host: "localhost", // Host of the blockchain node + port: 8545, // Port of the blockchain node + type: "rpc" // Type of connection (ws or rpc), + // Accounts to use instead of the default account to populate your wallet + /*,accounts: [ + { + privateKey: "your_private_key", + balance: "5 ether" // You can set the balance of the account in the dev environment + // Balances are in Wei, but you can specify the unit with its name + }, + { + privateKeyFile: "path/to/file", // Either a keystore or a list of keys, separated by , or ; + password: "passwordForTheKeystore" // Needed to decrypt the keystore file + }, + { + mnemonic: "12 word mnemonic", + addressIndex: "0", // Optionnal. The index to start getting the address + numAddresses: "1", // Optionnal. The number of addresses to get + hdpath: "m/44'/60'/0'/0/" // Optionnal. HD derivation path + } + ]*/ + }, + // order of connections the dapp should connect to + dappConnection: [ + "$WEB3", // uses pre existing web3 object if available (e.g in Mist) + "ws://localhost:8546", + "http://localhost:8545" + ], + gas: "auto", + contracts: { + "MiniMeToken": { "deploy": false }, + "MiniMeTokenFactory": { + + }, + "SNT": { + "instanceOf": "MiniMeToken", + "args": [ + "$MiniMeTokenFactory", + "0x0000000000000000000000000000000000000000", + 0, + "TestMiniMeToken", + 18, + "STT", + true + ] + }, + "Meritocracy": { + "args": [ "$SNT", 66] + } + } + }, + + // default environment, merges with the settings in default + // assumed to be the intended environment by `embark run` + development: { + dappConnection: [ + "ws://localhost:8546", + "http://localhost:8545", + "$WEB3" // uses pre existing web3 object if available (e.g in Mist) + ], + deployment: { + // The order here corresponds to the order of `web3.eth.getAccounts`, so the first one is the `defaultAccount` + accounts: [ + { + nodeAccounts: true + }, + { + mnemonic: "foster gesture flock merge beach plate dish view friend leave drink valley shield list enemy", + balance: "5 ether", + numAddresses: "10" + } + ] + }, +"afterDeploy": ["SNT.methods.generateTokens('$accounts[0]', '100000000000000000000').send()"] + }, + + // merges with the settings in default + // used with "embark run privatenet" + privatenet: { + }, + + // merges with the settings in default + // used with "embark run testnet" + testnet: { + }, + + // merges with the settings in default + // used with "embark run livenet" + livenet: { + }, + + // you can name an environment with specific settings and then specify with + // "embark run custom_name" or "embark blockchain custom_name" + //custom_name: { + //} +}; diff --git a/config/privatenet/genesis.json b/config/privatenet/genesis.json new file mode 100644 index 0000000..f018c65 --- /dev/null +++ b/config/privatenet/genesis.json @@ -0,0 +1,18 @@ +{ + "config": { + "homesteadBlock": 0, + "byzantiumBlock": 0, + "daoForkSupport": true + }, + "nonce": "0x0000000000000042", + "difficulty": "0x0", + "alloc": { + "0x3333333333333333333333333333333333333333": {"balance": "15000000000000000000"} + }, + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x3333333333333333333333333333333333333333", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x7a1200" +} diff --git a/config/privatenet/password b/config/privatenet/password new file mode 100644 index 0000000..94a2f79 --- /dev/null +++ b/config/privatenet/password @@ -0,0 +1 @@ +dev_password diff --git a/config/testnet/password b/config/testnet/password new file mode 100644 index 0000000..b5f1eed --- /dev/null +++ b/config/testnet/password @@ -0,0 +1 @@ +test_password diff --git a/config/webserver.js b/config/webserver.js new file mode 100644 index 0000000..2f04d39 --- /dev/null +++ b/config/webserver.js @@ -0,0 +1,6 @@ +module.exports = { + enabled: true, + host: "localhost", + openBrowser: true, + port: 8000 +}; diff --git a/contracts/Meritocracy.sol b/contracts/Meritocracy.sol new file mode 100644 index 0000000..45cc512 --- /dev/null +++ b/contracts/Meritocracy.sol @@ -0,0 +1,277 @@ +pragma solidity ^0.5.0; + +/* +Goals: +- Anyone can allocate Token to contributor base + + +Future Goals: +- remove admins necessity +- encourage contributors to allocate + +Notes: + +Dapp: +- show tokens to allocate +- allocate token to person with praise +- leaderboard, showing amount totalReceived and totalForfeited and amount, praises https://codepen.io/lewismcarey/pen/GJZVoG +- allows you to send SNT to meritocracy +- add/remove contributor +- add/remove adminstrator + + +and Meritocracy + +Extension that allows you to : + - =give a contributor status + - withdraw snt +*/ + +import "token/ERC20Token.sol"; + +contract Meritocracy { + + struct Status { + address author; + string praise; + uint256 amount; + uint256 time; // block.timestamp + } + + struct Contributor { + address addr; + uint256 allocation; // Amount they can send to other contributors, and amount they forfeit, when forfeit just zero this out and leave Token in contract, Owner can use escape to receive it back + uint256 totalForfeited; // Allocations they've burnt, can be used to show non-active players. + uint256 totalReceived; + uint256 received; // Ignore amounts in Status struct, and use this as source of truth, can withdraw at any time + // bool inPot; // Require Contributor WARN: praiseed because there's some edge cases not dealt with + Status[] status; + } + + address public token; // token contract + address payable public owner; // contract owner + uint256 public lastForfeit; // timestamp to block admins calling forfeitAllocations too quickly + address[] public registry; // array of contributor addresses + uint256 public maxContributors; // Dynamic finite limit on registry. + mapping(address => bool) admins; + mapping(address => Contributor) contributors; + + // Open Functions ---------------------------------------------------------------------------------------- + + // Access a contributors award history for leaderboard + // function status (address _contributor) external view returns (Status[] status) { + // // TODO can return array of struct ?? + // // i + // status = contributors[_contributor].status; + // } + + // Split amount over each contributor in registry, anyone can contribute. + function allocate(uint256 _amount) external { + // Locals + uint256 individualAmount; + Contributor memory cAllocator = contributors[msg.sender]; + // Requirements + require(cAllocator.addr == msg.sender); // is sender a Contributor? + // require(ERC20Token(token).transferFrom(msg.sender, address(this), _amount)); // TODO fix this, check balance Contributor has funds to allocate + // Body + // cAllocator.inPot = true; + individualAmount = _amount / registry.length; + for (uint256 i = 0; i < registry.length; i++) { + contributors[registry[i]].allocation += individualAmount; + } + } + + // Contributor Functions ------------------------------------------------------------------------------- + + // Allows a contributor to withdraw their received Token, when their allocation is 0 + function withdraw() external { + // Locals + Contributor storage cReceiver = contributors[msg.sender]; + // Requirements + require(cReceiver.addr == msg.sender); //is sender a Contributor? + require(cReceiver.received > 0); // Contributor has received some tokens + require(cReceiver.allocation == 0); // Contributor must allocate all Token (or have Token burnt) before they can withdraw. + // require(cReceiver.inPot); // Contributor has put some tokens into the pot + // Body + uint256 r = cReceiver.received; + cReceiver.received = 0; + // cReceiver.inPot = false; + ERC20Token(token).transferFrom(address(this), cReceiver.addr, r); + } + + // Allow Contributors to award allocated tokens to other Contributors + function award(address _contributor, uint256 _amount, string calldata _praise) external { + // Locals + Contributor storage cSender = contributors[msg.sender]; + Contributor storage cReceiver = contributors[_contributor]; + // Requirements + require(cSender.addr == msg.sender); // Ensure Contributors both exist, and isn't the same address + require(cReceiver.addr == _contributor); + require(cSender.addr != cReceiver.addr); // cannot send to self + require(cSender.allocation >= _amount); // Ensure Sender has enough tokens to allocate + // Body + cSender.allocation -= _amount; // burn is not adjusted, which is done only in forfeitAllocations + cReceiver.received += _amount; + cReceiver.totalReceived += _amount; + + Status memory s = Status({ + author: cSender.addr, + praise: _praise, + amount: _amount, + time: block.timestamp + }); + + cReceiver.status.push(s); // Record the history + } + + // Admin Functions ------------------------------------------------------------------------------------- + + // Add Contributor to Registry + function addContributor(address _contributor) public { + // Requirements + require(admins[msg.sender]); + require(registry.length + 1 <= maxContributors); + require(contributors[_contributor].addr != _contributor); // WARN: check if contributor already exists? + // Body + Contributor storage c = contributors[_contributor]; + c.addr = _contributor; + registry.push(_contributor); + } + + // Add Multiple Contributors to Tegistry in one tx + function addContributors(address[] calldata _newContributors ) external { + // Requirements + require(registry.length + _newContributors.length <= maxContributors); + // Body + for (uint256 i = 0; i < _newContributors.length; i++) { + addContributor(_newContributors[i]); + } + } + + // Remove Contributor from Registry + // Note: Should not be easy to remove multiple contributors in one tx + function removeContributor(address _contributor) external { + // Requirements + require(admins[msg.sender]); + // Body + // Find id of contributor address + uint256 idx = 0; + for (uint256 i = 0; i < registry.length; i++) { // should never be longer than maxContributors, see addContributor + if (registry[i] == _contributor) { + idx = i; + break; + } + } + + address c = registry[idx]; + // Swap & Pop! + registry[idx] = registry[registry.length - 1]; + registry.pop(); + delete contributors[c]; // TODO check if this works + } + + // Implictly sets a finite limit to registry length + function setMaxContributors(uint256 _maxContributors) external { + // Requirements + require(admins[msg.sender]); + require(_maxContributors > registry.length); // have to removeContributor first + // Body + maxContributors = _maxContributors; + } + + // Zero-out allocations for contributors, minimum once a week, if allocation still exists, add to burn + function forfeitAllocations() public { + // Requirements + require(admins[msg.sender]); + require(block.timestamp >= lastForfeit + 1 weeks); // prevents multiple admins accidently calling too quickly. + // Body + lastForfeit = block.timestamp; + + for (uint256 i = 0; i < registry.length; i++) { // should never be longer than maxContributors, see addContributor + Contributor storage c = contributors[registry[i]]; + c.totalForfeited += c.allocation; // Shaaaaame! + c.allocation = 0; + // cReceiver.inPot = false; // Contributor has to put tokens into next round + } + } + + // Owner Functions ------------------------------------------------------------------------------------- + + // Set Admin flag for address to true + function addAdmin(address _admin) public { + // Requirements + require(msg.sender == owner); + // Body + admins[_admin] = true; + } + + // Set Admin flag for address to false + function removeAdmin(address _admin) public { + // Requirements + require(msg.sender == owner); + // Body + delete admins[_admin]; + } + + // Change owner address, ideally to a management contract or multisig + function changeOwner(address payable _owner) external { + // Requirements + require(msg.sender == owner); + // Body + removeAdmin(owner); + addAdmin(_owner); + owner = _owner; + } + + // Change Token address + // WARN: call escape first, or escape(token); + function changeToken(address _token) external { + // Locals + uint256 r; + // Requirements + require(msg.sender == owner); + // Body + // Zero-out allocation and received, send out received tokens before token switch. + for (uint256 i = 0; i < registry.length; i++) { + Contributor storage c = contributors[registry[i]]; + r = c.received; + c.received = 0; + c.allocation = 0; + // WARN: Should totalReceived and totalForfeited be zeroed-out? + ERC20Token(token).transferFrom(address(this), c.addr, r); + } + lastForfeit = block.timestamp; + token = _token; + } + + // Failsafe, Owner can escape hatch all Tokens and ETH from Contract. + function escape() public { + // Requirements + require(msg.sender == owner); + // Body + ERC20Token(token).transferFrom(address(this), owner, ERC20Token(token).balanceOf(address(this))); + address(owner).transfer(address(this).balance); + } + + // Overloaded failsafe function, recourse incase changeToken is called before escape and funds are in a different token + // Don't want to require in changeToken incase bad behaviour of ERC20 token + function escape(address _token) external { + // Requirements + require(msg.sender == owner); + // Body + ERC20Token(_token).transferFrom(address(this), owner, ERC20Token(_token).balanceOf(address(this))); + escape(); + } + + // Set Token address and initial maxContributors + constructor(address _token, uint256 _maxContributors) public { + // Body + owner = msg.sender; + addAdmin(owner); + lastForfeit = block.timestamp; + token = _token; + maxContributors= _maxContributors; + } + + // function() public { throw; } // TODO Probably not needed? +} \ No newline at end of file diff --git a/contracts/common/Controlled.sol b/contracts/common/Controlled.sol new file mode 100644 index 0000000..219de3b --- /dev/null +++ b/contracts/common/Controlled.sol @@ -0,0 +1,22 @@ +pragma solidity >=0.5.0 <0.6.0; + +contract Controlled { + /// @notice The address of the controller is the only address that can call + /// a function with this modifier + modifier onlyController { + require(msg.sender == controller, "Unauthorized"); + _; + } + + address payable public controller; + + constructor() internal { + controller = msg.sender; + } + + /// @notice Changes the controller of the contract + /// @param _newController The new controller of the contract + function changeController(address payable _newController) public onlyController { + controller = _newController; + } +} diff --git a/contracts/token/ApproveAndCallFallBack.sol b/contracts/token/ApproveAndCallFallBack.sol new file mode 100644 index 0000000..3209516 --- /dev/null +++ b/contracts/token/ApproveAndCallFallBack.sol @@ -0,0 +1,5 @@ +pragma solidity ^0.5.0; + +contract ApproveAndCallFallBack { + function receiveApproval(address from, uint256 _amount, address _token, bytes memory _data) public; +} diff --git a/contracts/token/ERC20Receiver.sol b/contracts/token/ERC20Receiver.sol new file mode 100644 index 0000000..98166b7 --- /dev/null +++ b/contracts/token/ERC20Receiver.sol @@ -0,0 +1,90 @@ +pragma solidity ^0.5.0; + +import "./ERC20Token.sol"; + +contract ERC20Receiver { + + event TokenDeposited(address indexed token, address indexed sender, uint256 amount); + event TokenWithdrawn(address indexed token, address indexed sender, uint256 amount); + + mapping (address => mapping(address => uint256)) tokenBalances; + + constructor() public { + + } + + function depositToken( + ERC20Token _token + ) + external + { + _depositToken( + msg.sender, + _token, + _token.allowance( + msg.sender, + address(this) + ) + ); + } + + function withdrawToken( + ERC20Token _token, + uint256 _amount + ) + external + { + _withdrawToken(msg.sender, _token, _amount); + } + + function depositToken( + ERC20Token _token, + uint256 _amount + ) + external + { + require(_token.allowance(msg.sender, address(this)) >= _amount, "Bad argument"); + _depositToken(msg.sender, _token, _amount); + } + + function tokenBalanceOf( + ERC20Token _token, + address _from + ) + external + view + returns(uint256 fromTokenBalance) + { + return tokenBalances[address(_token)][_from]; + } + + function _depositToken( + address _from, + ERC20Token _token, + uint256 _amount + ) + private + { + require(_amount != 0, "Amount is required to be more than zero"); + if (_token.transferFrom(_from, address(this), _amount)) { + tokenBalances[address(_token)][_from] += _amount; + emit TokenDeposited(address(_token), _from, _amount); + } + } + + function _withdrawToken( + address _from, + ERC20Token _token, + uint256 _amount + ) + private + { + require(_amount > 0, "Bad argument"); + require(tokenBalances[address(_token)][_from] >= _amount, "Insufficient funds"); + tokenBalances[address(_token)][_from] -= _amount; + require(_token.transfer(_from, _amount), "Transfer fail"); + emit TokenWithdrawn(address(_token), _from, _amount); + } + + +} \ No newline at end of file diff --git a/contracts/token/ERC20Token.sol b/contracts/token/ERC20Token.sol new file mode 100644 index 0000000..ef207b6 --- /dev/null +++ b/contracts/token/ERC20Token.sol @@ -0,0 +1,53 @@ +pragma solidity ^0.5.0; + +// Abstract contract for the full ERC 20 Token standard +// https://github.com/ethereum/EIPs/issues/20 + +interface ERC20Token { + + /** + * @notice send `_value` token to `_to` from `msg.sender` + * @param _to The address of the recipient + * @param _value The amount of token to be transferred + * @return Whether the transfer was successful or not + */ + function transfer(address _to, uint256 _value) external returns (bool success); + + /** + * @notice `msg.sender` approves `_spender` to spend `_value` tokens + * @param _spender The address of the account able to transfer the tokens + * @param _value The amount of tokens to be approved for transfer + * @return Whether the approval was successful or not + */ + function approve(address _spender, uint256 _value) external returns (bool success); + + /** + * @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` + * @param _from The address of the sender + * @param _to The address of the recipient + * @param _value The amount of token to be transferred + * @return Whether the transfer was successful or not + */ + function transferFrom(address _from, address _to, uint256 _value) external returns (bool success); + + /** + * @param _owner The address from which the balance will be retrieved + * @return The balance + */ + function balanceOf(address _owner) external view returns (uint256 balance); + + /** + * @param _owner The address of the account owning tokens + * @param _spender The address of the account able to transfer the tokens + * @return Amount of remaining tokens allowed to spent + */ + function allowance(address _owner, address _spender) external view returns (uint256 remaining); + + /** + * @notice return total supply of tokens + */ + function totalSupply() external view returns (uint256 supply); + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + event Approval(address indexed _owner, address indexed _spender, uint256 _value); +} \ No newline at end of file diff --git a/contracts/token/MiniMeToken.sol b/contracts/token/MiniMeToken.sol new file mode 100644 index 0000000..db17012 --- /dev/null +++ b/contracts/token/MiniMeToken.sol @@ -0,0 +1,637 @@ +pragma solidity ^0.5.0; + +/* + Copyright 2016, Jordi Baylina + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +/** + * @title MiniMeToken Contract + * @author Jordi Baylina + * @dev This token contract's goal is to make it easy for anyone to clone this + * token using the token distribution at a given block, this will allow DAO's + * and DApps to upgrade their features in a decentralized manner without + * affecting the original token + * @dev It is ERC20 compliant, but still needs to under go further testing. + */ + +import "../common/Controlled.sol"; +import "./TokenController.sol"; +import "./ApproveAndCallFallBack.sol"; +import "./MiniMeTokenInterface.sol"; +import "./MiniMeTokenFactory.sol"; + +/** + * @dev The actual token contract, the default controller is the msg.sender + * that deploys the contract, so usually this token will be deployed by a + * token controller contract, which Giveth will call a "Campaign" + */ +contract MiniMeToken is MiniMeTokenInterface, Controlled { + + string public name; //The Token's name: e.g. DigixDAO Tokens + uint8 public decimals; //Number of decimals of the smallest unit + string public symbol; //An identifier: e.g. REP + string public version = "MMT_0.1"; //An arbitrary versioning scheme + + /** + * @dev `Checkpoint` is the structure that attaches a block number to a + * given value, the block number attached is the one that last changed the + * value + */ + struct Checkpoint { + + // `fromBlock` is the block number that the value was generated from + uint128 fromBlock; + + // `value` is the amount of tokens at a specific block number + uint128 value; + } + + // `parentToken` is the Token address that was cloned to produce this token; + // it will be 0x0 for a token that was not cloned + MiniMeToken public parentToken; + + // `parentSnapShotBlock` is the block number from the Parent Token that was + // used to determine the initial distribution of the Clone Token + uint public parentSnapShotBlock; + + // `creationBlock` is the block number that the Clone Token was created + uint public creationBlock; + + // `balances` is the map that tracks the balance of each address, in this + // contract when the balance changes the block number that the change + // occurred is also included in the map + mapping (address => Checkpoint[]) balances; + + // `allowed` tracks any extra transfer rights as in all ERC20 tokens + mapping (address => mapping (address => uint256)) allowed; + + // Tracks the history of the `totalSupply` of the token + Checkpoint[] totalSupplyHistory; + + // Flag that determines if the token is transferable or not. + bool public transfersEnabled; + + // The factory used to create new clone tokens + MiniMeTokenFactory public tokenFactory; + +//////////////// +// Constructor +//////////////// + + /** + * @notice Constructor to create a MiniMeToken + * @param _tokenFactory The address of the MiniMeTokenFactory contract that + * will create the Clone token contracts, the token factory needs to be + * deployed first + * @param _parentToken Address of the parent token, set to 0x0 if it is a + * new token + * @param _parentSnapShotBlock Block of the parent token that will + * determine the initial distribution of the clone token, set to 0 if it + * is a new token + * @param _tokenName Name of the new token + * @param _decimalUnits Number of decimals of the new token + * @param _tokenSymbol Token Symbol for the new token + * @param _transfersEnabled If true, tokens will be able to be transferred + */ + constructor( + address _tokenFactory, + address _parentToken, + uint _parentSnapShotBlock, + string memory _tokenName, + uint8 _decimalUnits, + string memory _tokenSymbol, + bool _transfersEnabled + ) + public + { + tokenFactory = MiniMeTokenFactory(_tokenFactory); + name = _tokenName; // Set the name + decimals = _decimalUnits; // Set the decimals + symbol = _tokenSymbol; // Set the symbol + parentToken = MiniMeToken(address(uint160(_parentToken))); + parentSnapShotBlock = _parentSnapShotBlock; + transfersEnabled = _transfersEnabled; + creationBlock = block.number; + } + + +/////////////////// +// ERC20 Methods +/////////////////// + + /** + * @notice Send `_amount` tokens to `_to` from `msg.sender` + * @param _to The address of the recipient + * @param _amount The amount of tokens to be transferred + * @return Whether the transfer was successful or not + */ + function transfer(address _to, uint256 _amount) public returns (bool success) { + require(transfersEnabled); + return doTransfer(msg.sender, _to, _amount); + } + + /** + * @notice Send `_amount` tokens to `_to` from `_from` on the condition it + * is approved by `_from` + * @param _from The address holding the tokens being transferred + * @param _to The address of the recipient + * @param _amount The amount of tokens to be transferred + * @return True if the transfer was successful + */ + function transferFrom( + address _from, + address _to, + uint256 _amount + ) + public + returns (bool success) + { + + // The controller of this contract can move tokens around at will, + // this is important to recognize! Confirm that you trust the + // controller of this contract, which in most situations should be + // another open source smart contract or 0x0 + if (msg.sender != controller) { + require(transfersEnabled); + + // The standard ERC 20 transferFrom functionality + if (allowed[_from][msg.sender] < _amount) { + return false; + } + allowed[_from][msg.sender] -= _amount; + } + return doTransfer(_from, _to, _amount); + } + + /** + * @dev This is the actual transfer function in the token contract, it can + * only be called by other functions in this contract. + * @param _from The address holding the tokens being transferred + * @param _to The address of the recipient + * @param _amount The amount of tokens to be transferred + * @return True if the transfer was successful + */ + function doTransfer( + address _from, + address _to, + uint _amount + ) + internal + returns(bool) + { + + if (_amount == 0) { + return true; + } + + require(parentSnapShotBlock < block.number); + + // Do not allow transfer to 0x0 or the token contract itself + require((_to != address(0)) && (_to != address(this))); + + // If the amount being transfered is more than the balance of the + // account the transfer returns false + uint256 previousBalanceFrom = balanceOfAt(_from, block.number); + if (previousBalanceFrom < _amount) { + return false; + } + + // Alerts the token controller of the transfer + if (isContract(controller)) { + require(TokenController(controller).onTransfer(_from, _to, _amount)); + } + + // First update the balance array with the new value for the address + // sending the tokens + updateValueAtNow(balances[_from], previousBalanceFrom - _amount); + + // Then update the balance array with the new value for the address + // receiving the tokens + uint256 previousBalanceTo = balanceOfAt(_to, block.number); + require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow + updateValueAtNow(balances[_to], previousBalanceTo + _amount); + + // An event to make the transfer easy to find on the blockchain + emit Transfer(_from, _to, _amount); + + return true; + } + + function doApprove( + address _from, + address _spender, + uint256 _amount + ) + internal + returns (bool) + { + require(transfersEnabled); + + // To change the approve amount you first have to reduce the addresses` + // allowance to zero by calling `approve(_spender,0)` if it is not + // already 0 to mitigate the race condition described here: + // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + require((_amount == 0) || (allowed[_from][_spender] == 0)); + + // Alerts the token controller of the approve function call + if (isContract(controller)) { + require(TokenController(controller).onApprove(_from, _spender, _amount)); + } + + allowed[_from][_spender] = _amount; + emit Approval(_from, _spender, _amount); + return true; + } + + /** + * @param _owner The address that's balance is being requested + * @return The balance of `_owner` at the current block + */ + function balanceOf(address _owner) external view returns (uint256 balance) { + return balanceOfAt(_owner, block.number); + } + + /** + * @notice `msg.sender` approves `_spender` to spend `_amount` tokens on + * its behalf. This is a modified version of the ERC20 approve function + * to be a little bit safer + * @param _spender The address of the account able to transfer the tokens + * @param _amount The amount of tokens to be approved for transfer + * @return True if the approval was successful + */ + function approve(address _spender, uint256 _amount) external returns (bool success) { + doApprove(msg.sender, _spender, _amount); + } + + /** + * @dev This function makes it easy to read the `allowed[]` map + * @param _owner The address of the account that owns the token + * @param _spender The address of the account able to transfer the tokens + * @return Amount of remaining tokens of _owner that _spender is allowed + * to spend + */ + function allowance( + address _owner, + address _spender + ) + external + view + returns (uint256 remaining) + { + return allowed[_owner][_spender]; + } + /** + * @notice `msg.sender` approves `_spender` to send `_amount` tokens on + * its behalf, and then a function is triggered in the contract that is + * being approved, `_spender`. This allows users to use their tokens to + * interact with contracts in one function call instead of two + * @param _spender The address of the contract able to transfer the tokens + * @param _amount The amount of tokens to be approved for transfer + * @return True if the function call was successful + */ + function approveAndCall( + address _spender, + uint256 _amount, + bytes memory _extraData + ) + public + returns (bool success) + { + require(doApprove(msg.sender, _spender, _amount)); + + ApproveAndCallFallBack(_spender).receiveApproval( + msg.sender, + _amount, + address(this), + _extraData + ); + + return true; + } + + /** + * @dev This function makes it easy to get the total number of tokens + * @return The total number of tokens + */ + function totalSupply() external view returns (uint) { + return totalSupplyAt(block.number); + } + + +//////////////// +// Query balance and totalSupply in History +//////////////// + + /** + * @dev Queries the balance of `_owner` at a specific `_blockNumber` + * @param _owner The address from which the balance will be retrieved + * @param _blockNumber The block number when the balance is queried + * @return The balance at `_blockNumber` + */ + function balanceOfAt( + address _owner, + uint _blockNumber + ) + public + view + returns (uint) + { + + // These next few lines are used when the balance of the token is + // requested before a check point was ever created for this token, it + // requires that the `parentToken.balanceOfAt` be queried at the + // genesis block for that token as this contains initial balance of + // this token + if ((balances[_owner].length == 0) + || (balances[_owner][0].fromBlock > _blockNumber)) { + if (address(parentToken) != address(0)) { + return parentToken.balanceOfAt(_owner, min(_blockNumber, parentSnapShotBlock)); + } else { + // Has no parent + return 0; + } + + // This will return the expected balance during normal situations + } else { + return getValueAt(balances[_owner], _blockNumber); + } + } + + /** + * @notice Total amount of tokens at a specific `_blockNumber`. + * @param _blockNumber The block number when the totalSupply is queried + * @return The total amount of tokens at `_blockNumber` + */ + function totalSupplyAt(uint _blockNumber) public view returns(uint) { + + // These next few lines are used when the totalSupply of the token is + // requested before a check point was ever created for this token, it + // requires that the `parentToken.totalSupplyAt` be queried at the + // genesis block for this token as that contains totalSupply of this + // token at this block number. + if ((totalSupplyHistory.length == 0) + || (totalSupplyHistory[0].fromBlock > _blockNumber)) { + if (address(parentToken) != address(0)) { + return parentToken.totalSupplyAt(min(_blockNumber, parentSnapShotBlock)); + } else { + return 0; + } + + // This will return the expected totalSupply during normal situations + } else { + return getValueAt(totalSupplyHistory, _blockNumber); + } + } + +//////////////// +// Clone Token Method +//////////////// + + /** + * @notice Creates a new clone token with the initial distribution being + * this token at `snapshotBlock` + * @param _cloneTokenName Name of the clone token + * @param _cloneDecimalUnits Number of decimals of the smallest unit + * @param _cloneTokenSymbol Symbol of the clone token + * @param _snapshotBlock Block when the distribution of the parent token is + * copied to set the initial distribution of the new clone token; + * if the block is zero than the actual block, the current block is used + * @param _transfersEnabled True if transfers are allowed in the clone + * @return The address of the new MiniMeToken Contract + */ + function createCloneToken( + string memory _cloneTokenName, + uint8 _cloneDecimalUnits, + string memory _cloneTokenSymbol, + uint _snapshotBlock, + bool _transfersEnabled + ) + public + returns(address) + { + uint snapshotBlock = _snapshotBlock; + if (snapshotBlock == 0) { + snapshotBlock = block.number; + } + MiniMeToken cloneToken = tokenFactory.createCloneToken( + address(this), + snapshotBlock, + _cloneTokenName, + _cloneDecimalUnits, + _cloneTokenSymbol, + _transfersEnabled + ); + + cloneToken.changeController(msg.sender); + + // An event to make the token easy to find on the blockchain + emit NewCloneToken(address(cloneToken), snapshotBlock); + return address(cloneToken); + } + +//////////////// +// Generate and destroy tokens +//////////////// + + /** + * @notice Generates `_amount` tokens that are assigned to `_owner` + * @param _owner The address that will be assigned the new tokens + * @param _amount The quantity of tokens generated + * @return True if the tokens are generated correctly + */ + function generateTokens( + address _owner, + uint _amount + ) + public + onlyController + returns (bool) + { + uint curTotalSupply = totalSupplyAt(block.number); + require(curTotalSupply + _amount >= curTotalSupply); // Check for overflow + uint previousBalanceTo = balanceOfAt(_owner, block.number); + require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow + updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount); + updateValueAtNow(balances[_owner], previousBalanceTo + _amount); + emit Transfer(address(0), _owner, _amount); + return true; + } + + /** + * @notice Burns `_amount` tokens from `_owner` + * @param _owner The address that will lose the tokens + * @param _amount The quantity of tokens to burn + * @return True if the tokens are burned correctly + */ + function destroyTokens( + address _owner, + uint _amount + ) + public + onlyController + returns (bool) + { + uint curTotalSupply = totalSupplyAt(block.number); + require(curTotalSupply >= _amount); + uint previousBalanceFrom = balanceOfAt(_owner, block.number); + require(previousBalanceFrom >= _amount); + updateValueAtNow(totalSupplyHistory, curTotalSupply - _amount); + updateValueAtNow(balances[_owner], previousBalanceFrom - _amount); + emit Transfer(_owner, address(0), _amount); + return true; + } + +//////////////// +// Enable tokens transfers +//////////////// + + /** + * @notice Enables token holders to transfer their tokens freely if true + * @param _transfersEnabled True if transfers are allowed in the clone + */ + function enableTransfers(bool _transfersEnabled) public onlyController { + transfersEnabled = _transfersEnabled; + } + +//////////////// +// Internal helper functions to query and set a value in a snapshot array +//////////////// + + /** + * @dev `getValueAt` retrieves the number of tokens at a given block number + * @param checkpoints The history of values being queried + * @param _block The block number to retrieve the value at + * @return The number of tokens being queried + */ + function getValueAt( + Checkpoint[] storage checkpoints, + uint _block + ) + internal + view + returns (uint) + { + if (checkpoints.length == 0) { + return 0; + } + + // Shortcut for the actual value + if (_block >= checkpoints[checkpoints.length-1].fromBlock) { + return checkpoints[checkpoints.length-1].value; + } + if (_block < checkpoints[0].fromBlock) { + return 0; + } + + // Binary search of the value in the array + uint min = 0; + uint max = checkpoints.length-1; + while (max > min) { + uint mid = (max + min + 1) / 2; + if (checkpoints[mid].fromBlock<=_block) { + min = mid; + } else { + max = mid-1; + } + } + return checkpoints[min].value; + } + + /** + * @dev `updateValueAtNow` used to update the `balances` map and the + * `totalSupplyHistory` + * @param checkpoints The history of data being updated + * @param _value The new number of tokens + */ + function updateValueAtNow(Checkpoint[] storage checkpoints, uint _value) internal { + if ((checkpoints.length == 0) + || (checkpoints[checkpoints.length -1].fromBlock < block.number)) { + Checkpoint storage newCheckPoint = checkpoints[checkpoints.length++]; + newCheckPoint.fromBlock = uint128(block.number); + newCheckPoint.value = uint128(_value); + } else { + Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length-1]; + oldCheckPoint.value = uint128(_value); + } + } + + /** + * @dev Internal function to determine if an address is a contract + * @param _addr The address being queried + * @return True if `_addr` is a contract + */ + function isContract(address _addr) internal view returns(bool) { + uint size; + if (_addr == address(0)){ + return false; + } + assembly { + size := extcodesize(_addr) + } + return size>0; + } + + /** + * @dev Helper function to return a min betwen the two uints + */ + function min(uint a, uint b) internal pure returns (uint) { + return a < b ? a : b; + } + + /** + * @notice The fallback function: If the contract's controller has not been + * set to 0, then the `proxyPayment` method is called which relays the + * ether and creates tokens as described in the token controller contract + */ + function () external payable { + require(isContract(controller)); + require(TokenController(controller).proxyPayment.value(msg.value)(msg.sender)); + } + +////////// +// Safety Methods +////////// + + /** + * @notice This method can be used by the controller to extract mistakenly + * sent tokens to this contract. + * @param _token The address of the token contract that you want to recover + * set to 0 in case you want to extract ether. + */ + function claimTokens(address _token) public onlyController { + if (_token == address(0)) { + controller.transfer(address(this).balance); + return; + } + + MiniMeToken token = MiniMeToken(address(uint160(_token))); + uint balance = token.balanceOf(address(this)); + token.transfer(controller, balance); + emit ClaimedTokens(_token, controller, balance); + } + +//////////////// +// Events +//////////////// + event ClaimedTokens(address indexed _token, address indexed _controller, uint _amount); + event Transfer(address indexed _from, address indexed _to, uint256 _amount); + event NewCloneToken(address indexed _cloneToken, uint snapshotBlock); + event Approval( + address indexed _owner, + address indexed _spender, + uint256 _amount + ); + +} diff --git a/contracts/token/MiniMeTokenFactory.sol b/contracts/token/MiniMeTokenFactory.sol new file mode 100644 index 0000000..b9d48a4 --- /dev/null +++ b/contracts/token/MiniMeTokenFactory.sol @@ -0,0 +1,49 @@ +pragma solidity ^0.5.0; + +import "./MiniMeToken.sol"; + +//////////////// +// MiniMeTokenFactory +//////////////// + +/** + * @dev This contract is used to generate clone contracts from a contract. + * In solidity this is the way to create a contract from a contract of the + * same class + */ +contract MiniMeTokenFactory { + + /** + * @notice Update the DApp by creating a new token with new functionalities + * the msg.sender becomes the controller of this clone token + * @param _parentToken Address of the token being cloned + * @param _snapshotBlock Block of the parent token that will + * determine the initial distribution of the clone token + * @param _tokenName Name of the new token + * @param _decimalUnits Number of decimals of the new token + * @param _tokenSymbol Token Symbol for the new token + * @param _transfersEnabled If true, tokens will be able to be transferred + * @return The address of the new token contract + */ + function createCloneToken( + address _parentToken, + uint _snapshotBlock, + string memory _tokenName, + uint8 _decimalUnits, + string memory _tokenSymbol, + bool _transfersEnabled + ) public returns (MiniMeToken) { + MiniMeToken newToken = new MiniMeToken( + address(this), + _parentToken, + _snapshotBlock, + _tokenName, + _decimalUnits, + _tokenSymbol, + _transfersEnabled + ); + + newToken.changeController(msg.sender); + return newToken; + } +} \ No newline at end of file diff --git a/contracts/token/MiniMeTokenInterface.sol b/contracts/token/MiniMeTokenInterface.sol new file mode 100644 index 0000000..549edde --- /dev/null +++ b/contracts/token/MiniMeTokenInterface.sol @@ -0,0 +1,108 @@ +pragma solidity ^0.5.0; + +import "./ERC20Token.sol"; + + +contract MiniMeTokenInterface is ERC20Token { + + /** + * @notice `msg.sender` approves `_spender` to send `_amount` tokens on + * its behalf, and then a function is triggered in the contract that is + * being approved, `_spender`. This allows users to use their tokens to + * interact with contracts in one function call instead of two + * @param _spender The address of the contract able to transfer the tokens + * @param _amount The amount of tokens to be approved for transfer + * @return True if the function call was successful + */ + function approveAndCall( + address _spender, + uint256 _amount, + bytes memory _extraData + ) + public + returns (bool success); + + /** + * @notice Creates a new clone token with the initial distribution being + * this token at `_snapshotBlock` + * @param _cloneTokenName Name of the clone token + * @param _cloneDecimalUnits Number of decimals of the smallest unit + * @param _cloneTokenSymbol Symbol of the clone token + * @param _snapshotBlock Block when the distribution of the parent token is + * copied to set the initial distribution of the new clone token; + * if the block is zero than the actual block, the current block is used + * @param _transfersEnabled True if transfers are allowed in the clone + * @return The address of the new MiniMeToken Contract + */ + function createCloneToken( + string memory _cloneTokenName, + uint8 _cloneDecimalUnits, + string memory _cloneTokenSymbol, + uint _snapshotBlock, + bool _transfersEnabled + ) + public + returns(address); + + /** + * @notice Generates `_amount` tokens that are assigned to `_owner` + * @param _owner The address that will be assigned the new tokens + * @param _amount The quantity of tokens generated + * @return True if the tokens are generated correctly + */ + function generateTokens( + address _owner, + uint _amount + ) + public + returns (bool); + + /** + * @notice Burns `_amount` tokens from `_owner` + * @param _owner The address that will lose the tokens + * @param _amount The quantity of tokens to burn + * @return True if the tokens are burned correctly + */ + function destroyTokens( + address _owner, + uint _amount + ) + public + returns (bool); + + /** + * @notice Enables token holders to transfer their tokens freely if true + * @param _transfersEnabled True if transfers are allowed in the clone + */ + function enableTransfers(bool _transfersEnabled) public; + + /** + * @notice This method can be used by the controller to extract mistakenly + * sent tokens to this contract. + * @param _token The address of the token contract that you want to recover + * set to 0 in case you want to extract ether. + */ + function claimTokens(address _token) public; + + /** + * @dev Queries the balance of `_owner` at a specific `_blockNumber` + * @param _owner The address from which the balance will be retrieved + * @param _blockNumber The block number when the balance is queried + * @return The balance at `_blockNumber` + */ + function balanceOfAt( + address _owner, + uint _blockNumber + ) + public + view + returns (uint); + + /** + * @notice Total amount of tokens at a specific `_blockNumber`. + * @param _blockNumber The block number when the totalSupply is queried + * @return The total amount of tokens at `_blockNumber` + */ + function totalSupplyAt(uint _blockNumber) public view returns(uint); + +} \ No newline at end of file diff --git a/contracts/token/TokenController.sol b/contracts/token/TokenController.sol new file mode 100644 index 0000000..95336be --- /dev/null +++ b/contracts/token/TokenController.sol @@ -0,0 +1,33 @@ +pragma solidity ^0.5.0; +/** + * @dev The token controller contract must implement these functions + */ +interface TokenController { + /** + * @notice Called when `_owner` sends ether to the MiniMe Token contract + * @param _owner The address that sent the ether to create tokens + * @return True if the ether is accepted, false if it throws + */ + function proxyPayment(address _owner) external payable returns(bool); + + /** + * @notice Notifies the controller about a token transfer allowing the + * controller to react if desired + * @param _from The origin of the transfer + * @param _to The destination of the transfer + * @param _amount The amount of the transfer + * @return False if the controller does not authorize the transfer + */ + function onTransfer(address _from, address _to, uint _amount) external returns(bool); + + /** + * @notice Notifies the controller about an approval allowing the + * controller to react if desired + * @param _owner The address that calls `approve()` + * @param _spender The spender in the `approve()` call + * @param _amount The amount in the `approve()` call + * @return False if the controller does not authorize the approval + */ + function onApprove(address _owner, address _spender, uint _amount) external + returns(bool); +} diff --git a/embark.json b/embark.json new file mode 100644 index 0000000..5eb9dcb --- /dev/null +++ b/embark.json @@ -0,0 +1,23 @@ +{ + "contracts": ["contracts/**"], + "app": { + "js/dapp.js": ["app/dapp.js"], + "index.html": "app/index.html", + "images/": ["app/images/**"] + }, + "buildDir": "dist/", + "config": "config/", + "versions": { + "web3": "1.0.0-beta", + "solc": "0.5.0", + "ipfs-api": "17.2.4" + }, + "plugins": { + }, + "options": { + "solc": { + "optimize": true, + "optimize-runs": 200 + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9c88667 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,212 @@ +{ + "name": "app_name", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/runtime": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.1.tgz", + "integrity": "sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==", + "requires": { + "regenerator-runtime": "^0.12.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, + "core-js": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.3.tgz", + "integrity": "sha512-l00tmFFZOBHtYhN4Cz7k32VM7vTn3rE2ANjQDxdEN6zmXZ/xq1jQuutnmHvMG1ZJ7xd72+TA5YpUK8wz3rWsfQ==" + }, + "dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "^7.1.2" + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "keycode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", + "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, + "prop-types-extra": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.0.tgz", + "integrity": "sha512-QFyuDxvMipmIVKD2TwxLVPzMnO4e5oOf1vr3tJIomL8E7d0lr6phTHd5nkPhFIzTD1idBLLEPeylL9g+rrTzRg==", + "requires": { + "react-is": "^16.3.2", + "warning": "^3.0.0" + } + }, + "react": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.7.0.tgz", + "integrity": "sha512-StCz3QY8lxTb5cl2HJxjwLFOXPIFQp+p+hxQfc8WE0QiLfCtIlKj8/+5tjjKm8uSTlAW+fCPaavGFS06V9Ar3A==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.12.0" + } + }, + "react-bootstrap": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.32.1.tgz", + "integrity": "sha512-RbfzKUbsukWsToWqGHfCCyMFq9QQI0TznutdyxyJw6dih2NvIne25Mrssg8LZsprqtPpyQi8bN0L0Fx3fUsL8Q==", + "requires": { + "babel-runtime": "^6.11.6", + "classnames": "^2.2.5", + "dom-helpers": "^3.2.0", + "invariant": "^2.2.1", + "keycode": "^2.1.2", + "prop-types": "^15.5.10", + "prop-types-extra": "^1.0.1", + "react-overlays": "^0.8.0", + "react-prop-types": "^0.4.0", + "react-transition-group": "^2.0.0", + "uncontrollable": "^4.1.0", + "warning": "^3.0.0" + } + }, + "react-dom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.7.0.tgz", + "integrity": "sha512-D0Ufv1ExCAmF38P2Uh1lwpminZFRXEINJe53zRAbm4KPwSyd6DY/uDoS0Blj9jvPpn1+wivKpZYc8aAAN/nAkg==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.12.0" + } + }, + "react-is": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.7.0.tgz", + "integrity": "sha512-Z0VRQdF4NPDoI0tsXVMLkJLiwEBa+RP66g0xDHxgxysxSoCUccSten4RTF/UFvZF1dZvZ9Zu1sx+MDXwcOR34g==" + }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-overlays": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.8.3.tgz", + "integrity": "sha512-h6GT3jgy90PgctleP39Yu3eK1v9vaJAW73GOA/UbN9dJ7aAN4BTZD6793eI1D5U+ukMk17qiqN/wl3diK1Z5LA==", + "requires": { + "classnames": "^2.2.5", + "dom-helpers": "^3.2.1", + "prop-types": "^15.5.10", + "prop-types-extra": "^1.0.1", + "react-transition-group": "^2.2.0", + "warning": "^3.0.0" + } + }, + "react-prop-types": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/react-prop-types/-/react-prop-types-0.4.0.tgz", + "integrity": "sha1-+ZsL+0AGkpya8gUefBQUpcdbk9A=", + "requires": { + "warning": "^3.0.0" + } + }, + "react-transition-group": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.3.tgz", + "integrity": "sha512-2DGFck6h99kLNr8pOFk+z4Soq3iISydwOFeeEVPjTN6+Y01CmvbWmnN02VuTWyFdnRtIDPe+wy2q6Ui8snBPZg==", + "requires": { + "dom-helpers": "^3.3.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "scheduler": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.12.0.tgz", + "integrity": "sha512-t7MBR28Akcp4Jm+QoR63XgAi9YgCUmgvDHqf5otgAj4QvdoBE4ImCX0ffehefePPG+aitiYHp0g/mW6s4Tp+dw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "uncontrollable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-4.1.0.tgz", + "integrity": "sha1-4DWCkSUuGGUiLZCTmxny9J+Bwak=", + "requires": { + "invariant": "^2.1.0" + } + }, + "warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "requires": { + "loose-envify": "^1.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..cc68b5a --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "app_name", + "version": "0.0.1", + "description": "", + "main": "Gruntfile.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "homepage": "", + "dependencies": { + "react": "^16.3.2", + "react-bootstrap": "0.32.1", + "react-dom": "^16.3.2" + } +} diff --git a/test/Meritocracy_spec.js b/test/Meritocracy_spec.js new file mode 100644 index 0000000..a5fcdb2 --- /dev/null +++ b/test/Meritocracy_spec.js @@ -0,0 +1,57 @@ +/*global contract, config, it, assert*/ +const Meritocracy = require('Embark/contracts/Meritocracy'); +// const StandardToken = require('Embark/contracts/StandardToken'); +const SNT = require('Embark/contracts/SNT'); + +let accounts; + +// For documentation please see https://embark.status.im/docs/contracts_testing.html +config({ + //deployment: { + // accounts: [ + // // you can configure custom accounts with a custom balance + // // see https://embark.status.im/docs/contracts_testing.html#Configuring-accounts + // ] + //}, + contracts: { + "MiniMeToken": { "deploy": false, "args" : [] }, + "MiniMeTokenFactory": { }, + "SNT": { + "instanceOf": "MiniMeToken", + "args": [ + "$MiniMeTokenFactory", + "0x0000000000000000000000000000000000000000", + 0, + "TestMiniMeToken", + 18, + "STT", + true + ] + }, + "Meritocracy": { + "args": ["$SNT", 100] + } + } +}, (_err, web3_accounts) => { + accounts = web3_accounts +}); + +contract("Meritocracy", function () { + this.timeout(0); + + // it("should set constructor value", async function () { + // let result = await Meritocracy.methods.storedData().call(); + // assert.strictEqual(parseInt(result, 10), 100); + // }); + + // it("set storage value", async function () { + // await Meritocracy.methods.set(150).send(); + // let result = await SimpleStorage.methods.get().call(); + // assert.strictEqual(parseInt(result, 10), 150); + // }); + + // it("should have account with balance", async function() { + // let balance = await web3.eth.getBalance(accounts[0]); + // assert.ok(parseInt(balance, 10) > 0); + // }); +});