initial commit
This commit is contained in:
commit
66f12d1323
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "airbnb",
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/prop-types": 0
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
*.sol linguist-language=Solidity
|
|
@ -0,0 +1,43 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# embark
|
||||
.embark/
|
||||
chains.json
|
||||
.password
|
||||
embark_demo/
|
||||
|
||||
# egg-related
|
||||
viper.egg-info/
|
||||
build/
|
||||
dist/
|
||||
.eggs/
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Coverage tests
|
||||
.coverage
|
||||
.cache/
|
||||
coverage/
|
||||
coverageEnv/
|
||||
coverage.json
|
||||
|
||||
# node
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
package-lock.json
|
||||
|
||||
# other
|
||||
.vs/
|
||||
bin/
|
|
@ -0,0 +1,19 @@
|
|||
# status.im contracts
|
||||
Requires https://github.com/creationix/nvm
|
||||
Usage:
|
||||
```
|
||||
nvm install v8.9.4
|
||||
nvm use v8.9.4
|
||||
npm install -g embark
|
||||
git clone https://github.com/status-im/contracts.git
|
||||
cd contracts
|
||||
npm install
|
||||
embark simulator
|
||||
embark test
|
||||
embark run
|
||||
```
|
||||
|
||||
| Contract | Deploy | Test | UI |
|
||||
| -------------------------------------- | ------ | ---- | --- |
|
||||
| token/TestToken | Yes | Yes | Yes |
|
||||
| token/ERC20Token | No | Yes | Yes |
|
|
@ -0,0 +1,50 @@
|
|||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import TestStatusNetwork from 'Embark/contracts/TestStatusNetwork';
|
||||
import MiniMeToken from 'Embark/contracts/MiniMeToken';
|
||||
import React from 'react';
|
||||
import { Form, FormGroup, FormControl, HelpBlock, Button } from 'react-bootstrap';
|
||||
import ERC20TokenUI from './erc20token';
|
||||
|
||||
class TestStatusNetworkUI extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
amountToMint: 100,
|
||||
}
|
||||
}
|
||||
|
||||
handleMintAmountChange(e){
|
||||
this.setState({amountToMint: e.target.value});
|
||||
}
|
||||
|
||||
async mint(e){
|
||||
e.preventDefault();
|
||||
await EmbarkJS.enableEthereum();
|
||||
var value = parseInt(this.state.amountToMint, 10);
|
||||
TestStatusNetwork.methods.mint(value).send({ gas: 1000000 })
|
||||
|
||||
console.log(TestStatusNetwork.options.address +".mint("+value+").send({from: " + web3.eth.defaultAccount + "})");
|
||||
}
|
||||
|
||||
render(){
|
||||
return (<React.Fragment>
|
||||
<h3> Test Status Network</h3>
|
||||
<Form inline>
|
||||
<FormGroup>
|
||||
<FormControl
|
||||
type="text"
|
||||
defaultValue={this.state.amountToMint}
|
||||
onChange={(e) => this.handleMintAmountChange(e)} />
|
||||
<Button bsStyle="primary" onClick={(e) => this.mint(e)}>Mint</Button>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
|
||||
<ERC20TokenUI address={ MiniMeToken.options.address } />
|
||||
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TestStatusNetworkUI;
|
|
@ -0,0 +1,109 @@
|
|||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import ERC20Token from 'Embark/contracts/ERC20Token';
|
||||
import React from 'react';
|
||||
import { Form, FormGroup, FormControl, HelpBlock, Button } from 'react-bootstrap';
|
||||
|
||||
class ERC20TokenUI extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
ERC20Token.options.address = props.address;
|
||||
this.state = {
|
||||
balanceOf: 0,
|
||||
transferTo: "",
|
||||
transferAmount: 0,
|
||||
accountBalance: 0,
|
||||
accountB: web3.eth.defaultAccount,
|
||||
}
|
||||
}
|
||||
|
||||
update_transferTo(e){
|
||||
this.setState({transferTo: e.target.value});
|
||||
}
|
||||
|
||||
update_transferAmount(e){
|
||||
this.setState({transferAmount: e.target.value});
|
||||
}
|
||||
|
||||
transfer(e){
|
||||
var to = this.state.transferTo;
|
||||
var amount = this.state.transferAmount;
|
||||
var tx = ERC20Token.methods.transfer(to, amount).send({from: web3.eth.defaultAccount});
|
||||
this._addToLog(ERC20Token.options.address+".transfer(" + to + ", "+amount+")");
|
||||
}
|
||||
|
||||
approve(e){
|
||||
var to = this.state.transferTo;
|
||||
var amount = this.state.transferAmount;
|
||||
var tx = ERC20Token.methods.approve(to, amount).send({from: web3.eth.defaultAccount});
|
||||
this._addToLog(ERC20Token.options.address+".approve(" + to + ", "+amount+")");
|
||||
}
|
||||
|
||||
balanceOf(e){
|
||||
e.preventDefault();
|
||||
var who = e.target.value;
|
||||
ERC20Token.methods.balanceOf(who).call()
|
||||
.then(_value => this.setState({balanceOf: _value}))
|
||||
this._addToLog(ERC20Token.options.address+".balanceOf(" + who + ")");
|
||||
}
|
||||
|
||||
getDefaultAccountBalance(){
|
||||
ERC20Token.methods.balanceOf(web3.eth.defaultAccount).call()
|
||||
.then(_value => this.setState({accountBalance: _value}))
|
||||
this._addToLog(ERC20Token.options.address + ".balanceOf(" + web3.eth.defaultAccount + ")");
|
||||
}
|
||||
|
||||
_addToLog(txt){
|
||||
console.log(txt);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h3> Read account token balance</h3>
|
||||
<Form inline>
|
||||
<FormGroup>
|
||||
<label>
|
||||
Of:
|
||||
<FormControl
|
||||
type="text"
|
||||
defaultValue={this.state.accountB}
|
||||
onChange={(e) => this.balanceOf(e)} />
|
||||
</label>
|
||||
<label>
|
||||
<HelpBlock><span className="balanceOf">{this.state.balanceOf}</span></HelpBlock>
|
||||
</label>
|
||||
|
||||
</FormGroup>
|
||||
</Form>
|
||||
|
||||
<h3> Transfer/Approve token balance</h3>
|
||||
<Form inline>
|
||||
<FormGroup>
|
||||
<label>
|
||||
To:
|
||||
<FormControl
|
||||
type="text"
|
||||
defaultValue={this.state.transferTo}
|
||||
onChange={(e) => this.update_transferTo(e) } />
|
||||
</label>
|
||||
<label>
|
||||
Amount:
|
||||
<FormControl
|
||||
type="text"
|
||||
defaultValue={this.state.transferAmount}
|
||||
onChange={(e) => this.update_transferAmount(e) } />
|
||||
</label>
|
||||
<Button bsStyle="primary" onClick={(e) => this.transfer(e)}>Transfer</Button>
|
||||
<Button bsStyle="primary" onClick={(e) => this.approve(e)}>Approve</Button>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default ERC20TokenUI;
|
|
@ -0,0 +1,63 @@
|
|||
.navbar {
|
||||
|
||||
}
|
||||
|
||||
.accounts {
|
||||
float: right;
|
||||
margin-right: 17px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.identicon {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
|
||||
.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: 5px;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import React from 'react';
|
||||
import { Tabs, Tab } from 'react-bootstrap';
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import TestTokenUI from './components/TestStatusNetwork';
|
||||
|
||||
import './dapp.css';
|
||||
|
||||
class DApp extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
_renderStatus(title, available) {
|
||||
let className = available ? 'pull-right status-online' : 'pull-right status-offline';
|
||||
return <React.Fragment>
|
||||
{title}
|
||||
<span className={className}></span>
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
|
||||
<Tabs defaultActiveKey={1} id="uncontrolled-tab-example">
|
||||
<Tab eventKey={1} title="TestToken">
|
||||
<TestTokenUI />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
export default DApp;
|
|
@ -0,0 +1,12 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Status.im - Contracts</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
|
||||
</head>
|
||||
<body class="container">
|
||||
<div id="app">
|
||||
</div>
|
||||
<script src="js/index.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import DApp from './dapp';
|
||||
import './dapp.css';
|
||||
|
||||
render(
|
||||
<DApp />,
|
||||
document.getElementById('app')
|
||||
);
|
|
@ -0,0 +1,73 @@
|
|||
module.exports = {
|
||||
development: {
|
||||
enabled: true,
|
||||
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
|
||||
genesisBlock: "config/development/genesis.json", // Genesis block to initiate on first creation of a development node
|
||||
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)
|
||||
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
|
||||
proxy: true, // Proxy is used to present meaningful information about transactions
|
||||
accounts: [
|
||||
{
|
||||
nodeAccounts: true,
|
||||
password: "config/development/.password"
|
||||
}
|
||||
],
|
||||
targetGasLimit: 8000000, // Target gas limit sets the artificial target gas floor for the blocks to mine
|
||||
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)
|
||||
simulatorBlocktime: 0 // Specify blockTime in seconds for automatic mining. Default is 0 and no auto-mining.
|
||||
},
|
||||
testnet: {
|
||||
enabled: true,
|
||||
networkType: "testnet",
|
||||
syncMode: "light",
|
||||
rpcHost: "localhost",
|
||||
rpcPort: 8545,
|
||||
rpcCorsDomain: "http://localhost:8000",
|
||||
accounts: [
|
||||
{
|
||||
nodeAccounts: true,
|
||||
password: "config/testnet/.password"
|
||||
}
|
||||
],
|
||||
},
|
||||
livenet: {
|
||||
enabled: false,
|
||||
networkType: "livenet",
|
||||
syncMode: "light",
|
||||
rpcHost: "localhost",
|
||||
rpcPort: 8545,
|
||||
rpcCorsDomain: "http://localhost:8000",
|
||||
accounts: [
|
||||
{
|
||||
nodeAccounts: true,
|
||||
password: "config/livenet/.password"
|
||||
}
|
||||
],
|
||||
},
|
||||
rinkeby: {
|
||||
enabled: true,
|
||||
networkType: "rinkeby",
|
||||
syncMode: "light",
|
||||
rpcHost: "localhost",
|
||||
rpcPort: 8545,
|
||||
rpcCorsDomain: "http://localhost:8000",
|
||||
accounts: [
|
||||
{
|
||||
nodeAccounts: true,
|
||||
password: "config/rinkeby/.password"
|
||||
}
|
||||
],
|
||||
}
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
module.exports = {
|
||||
default: {
|
||||
enabled: true,
|
||||
provider: "whisper", // Communication provider. Currently, Embark only supports whisper
|
||||
available_providers: ["whisper"], // Array of available providers
|
||||
connection: {
|
||||
host: "localhost", // Host of the blockchain node
|
||||
port: 8546, // Port of the blockchain node
|
||||
type: "ws" // Type of connection (ws or rpc)
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,86 @@
|
|||
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),
|
||||
},
|
||||
// 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",
|
||||
strategy: 'explicit',
|
||||
contracts: {
|
||||
"MiniMeTokenFactory": {},
|
||||
"MiniMeToken": {
|
||||
"args":["$MiniMeTokenFactory", "0x0", "0x0", "Status Test Token", 18, "STT", true],
|
||||
},
|
||||
"StatusRoot": {
|
||||
"instanceOf": "TestStatusNetwork",
|
||||
"args": ["0x0", "$MiniMeToken"],
|
||||
"onDeploy": [
|
||||
"await MiniMeToken.methods.changeController(TestStatusNetwork.address).send()",
|
||||
"await StatusRoot.methods.setOpen(true).send()",
|
||||
]
|
||||
},
|
||||
"StickerMarket": {
|
||||
"args": ["$MiniMeToken"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
development: {
|
||||
deployment: {
|
||||
accounts: [
|
||||
{
|
||||
privateKey: "b2ab40d549e67ba67f278781fec03b3a90515ad4d0c898a6326dd958de1e46fa",
|
||||
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
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
testnet: {
|
||||
contracts: {
|
||||
"MiniMeTokenFactory": {
|
||||
"deploy": false,
|
||||
"address": "0x6bFa86A71A7DBc68566d5C741F416e3009804279"
|
||||
},
|
||||
"MiniMeToken": {
|
||||
"deploy": false,
|
||||
"address": "0xc55cF4B03948D7EBc8b9E8BAD92643703811d162"
|
||||
},
|
||||
"StatusRoot": {
|
||||
"instanceOf": "TestStatusNetwork",
|
||||
"deploy": false,
|
||||
"address": "0x34358C45FbA99ef9b78cB501584E8cBFa6f85Cef"
|
||||
},
|
||||
"StickerMarket": {
|
||||
"deploy": false,
|
||||
"address": "0x39d16cdb56b5a6a89e1a397a13fe48034694316e"
|
||||
}
|
||||
}
|
||||
},
|
||||
rinkeby: {
|
||||
contracts: {
|
||||
"MiniMeTokenFactory": {
|
||||
"deploy": false,
|
||||
"address": "0x5bA5C786845CaacD45f5952E1135F4bFB8855469"
|
||||
},
|
||||
"MiniMeToken": {
|
||||
"deploy": false,
|
||||
"address": "0x43d5adC3B49130A575ae6e4b00dFa4BC55C71621"
|
||||
},
|
||||
"StatusRoot": {
|
||||
"instanceOf": "TestStatusNetwork",
|
||||
"deploy": false,
|
||||
"address": "0xEdEB948dE35C6ac414359f97329fc0b4be70d3f1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"config": {
|
||||
"homesteadBlock": 1,
|
||||
"byzantiumBlock": 1,
|
||||
"daoForkSupport": true
|
||||
},
|
||||
"nonce": "0x0000000000000042",
|
||||
"difficulty": "0x0",
|
||||
"alloc": {
|
||||
"0x3333333333333333333333333333333333333333": {"balance": "15000000000000000000"}
|
||||
},
|
||||
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"coinbase": "0x3333333333333333333333333333333333333333",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"extraData": "0x",
|
||||
"gasLimit": "0x7a1200"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
default: {
|
||||
available_providers: ["ens"],
|
||||
provider: "ens"
|
||||
}
|
||||
};
|
|
@ -0,0 +1,59 @@
|
|||
module.exports = {
|
||||
// default applies to all environments
|
||||
default: {
|
||||
enabled: true,
|
||||
ipfs_bin: "ipfs",
|
||||
available_providers: ["ipfs"],
|
||||
upload: {
|
||||
provider: "ipfs",
|
||||
host: "localhost",
|
||||
port: 5001
|
||||
},
|
||||
dappConnection: [
|
||||
{
|
||||
provider:"ipfs",
|
||||
host: "localhost",
|
||||
port: 5001,
|
||||
getUrl: "http://localhost:8080/ipfs/"
|
||||
}
|
||||
]
|
||||
// Configuration to start Swarm in the same terminal as `embark run`
|
||||
/*,account: {
|
||||
address: "YOUR_ACCOUNT_ADDRESS", // Address of account accessing Swarm
|
||||
password: "PATH/TO/PASSWORD/FILE" // File containing the password of the account
|
||||
},
|
||||
swarmPath: "PATH/TO/SWARM/EXECUTABLE" // Path to swarm executable (default: swarm)*/
|
||||
},
|
||||
|
||||
// default environment, merges with the settings in default
|
||||
// assumed to be the intended environment by `embark run`
|
||||
development: {
|
||||
enabled: true,
|
||||
upload: {
|
||||
provider: "ipfs",
|
||||
host: "localhost",
|
||||
port: 5001,
|
||||
getUrl: "http://localhost:8080/ipfs/"
|
||||
}
|
||||
},
|
||||
|
||||
// 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"
|
||||
//custom_name: {
|
||||
//}
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
enabled: true,
|
||||
host: "localhost",
|
||||
port: 8000
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
/**
|
||||
* Utility library of inline functions on addresses
|
||||
*/
|
||||
library Address {
|
||||
/**
|
||||
* Returns whether the target address is a contract
|
||||
* @dev This function will return false if invoked during the constructor of a contract,
|
||||
* as the code is not actually created until after the constructor finishes.
|
||||
* @param account address of the account to check
|
||||
* @return whether the target address is a contract
|
||||
*/
|
||||
function isContract(address account) internal view returns (bool) {
|
||||
uint256 size;
|
||||
// XXX Currently there is no better way to check if there is a contract in an address
|
||||
// than to check the size of the code at that address.
|
||||
// See https://ethereum.stackexchange.com/a/14016/36603
|
||||
// for more details about how this works.
|
||||
// TODO Check this again before the Serenity release, because all addresses will be
|
||||
// contracts then.
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly { size := extcodesize(account) }
|
||||
return size > 0;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
interface ERC165 {
|
||||
/**
|
||||
* @notice Query if a contract implements an interface
|
||||
* @param interfaceID The interface identifier, as specified in ERC-165
|
||||
* @dev Interface identification is specified in ERC-165. This function
|
||||
* uses less than 30,000 gas.
|
||||
* @return `true` if the contract implements `interfaceID` and
|
||||
* `interfaceID` is not 0xffffffff, `false` otherwise
|
||||
*/
|
||||
function supportsInterface(bytes4 interfaceID) external view returns (bool);
|
||||
}
|
||||
|
||||
/**
|
||||
* @title ERC165
|
||||
* @author Matt Condon (@shrugs)
|
||||
* @dev Implements ERC165 using a lookup table.
|
||||
*/
|
||||
contract Introspective is ERC165 {
|
||||
bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
|
||||
/**
|
||||
* 0x01ffc9a7 ===
|
||||
* bytes4(keccak256('supportsInterface(bytes4)'))
|
||||
*/
|
||||
|
||||
/**
|
||||
* @dev a mapping of interface id to whether or not it's supported
|
||||
*/
|
||||
mapping(bytes4 => bool) private _supportedInterfaces;
|
||||
|
||||
/**
|
||||
* @dev A contract implementing SupportsInterfaceWithLookup
|
||||
* implement ERC165 itself
|
||||
*/
|
||||
constructor () internal {
|
||||
_registerInterface(_INTERFACE_ID_ERC165);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev implement supportsInterface(bytes4) using a lookup table
|
||||
*/
|
||||
function supportsInterface(bytes4 interfaceId) external view returns (bool) {
|
||||
return _supportedInterfaces[interfaceId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev internal method for registering an interface
|
||||
*/
|
||||
function _registerInterface(bytes4 interfaceId) internal {
|
||||
require(interfaceId != 0xffffffff, "Bad interfaceId");
|
||||
_supportedInterfaces[interfaceId] = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
/**
|
||||
* @title MerkleProof
|
||||
* @dev Merkle proof verification based on
|
||||
* https://github.com/ameensol/merkle-tree-solidity/blob/master/src/MerkleProof.sol
|
||||
*/
|
||||
|
||||
library MerkleProof {
|
||||
/**
|
||||
* @dev Verifies a Merkle proof proving the existence of a leaf in a Merkle tree. Assumes that each pair of leaves
|
||||
* and each pair of pre-images are sorted.
|
||||
* @param proof Merkle proof containing sibling hashes on the branch from the leaf to the root of the Merkle tree
|
||||
* @param root Merkle root
|
||||
* @param leaf Leaf of Merkle tree
|
||||
*/
|
||||
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
|
||||
bytes32 computedHash = leaf;
|
||||
|
||||
for (uint256 i = 0; i < proof.length; i++) {
|
||||
bytes32 proofElement = proof[i];
|
||||
|
||||
if (computedHash < proofElement) {
|
||||
// Hash(current computed hash + current element of the proof)
|
||||
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
|
||||
} else {
|
||||
// Hash(current element of the proof + current computed hash)
|
||||
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the computed hash (root) is equal to the provided root
|
||||
return computedHash == root;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
/**
|
||||
* @notice Uses ethereum signed messages
|
||||
*/
|
||||
contract MessageSigned {
|
||||
|
||||
constructor() internal {}
|
||||
|
||||
/**
|
||||
* @notice recovers address who signed the message
|
||||
* @param _signHash operation ethereum signed message hash
|
||||
* @param _messageSignature message `_signHash` signature
|
||||
*/
|
||||
function recoverAddress(
|
||||
bytes32 _signHash,
|
||||
bytes memory _messageSignature
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns(address)
|
||||
{
|
||||
uint8 v;
|
||||
bytes32 r;
|
||||
bytes32 s;
|
||||
(v,r,s) = signatureSplit(_messageSignature);
|
||||
return ecrecover(
|
||||
_signHash,
|
||||
v,
|
||||
r,
|
||||
s
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Hash a hash with `"\x19Ethereum Signed Message:\n32"`
|
||||
* @param _hash Sign to hash.
|
||||
* @return signHash Hash to be signed.
|
||||
*/
|
||||
function getSignHash(
|
||||
bytes32 _hash
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes32 signHash)
|
||||
{
|
||||
signHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s`
|
||||
*/
|
||||
function signatureSplit(bytes memory _signature)
|
||||
internal
|
||||
pure
|
||||
returns (uint8 v, bytes32 r, bytes32 s)
|
||||
{
|
||||
// The signature format is a compact form of:
|
||||
// {bytes32 r}{bytes32 s}{uint8 v}
|
||||
// Compact means, uint8 is not padded to 32 bytes.
|
||||
assembly {
|
||||
r := mload(add(_signature, 32))
|
||||
s := mload(add(_signature, 64))
|
||||
// Here we are loading the last 32 bytes, including 31 bytes
|
||||
// of 's'. There is no 'mload8' to do this.
|
||||
//
|
||||
// 'byte' is not working due to the Solidity parser, so lets
|
||||
// use the second best option, 'and'
|
||||
v := and(mload(add(_signature, 65)), 0xff)
|
||||
}
|
||||
|
||||
require(v == 27 || v == 28, "Bad signature");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
/// @dev `Owned` is a base level contract that assigns an `owner` that can be
|
||||
/// later changed
|
||||
contract Owned {
|
||||
|
||||
/// @dev `owner` is the only address that can call a function with this
|
||||
/// modifier
|
||||
modifier onlyOwner() {
|
||||
require(msg.sender == owner, "Unauthorized");
|
||||
_;
|
||||
}
|
||||
|
||||
address payable public owner;
|
||||
|
||||
/// @notice The Constructor assigns the message sender to be `owner`
|
||||
constructor() internal {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
address payable public newOwner;
|
||||
|
||||
/// @notice `owner` can step down and assign some other address to this role
|
||||
/// @param _newOwner The address of the new owner. 0x0 can be used to create
|
||||
/// an unowned neutral vault, however that cannot be undone
|
||||
function changeOwner(address payable _newOwner) public onlyOwner {
|
||||
newOwner = _newOwner;
|
||||
}
|
||||
|
||||
|
||||
function acceptOwnership() public {
|
||||
if (msg.sender == newOwner) {
|
||||
owner = newOwner;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
/**
|
||||
* Math operations with safety checks
|
||||
*/
|
||||
library SafeMath {
|
||||
function mul(uint a, uint b) internal pure returns (uint) {
|
||||
uint c = a * b;
|
||||
assert(a == 0 || c / a == b);
|
||||
return c;
|
||||
}
|
||||
|
||||
function div(uint a, uint b) internal pure returns (uint) {
|
||||
// assert(b > 0); // Solidity automatically throws when dividing by 0
|
||||
uint c = a / b;
|
||||
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
|
||||
return c;
|
||||
}
|
||||
|
||||
function sub(uint a, uint b) internal pure returns (uint) {
|
||||
assert(b <= a);
|
||||
return a - b;
|
||||
}
|
||||
|
||||
function add(uint a, uint b) internal pure returns (uint) {
|
||||
uint c = a + b;
|
||||
assert(c >= a);
|
||||
return c;
|
||||
}
|
||||
|
||||
function max64(uint64 a, uint64 b) internal pure returns (uint64) {
|
||||
return a >= b ? a : b;
|
||||
}
|
||||
|
||||
function min64(uint64 a, uint64 b) internal pure returns (uint64) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
function max256(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||
return a >= b ? a : b;
|
||||
}
|
||||
|
||||
function min256(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
import "../token/TokenController.sol";
|
||||
import "../common/Owned.sol";
|
||||
import "../token/ERC20Token.sol";
|
||||
import "../token/MiniMeToken.sol";
|
||||
/**
|
||||
* @title SNTController
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* @notice enables economic abstraction for SNT
|
||||
*/
|
||||
contract SNTController is TokenController, Owned {
|
||||
|
||||
MiniMeToken public snt;
|
||||
|
||||
event ClaimedTokens(address indexed _token, address indexed _controller, uint256 _amount);
|
||||
event ControllerChanged(address indexed _newController);
|
||||
|
||||
/**
|
||||
* @notice Constructor
|
||||
* @param _owner Authority address
|
||||
* @param _snt SNT token
|
||||
*/
|
||||
constructor(address payable _owner, MiniMeToken _snt) internal {
|
||||
if(_owner != address(0)){
|
||||
owner = _owner;
|
||||
}
|
||||
snt = _snt;
|
||||
}
|
||||
/**
|
||||
* @notice The owner of this contract can change the controller of the SNT token
|
||||
* Please, be sure that the owner is a trusted agent or 0x0 address.
|
||||
* @param _newController The address of the new controller
|
||||
*/
|
||||
function changeController(address payable _newController) public onlyOwner {
|
||||
snt.changeController(_newController);
|
||||
emit ControllerChanged(_newController);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 onlyOwner {
|
||||
if (snt.controller() == address(this)) {
|
||||
snt.claimTokens(_token);
|
||||
}
|
||||
if (_token == address(0)) {
|
||||
address(owner).transfer(address(this).balance);
|
||||
return;
|
||||
}
|
||||
|
||||
ERC20Token token = ERC20Token(_token);
|
||||
uint256 balance = token.balanceOf(address(this));
|
||||
token.transfer(owner, balance);
|
||||
emit ClaimedTokens(_token, owner, balance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice payment by address coming from controlled token
|
||||
* @dev In between the offering and the network. Default settings for allowing token transfers.
|
||||
*/
|
||||
function proxyPayment(address) external payable returns (bool) {
|
||||
//Uncomment above line when using parameters
|
||||
//require(msg.sender == address(snt), "Unauthorized");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice register and authorizes transfer from token
|
||||
* @dev called by snt when a transfer is made
|
||||
*/
|
||||
function onTransfer(address, address, uint256) external returns (bool) {
|
||||
//Uncomment above line when using parameters
|
||||
//require(msg.sender == address(snt), "Unauthorized");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice register and authorizes approve from token
|
||||
* @dev called by snt when an approval is made
|
||||
*/
|
||||
function onApprove(address, address, uint256) external returns (bool) {
|
||||
//Uncomment above line when using parameters
|
||||
//require(msg.sender == address(snt), "Unauthorized");
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
import "./SNTController.sol";
|
||||
|
||||
/**
|
||||
* @dev Status Network is implemented here
|
||||
*/
|
||||
contract StatusNetwork is SNTController {
|
||||
|
||||
/**
|
||||
* @notice Constructor
|
||||
* @param _owner Authority address
|
||||
* @param _snt SNT token
|
||||
*/
|
||||
constructor(
|
||||
address payable _owner,
|
||||
MiniMeToken _snt
|
||||
)
|
||||
public
|
||||
SNTController(_owner, _snt)
|
||||
{ }
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
import "./StatusNetwork.sol";
|
||||
/**
|
||||
* @title SNTController
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* @notice Test net version of SNTController which allow public mint
|
||||
*/
|
||||
contract TestStatusNetwork is StatusNetwork {
|
||||
|
||||
bool public open = false;
|
||||
|
||||
/**
|
||||
* @notice Constructor
|
||||
* @param _owner Authority address
|
||||
* @param _snt SNT token
|
||||
*/
|
||||
constructor(address payable _owner, MiniMeToken _snt)
|
||||
public
|
||||
StatusNetwork(_owner, _snt)
|
||||
{ }
|
||||
|
||||
function () external {
|
||||
_generateTokens(msg.sender, 1000 * (10 ** uint(snt.decimals())));
|
||||
}
|
||||
|
||||
function mint(uint256 _amount) external {
|
||||
_generateTokens(msg.sender, _amount);
|
||||
}
|
||||
|
||||
function generateTokens(address _who, uint _amount) external {
|
||||
_generateTokens(_who, _amount);
|
||||
}
|
||||
|
||||
function destroyTokens(address _who, uint _amount) external onlyOwner {
|
||||
snt.destroyTokens(_who, _amount);
|
||||
}
|
||||
|
||||
function setOpen(bool _open) external onlyOwner {
|
||||
open = _open;
|
||||
}
|
||||
|
||||
function _generateTokens(address _who, uint _amount) private {
|
||||
require(msg.sender == owner || open, "Test Mint Disabled");
|
||||
address statusNetwork = snt.controller();
|
||||
if(statusNetwork == address(this)){
|
||||
snt.generateTokens(_who, _amount);
|
||||
} else {
|
||||
TestStatusNetwork(statusNetwork).generateTokens(_who, _amount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,603 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
import "../../token/NonfungibleToken.sol";
|
||||
import "../../token/ERC20Token.sol";
|
||||
import "../../token/ApproveAndCallFallBack.sol";
|
||||
import "../../common/Controlled.sol";
|
||||
|
||||
/**
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* StickerMarket allows any address register "StickerPack" which can be sold to any address in form of "StickerPack", an ERC721 token.
|
||||
*/
|
||||
contract StickerMarket is Controlled, NonfungibleToken, ApproveAndCallFallBack {
|
||||
event Register(uint256 indexed packId, uint256 dataPrice, bytes _contenthash);
|
||||
event Categorized(bytes4 indexed category, uint256 indexed packId);
|
||||
event Uncategorized(bytes4 indexed category, uint256 indexed packId);
|
||||
event Unregister(uint256 indexed packId);
|
||||
event ClaimedTokens(address indexed _token, address indexed _controller, uint256 _amount);
|
||||
event MarketState(State state);
|
||||
event RegisterFee(uint256 value);
|
||||
event BurnRate(uint256 value);
|
||||
|
||||
enum State { Invalid, Open, BuyOnly, Controlled, Closed }
|
||||
|
||||
struct Pack {
|
||||
bytes4[] category;
|
||||
address owner; //beneficiary of "buy"
|
||||
bool mintable;
|
||||
uint256 timestamp;
|
||||
uint256 price; //in "wei"
|
||||
uint256 donate; //in "wei"
|
||||
bytes contenthash;
|
||||
}
|
||||
|
||||
State public state = State.Open;
|
||||
uint256 registerFee;
|
||||
uint256 burnRate;
|
||||
|
||||
//include global var to set burn rate/percentage
|
||||
ERC20Token public snt; //payment token
|
||||
mapping(uint256 => Pack) public packs;
|
||||
mapping(uint256 => uint256) public tokenPackId; //packId
|
||||
uint256 public packCount; //pack registers
|
||||
uint256 public tokenCount; //tokens buys
|
||||
|
||||
//auxilary views
|
||||
mapping(bytes4 => uint256[]) private availablePacks; //array of available packs
|
||||
mapping(bytes4 => mapping(uint256 => uint256)) private availablePacksIndex; //position on array of available packs
|
||||
mapping(uint256 => mapping(bytes4 => uint256)) private packCategoryIndex;
|
||||
/**
|
||||
* @dev can only be called when market is open
|
||||
*/
|
||||
modifier marketManagement {
|
||||
require(state == State.Open || (msg.sender == controller && state == State.Controlled), "Market Disabled");
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev can only be called when market is open
|
||||
*/
|
||||
modifier marketSell {
|
||||
require(state == State.Open || state == State.BuyOnly || (msg.sender == controller && state == State.Controlled), "Market Disabled");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier packOwner(uint256 _packId) {
|
||||
require(msg.sender == controller || packs[_packId].owner == msg.sender);
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param _snt SNT token
|
||||
*/
|
||||
constructor(
|
||||
ERC20Token _snt
|
||||
)
|
||||
public
|
||||
{
|
||||
snt = _snt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Mints NFT StickerPack in `msg.sender` account, and Transfers SNT using user allowance
|
||||
* emit NonfungibleToken.Transfer(`address(0)`, `msg.sender`, `tokenId`)
|
||||
* @notice buy a pack from market pack owner, including a StickerPack's token in msg.sender account with same metadata of `_packId`
|
||||
* @param _packId id of market pack
|
||||
* @param _destination owner of token being brought
|
||||
* @return tokenId generated StickerPack token
|
||||
*/
|
||||
function buyToken(
|
||||
uint256 _packId,
|
||||
address _destination
|
||||
)
|
||||
external
|
||||
returns (uint256 tokenId)
|
||||
{
|
||||
return buy(msg.sender, _packId, _destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev emits StickerMarket.Register(`packId`, `_urlHash`, `_price`, `_contenthash`)
|
||||
* @notice Registers to sell a sticker pack
|
||||
* @param _price cost in wei to users minting with _urlHash metadata
|
||||
* @param _donate optional amount of `_price` that is donated to StickerMarket at every buy
|
||||
* @param _category listing category
|
||||
* @param _owner address of the beneficiary of buys
|
||||
* @param _contenthash EIP1577 pack contenthash for listings
|
||||
* @return packId Market position of Sticker Pack data.
|
||||
*/
|
||||
function registerPack(
|
||||
uint256 _price,
|
||||
uint256 _donate,
|
||||
bytes4[] calldata _category,
|
||||
address _owner,
|
||||
bytes calldata _contenthash
|
||||
)
|
||||
external
|
||||
returns(uint256 packId)
|
||||
{
|
||||
packId = register(msg.sender, _category, _owner, _price, _donate, _contenthash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice changes beneficiary of `_packId`, can only be called when market is open
|
||||
* @param _packId which market position is being transfered
|
||||
* @param _to new beneficiary
|
||||
*/
|
||||
function setPackOwner(uint256 _packId, address _to)
|
||||
external
|
||||
marketManagement
|
||||
packOwner(_packId)
|
||||
{
|
||||
packs[_packId].owner = _to;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice changes price of `_packId`, can only be called when market is open
|
||||
* @param _packId which market position is being transfered
|
||||
* @param _price new value
|
||||
*/
|
||||
function setPackPrice(uint256 _packId, uint256 _price, uint256 _donate)
|
||||
external
|
||||
marketManagement
|
||||
packOwner(_packId)
|
||||
{
|
||||
require(_donate <= 10000, "Bad argument, _donate cannot be more then 100.00%");
|
||||
packs[_packId].price = _price;
|
||||
packs[_packId].donate = _donate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice changes caregory of `_packId`, can only be called when market is open
|
||||
* @param _packId which market position is being transfered
|
||||
* @param _category new category
|
||||
*/
|
||||
|
||||
function addPackCategory(uint256 _packId, bytes4 _category)
|
||||
external
|
||||
marketManagement
|
||||
packOwner(_packId)
|
||||
{
|
||||
addAvailablePack(_packId, _category);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice changes caregory of `_packId`, can only be called when market is open
|
||||
* @param _packId which market position is being transfered
|
||||
* @param _category category to unlist
|
||||
*/
|
||||
|
||||
function removePackCategory(uint256 _packId, bytes4 _category)
|
||||
external
|
||||
marketManagement
|
||||
packOwner(_packId)
|
||||
{
|
||||
removeAvailablePack(_packId, _category);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice
|
||||
* @param _packId position edit
|
||||
*/
|
||||
function setPackState(uint256 _packId, bool _mintable)
|
||||
external
|
||||
marketManagement
|
||||
packOwner(_packId)
|
||||
{
|
||||
packs[_packId].mintable = _mintable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice MiniMeToken ApproveAndCallFallBack forwarder for registerPack and buyToken
|
||||
* @param _from account calling "approve and buy"
|
||||
* @param _token must be exactly SNT contract
|
||||
* @param _data abi encoded call
|
||||
*/
|
||||
function receiveApproval(
|
||||
address _from,
|
||||
uint256,
|
||||
address _token,
|
||||
bytes calldata _data
|
||||
)
|
||||
external
|
||||
{
|
||||
require(_token == address(snt), "Bad token");
|
||||
require(_token == address(msg.sender), "Bad call");
|
||||
bytes4 sig = abiDecodeSig(_data);
|
||||
bytes memory cdata = slice(_data,4,_data.length-4);
|
||||
if(sig == bytes4(keccak256("buyToken(uint256,address)"))){
|
||||
require(cdata.length == 64, "Bad data length");
|
||||
(uint256 packId, address owner) = abi.decode(cdata, (uint256, address));
|
||||
buy(_from, packId, owner);
|
||||
} else if(sig == bytes4(keccak256("registerPack(uint256,uint256,bytes4[],address,bytes)"))) {
|
||||
require(cdata.length >= 156, "Bad data length");
|
||||
(uint256 _price, uint256 _donate, bytes4[] memory _category, address _owner, bytes memory _contenthash) = abi.decode(cdata, (uint256,uint256,bytes4[],address,bytes));
|
||||
register(_from, _category, _owner, _price, _donate, _contenthash);
|
||||
} else {
|
||||
revert("Bad call");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice changes contenthash of `_packId`, can only be called by controller
|
||||
* @param _packId which market position is being altered
|
||||
* @param _contenthash new contenthash
|
||||
*/
|
||||
function setPackContenthash(uint256 _packId, bytes calldata _contenthash)
|
||||
external
|
||||
onlyController
|
||||
{
|
||||
packs[_packId].contenthash = _contenthash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice removes all market data about a marketed pack, can only be called by listing owner or market controller, and when market is open
|
||||
* @param _packId position to be deleted
|
||||
*/
|
||||
function purgePack(uint256 _packId, uint256 _limit)
|
||||
external
|
||||
onlyController
|
||||
{
|
||||
bytes4[] memory _category = packs[_packId].category;
|
||||
uint limit;
|
||||
if(_limit == 0) {
|
||||
limit = _category.length;
|
||||
} else {
|
||||
require(_limit <= _category.length, "Bad limit");
|
||||
limit = _limit;
|
||||
}
|
||||
|
||||
uint256 len = _category.length;
|
||||
if(len > 0){
|
||||
len--;
|
||||
}
|
||||
for(uint i = 0; i < limit; i++){
|
||||
removeAvailablePack(_packId, _category[len-i]);
|
||||
}
|
||||
|
||||
if(packs[_packId].category.length == 0){
|
||||
delete packs[_packId];
|
||||
emit Unregister(_packId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice changes market state, only controller can call.
|
||||
* @param _state new state
|
||||
*/
|
||||
function setMarketState(State _state)
|
||||
external
|
||||
onlyController
|
||||
{
|
||||
state = _state;
|
||||
emit MarketState(_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice changes register fee, only controller can call.
|
||||
* @param _value new register fee
|
||||
*/
|
||||
function setRegisterFee(uint256 _value)
|
||||
external
|
||||
onlyController
|
||||
{
|
||||
registerFee = _value;
|
||||
emit RegisterFee(_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice changes burn rate, only controller can call.
|
||||
* @param _value new burn rate
|
||||
*/
|
||||
function setBurnRate(uint256 _value)
|
||||
external
|
||||
onlyController
|
||||
{
|
||||
burnRate = _value;
|
||||
require(_value <= 10000, "cannot be more then 100.00%");
|
||||
emit BurnRate(_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice controller can generate tokens at will
|
||||
* @param _owner account being included new token
|
||||
* @param _packId pack being minted
|
||||
* @return tokenId created
|
||||
*/
|
||||
function generateToken(address _owner, uint256 _packId)
|
||||
external
|
||||
onlyController
|
||||
returns (uint256 tokenId)
|
||||
{
|
||||
return mintStickerPack(_owner, _packId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
external
|
||||
onlyController
|
||||
{
|
||||
if (_token == address(0)) {
|
||||
address(controller).transfer(address(this).balance);
|
||||
return;
|
||||
}
|
||||
ERC20Token token = ERC20Token(_token);
|
||||
uint256 balance = token.balanceOf(address(this));
|
||||
token.transfer(controller, balance);
|
||||
emit ClaimedTokens(_token, controller, balance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice read available market ids in a category (might be slow)
|
||||
* @return array of market id registered
|
||||
*/
|
||||
function getAvailablePacks(bytes4 _category)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory availableIds)
|
||||
{
|
||||
return availablePacks[_category];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice count total packs in a category
|
||||
* @return lenght
|
||||
*/
|
||||
function getCategoryLength(bytes4 _category)
|
||||
external
|
||||
view
|
||||
returns (uint256 size)
|
||||
{
|
||||
size = availablePacks[_category].length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice read packId of a category index
|
||||
* @return packId
|
||||
*/
|
||||
function getCategoryPack(bytes4 _category, uint256 _index)
|
||||
external
|
||||
view
|
||||
returns (uint256 packId)
|
||||
{
|
||||
packId = availablePacks[_category][_index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice returns all data from pack in market
|
||||
*/
|
||||
function getPackData(uint256 _packId)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
bytes4[] memory category,
|
||||
address owner,
|
||||
bool mintable,
|
||||
uint256 timestamp,
|
||||
uint256 price,
|
||||
bytes memory contenthash
|
||||
)
|
||||
{
|
||||
Pack memory pack = packs[_packId];
|
||||
return (
|
||||
pack.category,
|
||||
pack.owner,
|
||||
pack.mintable,
|
||||
pack.timestamp,
|
||||
pack.price,
|
||||
pack.contenthash
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice returns relevant token data
|
||||
*/
|
||||
function getTokenData(uint256 _tokenId)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
bytes4[] memory category,
|
||||
uint256 timestamp,
|
||||
bytes memory contenthash
|
||||
)
|
||||
{
|
||||
Pack memory pack = getTokenPack(_tokenId);
|
||||
return (
|
||||
pack.category,
|
||||
pack.timestamp,
|
||||
pack.contenthash
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev register new pack to owner
|
||||
*/
|
||||
function register(
|
||||
address _caller,
|
||||
bytes4[] memory _category,
|
||||
address _owner,
|
||||
uint256 _price,
|
||||
uint256 _donate,
|
||||
bytes memory _contenthash
|
||||
)
|
||||
internal
|
||||
marketManagement
|
||||
returns(uint256 packId)
|
||||
{
|
||||
if(registerFee > 0){
|
||||
require(snt.transferFrom(_caller, address(this), registerFee), "Bad payment");
|
||||
}
|
||||
require(_donate <= 10000, "Bad argument, _donate cannot be more then 100.00%");
|
||||
packId = packCount++;
|
||||
packs[packId] = Pack(new bytes4[](0), _owner, true, block.timestamp, _price, _donate, _contenthash);
|
||||
for(uint i = 0;i < _category.length; i++){
|
||||
addAvailablePack(packId, _category[i]);
|
||||
}
|
||||
|
||||
emit Register(packId, _price, _contenthash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev transfer SNT from buyer to pack owner and mint sticker pack token
|
||||
*/
|
||||
function buy(
|
||||
address _caller,
|
||||
uint256 _packId,
|
||||
address _destination
|
||||
)
|
||||
internal
|
||||
marketSell
|
||||
returns (uint256 tokenId)
|
||||
{
|
||||
Pack memory _pack = packs[_packId];
|
||||
require(_pack.owner != address(0), "Bad pack");
|
||||
require(_pack.mintable, "Disabled");
|
||||
uint256 amount = _pack.price;
|
||||
require(amount > 0, "Unauthorized");
|
||||
if(amount > 0 && burnRate > 0) {
|
||||
uint256 burned = (amount * burnRate) / 10000;
|
||||
amount -= burned;
|
||||
require(snt.transferFrom(_caller, Controlled(address(snt)).controller(), burned), "Bad burn");
|
||||
}
|
||||
if(amount > 0 && _pack.donate > 0) {
|
||||
uint256 donate = (amount * _pack.donate) / 10000;
|
||||
amount -= donate;
|
||||
require(snt.transferFrom(_caller, address(this), donate), "Bad donate");
|
||||
}
|
||||
if(amount > 0) {
|
||||
require(snt.transferFrom(_caller, _pack.owner, amount), "Bad payment");
|
||||
}
|
||||
return mintStickerPack(_destination, _packId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev creates new NFT
|
||||
*/
|
||||
function mintStickerPack(
|
||||
address _owner,
|
||||
uint256 _packId
|
||||
)
|
||||
internal
|
||||
returns (uint256 tokenId)
|
||||
{
|
||||
tokenId = tokenCount++;
|
||||
tokenPackId[tokenId] = _packId;
|
||||
mint(_owner, tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev adds id from "available list"
|
||||
*/
|
||||
function addAvailablePack(uint256 _packId, bytes4 _category) private {
|
||||
require(packCategoryIndex[_packId][_category] == 0, "Duplicate categorization");
|
||||
availablePacksIndex[_category][_packId] = availablePacks[_category].push(_packId);
|
||||
packCategoryIndex[_packId][_category] = packs[_packId].category.push(_category);
|
||||
emit Categorized(_category, _packId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev remove id from "available list"
|
||||
*/
|
||||
function removeAvailablePack(uint256 _packId, bytes4 _category) private {
|
||||
uint pos = availablePacksIndex[_category][_packId];
|
||||
require(pos > 0, "Not categorized [1]");
|
||||
delete availablePacksIndex[_category][_packId];
|
||||
if(pos != availablePacks[_category].length){
|
||||
uint256 movedElement = availablePacks[_category][availablePacks[_category].length-1]; //tokenId;
|
||||
availablePacks[_category][pos-1] = movedElement;
|
||||
availablePacksIndex[_category][movedElement] = pos;
|
||||
}
|
||||
availablePacks[_category].length--;
|
||||
|
||||
uint pos2 = packCategoryIndex[_packId][_category];
|
||||
require(pos2 > 0, "Not categorized [2]");
|
||||
delete packCategoryIndex[_packId][_category];
|
||||
if(pos2 != packs[_packId].category.length){
|
||||
bytes4 movedElement2 = packs[_packId].category[packs[_packId].category.length-1]; //tokenId;
|
||||
packs[_packId].category[pos2-1] = movedElement2;
|
||||
packCategoryIndex[_packId][movedElement2] = pos2;
|
||||
}
|
||||
packs[_packId].category.length--;
|
||||
emit Uncategorized(_category, _packId);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev reads token pack data
|
||||
*/
|
||||
function getTokenPack(uint256 _tokenId) private view returns(Pack memory pack){
|
||||
pack = packs[tokenPackId[_tokenId]];
|
||||
}
|
||||
|
||||
|
||||
function abiDecodeSig(bytes memory _data) private pure returns(bytes4 sig){
|
||||
assembly {
|
||||
sig := mload(add(_data, add(0x20, 0)))
|
||||
}
|
||||
}
|
||||
|
||||
function slice(bytes memory _bytes, uint _start, uint _length) private pure returns (bytes memory) {
|
||||
require(_bytes.length >= (_start + _length));
|
||||
|
||||
bytes memory tempBytes;
|
||||
|
||||
assembly {
|
||||
switch iszero(_length)
|
||||
case 0 {
|
||||
// Get a location of some free memory and store it in tempBytes as
|
||||
// Solidity does for memory variables.
|
||||
tempBytes := mload(0x40)
|
||||
|
||||
// The first word of the slice result is potentially a partial
|
||||
// word read from the original array. To read it, we calculate
|
||||
// the length of that partial word and start copying that many
|
||||
// bytes into the array. The first word we copy will start with
|
||||
// data we don't care about, but the last `lengthmod` bytes will
|
||||
// land at the beginning of the contents of the new array. When
|
||||
// we're done copying, we overwrite the full first word with
|
||||
// the actual length of the slice.
|
||||
let lengthmod := and(_length, 31)
|
||||
|
||||
// The multiplication in the next line is necessary
|
||||
// because when slicing multiples of 32 bytes (lengthmod == 0)
|
||||
// the following copy loop was copying the origin's length
|
||||
// and then ending prematurely not copying everything it should.
|
||||
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
|
||||
let end := add(mc, _length)
|
||||
|
||||
for {
|
||||
// The multiplication in the next line has the same exact purpose
|
||||
// as the one above.
|
||||
let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
|
||||
} lt(mc, end) {
|
||||
mc := add(mc, 0x20)
|
||||
cc := add(cc, 0x20)
|
||||
} {
|
||||
mstore(mc, mload(cc))
|
||||
}
|
||||
|
||||
mstore(tempBytes, _length)
|
||||
|
||||
//update free-memory pointer
|
||||
//allocating the array padded to 32 bytes like the compiler does now
|
||||
mstore(0x40, and(add(mc, 31), not(31)))
|
||||
}
|
||||
//if we want a zero-length slice let's just return a zero-length array
|
||||
default {
|
||||
tempBytes := mload(0x40)
|
||||
|
||||
mstore(0x40, add(tempBytes, 0x20))
|
||||
}
|
||||
}
|
||||
|
||||
return tempBytes;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
interface ApproveAndCallFallBack {
|
||||
function receiveApproval(address from, uint256 _amount, address _token, bytes calldata _data) external;
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
pragma solidity >=0.5.0 <0.6.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, "Bad argument");
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
pragma solidity >=0.5.0 <0.6.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);
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
/**
|
||||
* @title ERC-721 Non-Fungible Token Standard
|
||||
* @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
|
||||
* Note: the ERC-165 identifier for this interface is 0x80ac58cd
|
||||
*/
|
||||
interface ERC721 /* is ERC165 */ {
|
||||
/**
|
||||
* @dev This emits when ownership of any NFT changes by any mechanism.
|
||||
* This event emits when NFTs are created (`from` == 0) and destroyed
|
||||
* (`to` == 0). Exception: during contract creation, any number of NFTs
|
||||
* may be created and assigned without emitting Transfer. At the time of
|
||||
* any transfer, the approved address for that NFT (if any) is reset to none.
|
||||
*/
|
||||
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
|
||||
|
||||
/**
|
||||
* @dev This emits when the approved address for an NFT is changed or
|
||||
* reaffirmed. The zero address indicates there is no approved address.
|
||||
* When a Transfer event emits, this also indicates that the approved
|
||||
* address for that NFT (if any) is reset to none.
|
||||
*/
|
||||
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
|
||||
|
||||
/**
|
||||
* @dev This emits when an operator is enabled or disabled for an owner.
|
||||
* The operator can manage all NFTs of the owner.
|
||||
*/
|
||||
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
|
||||
|
||||
/**
|
||||
* @notice Count all NFTs assigned to an owner
|
||||
* @dev NFTs assigned to the zero address are considered invalid, and this
|
||||
* function throws for queries about the zero address.
|
||||
* @param _owner An address for whom to query the balance
|
||||
* @return The number of NFTs owned by `_owner`, possibly zero
|
||||
*/
|
||||
function balanceOf(address _owner) external view returns (uint256);
|
||||
|
||||
/**
|
||||
* @notice Find the owner of an NFT
|
||||
* @dev NFTs assigned to zero address are considered invalid, and queries
|
||||
* about them do throw.
|
||||
* @param _tokenId The identifier for an NFT
|
||||
* @return The address of the owner of the NFT
|
||||
*/
|
||||
function ownerOf(uint256 _tokenId) external view returns (address);
|
||||
|
||||
/**
|
||||
* @notice Transfers the ownership of an NFT from one address to another address
|
||||
* @dev Throws unless `msg.sender` is the current owner, an authorized
|
||||
* operator, or the approved address for this NFT. Throws if `_from` is
|
||||
* not the current owner. Throws if `_to` is the zero address. Throws if
|
||||
* `_tokenId` is not a valid NFT. When transfer is complete, this function
|
||||
* checks if `_to` is a smart contract (code size > 0). If so, it calls
|
||||
* `onERC721Received` on `_to` and throws if the return value is not
|
||||
* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
|
||||
* @param _from The current owner of the NFT
|
||||
* @param _to The new owner
|
||||
* @param _tokenId The NFT to transfer
|
||||
* @param _data Additional data with no specified format, sent in call to `_to`
|
||||
*/
|
||||
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata _data) external;
|
||||
|
||||
/**
|
||||
* @notice Transfers the ownership of an NFT from one address to another address
|
||||
* @dev This works identically to the other function with an extra data parameter,
|
||||
* except this function just sets data to ""
|
||||
* @param _from The current owner of the NFT
|
||||
* @param _to The new owner
|
||||
* @param _tokenId The NFT to transfer
|
||||
*/
|
||||
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external;
|
||||
|
||||
/**
|
||||
* @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
|
||||
* TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
|
||||
* THEY MAY BE PERMANENTLY LOST
|
||||
* @dev Throws unless `msg.sender` is the current owner, an authorized
|
||||
* operator, or the approved address for this NFT. Throws if `_from` is
|
||||
* not the current owner. Throws if `_to` is the zero address. Throws if
|
||||
* `_tokenId` is not a valid NFT.
|
||||
* @param _from The current owner of the NFT
|
||||
* @param _to The new owner
|
||||
* @param _tokenId The NFT to transfer
|
||||
*/
|
||||
function transferFrom(address _from, address _to, uint256 _tokenId) external;
|
||||
|
||||
/**
|
||||
* @notice Set or reaffirm the approved address for an NFT
|
||||
* @dev The zero address indicates there is no approved address.
|
||||
* @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
|
||||
* operator of the current owner.
|
||||
* @param _approved The new approved NFT controller
|
||||
* @param _tokenId The NFT to approve
|
||||
*/
|
||||
function approve(address _approved, uint256 _tokenId) external;
|
||||
|
||||
/**
|
||||
* @notice Enable or disable approval for a third party ("operator") to manage
|
||||
* all of `msg.sender`'s assets.
|
||||
* @dev Emits the ApprovalForAll event. The contract MUST allow
|
||||
* multiple operators per owner.
|
||||
* @param _operator Address to add to the set of authorized operators.
|
||||
* @param _approved True if the operator is approved, false to revoke approval
|
||||
*/
|
||||
function setApprovalForAll(address _operator, bool _approved) external;
|
||||
|
||||
/**
|
||||
* @notice Get the approved address for a single NFT
|
||||
* @dev Throws if `_tokenId` is not a valid NFT
|
||||
* @param _tokenId The NFT to find the approved address for
|
||||
* @return The approved address for this NFT, or the zero address if there is none
|
||||
*/
|
||||
function getApproved(uint256 _tokenId) external view returns (address);
|
||||
|
||||
/**
|
||||
* @notice Query if an address is an authorized operator for another address
|
||||
* @param _owner The address that owns the NFTs
|
||||
* @param _operator The address that acts on behalf of the owner
|
||||
* @return True if `_operator` is an approved operator for `_owner`, false otherwise
|
||||
*/
|
||||
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
interface ERC721Receiver {
|
||||
/**
|
||||
* @notice Handle the receipt of an NFT
|
||||
* @dev The ERC721 smart contract calls this function on the
|
||||
* recipient after a `transfer`. This function MAY throw to revert and reject the transfer. Return
|
||||
* of other than the magic value MUST result in the transaction being reverted.
|
||||
* @notice The contract address is always the message sender.
|
||||
* @param _operator The address which called `safeTransferFrom` function
|
||||
* @param _from The address which previously owned the token
|
||||
* @param _tokenId The NFT identifier which is being transferred
|
||||
* @param _data Additional data with no specified format
|
||||
* @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
|
||||
* unless throwing
|
||||
*/
|
||||
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4);
|
||||
}
|
|
@ -0,0 +1,633 @@
|
|||
pragma solidity >=0.5.0 <0.6.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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* @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 "./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 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, "Transfers disabled");
|
||||
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, "Transfers disabled");
|
||||
|
||||
// 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, "Invalid block.number");
|
||||
|
||||
// Do not allow transfer to 0x0 or the token contract itself
|
||||
require((_to != address(0)) && (_to != address(this)), "Invalid _to");
|
||||
|
||||
// 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), "Unauthorized transfer");
|
||||
}
|
||||
|
||||
// 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, "Balance overflow"); // 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, "Transfers disabled");
|
||||
|
||||
// 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), "Reset allowance first");
|
||||
|
||||
// Alerts the token controller of the approve function call
|
||||
if (isContract(controller)) {
|
||||
require(TokenController(controller).onApprove(_from, _spender, _amount), "Unauthorized approve");
|
||||
}
|
||||
|
||||
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) {
|
||||
return 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), "Approve failed");
|
||||
|
||||
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, "Total overflow"); // Check for overflow
|
||||
uint previousBalanceTo = balanceOfAt(_owner, block.number);
|
||||
require(previousBalanceTo + _amount >= previousBalanceTo, "Balance overflow"); // 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, "No enough supply");
|
||||
uint previousBalanceFrom = balanceOfAt(_owner, block.number);
|
||||
require(previousBalanceFrom >= _amount, "No enough balance");
|
||||
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), "Deposit unallowed");
|
||||
require(TokenController(controller).proxyPayment.value(msg.value)(msg.sender), "Deposit denied");
|
||||
}
|
||||
|
||||
//////////
|
||||
// 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
|
||||
);
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
pragma solidity >=0.5.0 <0.6.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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
import "./ERC721.sol";
|
||||
import "./ERC721Receiver.sol";
|
||||
import "../common/SafeMath.sol";
|
||||
import "../common/Address.sol";
|
||||
import "../common/Introspective.sol";
|
||||
|
||||
/**
|
||||
* @title ERC721 Non-Fungible Token Standard basic implementation
|
||||
* @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
|
||||
* Based on https://github.com/OpenZeppelin/openzeppelin-solidity/blob/8dd92fd6cafe4e4ce7b8ae297a4ba1212ca21ca6/contracts/token/ERC721/ERC721.sol
|
||||
*/
|
||||
contract NonfungibleToken is Introspective, ERC721 {
|
||||
using SafeMath for uint256;
|
||||
using Address for address;
|
||||
|
||||
// Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
|
||||
// which can be also obtained as `ERC721Receiver(0).onERC721Received.selector`
|
||||
bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;
|
||||
|
||||
// Mapping from token ID to owner
|
||||
mapping (uint256 => address) private _tokenOwner;
|
||||
|
||||
// Mapping from token ID to approved address
|
||||
mapping (uint256 => address) private _tokenApprovals;
|
||||
|
||||
// Cache view of user token list
|
||||
mapping (address => uint256[]) private _ownedTokens;
|
||||
mapping (uint256 => uint256) private _ownedTokensPos;
|
||||
|
||||
// Mapping from owner to operator approvals
|
||||
mapping (address => mapping (address => bool)) private _operatorApprovals;
|
||||
|
||||
bytes4 private constant _InterfaceId_ERC721 = 0x80ac58cd;
|
||||
/*
|
||||
* 0x80ac58cd ===
|
||||
* bytes4(keccak256('balanceOf(address)')) ^
|
||||
* bytes4(keccak256('ownerOf(uint256)')) ^
|
||||
* bytes4(keccak256('approve(address,uint256)')) ^
|
||||
* bytes4(keccak256('getApproved(uint256)')) ^
|
||||
* bytes4(keccak256('setApprovalForAll(address,bool)')) ^
|
||||
* bytes4(keccak256('isApprovedForAll(address,address)')) ^
|
||||
* bytes4(keccak256('transferFrom(address,address,uint256)')) ^
|
||||
* bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^
|
||||
* bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)'))
|
||||
*/
|
||||
|
||||
modifier approvedOrOwner(uint256 tokenId){
|
||||
address spender = msg.sender;
|
||||
address owner = getOwner(tokenId);
|
||||
// Disable solium check because of
|
||||
// https://github.com/duaraghav8/Solium/issues/175
|
||||
// solium-disable-next-line operator-whitespace
|
||||
require(spender == owner || _tokenApprovals[tokenId] == spender || _operatorApprovals[owner][spender], "Unauthorized");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier safe(address from, address to, uint256 tokenId, bytes memory _data) {
|
||||
_;
|
||||
if (to.isContract()) {
|
||||
bytes4 retval = ERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data);
|
||||
require(retval == _ERC721_RECEIVED, "Bad operation");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
constructor () public {
|
||||
// register the supported interfaces to conform to ERC721 via ERC165
|
||||
_registerInterface(_InterfaceId_ERC721);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets the balance of the specified address
|
||||
* @param owner address to query the balance of
|
||||
* @return uint256 representing the amount owned by the passed address
|
||||
*/
|
||||
function balanceOf(address owner) external view returns (uint256) {
|
||||
require(owner != address(0), "Bad address");
|
||||
return _ownedTokens[owner].length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets the owner of the specified token ID
|
||||
* @param tokenId uint256 ID of the token to query the owner of
|
||||
* @return owner address currently marked as the owner of the given token ID
|
||||
*/
|
||||
function ownerOf(uint256 tokenId) external view returns (address) {
|
||||
return getOwner(tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Approves another address to transfer the given token ID
|
||||
* The zero address indicates there is no approved address.
|
||||
* There can only be one approved address per token at a given time.
|
||||
* Can only be called by the token owner or an approved operator.
|
||||
* @param to address to be approved for the given token ID
|
||||
* @param tokenId uint256 ID of the token to be approved
|
||||
*/
|
||||
function approve(address to, uint256 tokenId) external {
|
||||
address owner = getOwner(tokenId);
|
||||
require(to != owner, "Bad operation");
|
||||
require(msg.sender == owner || _operatorApprovals[owner][msg.sender], "Unauthorized");
|
||||
|
||||
_tokenApprovals[tokenId] = to;
|
||||
emit Approval(owner, to, tokenId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @dev Gets the approved address for a token ID, or zero if no address set
|
||||
* Reverts if the token ID does not exist.
|
||||
* @param tokenId uint256 ID of the token to query the approval of
|
||||
* @return address currently approved for the given token ID
|
||||
*/
|
||||
function getApproved(uint256 tokenId) external view returns (address) {
|
||||
require(exists(tokenId), "Bad token");
|
||||
return _tokenApprovals[tokenId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets or unsets the approval of a given operator
|
||||
* An operator is allowed to transfer all tokens of the sender on their behalf
|
||||
* @param to operator address to set the approval
|
||||
* @param approved representing the status of the approval to be set
|
||||
*/
|
||||
function setApprovalForAll(address to, bool approved) external {
|
||||
require(to != msg.sender, "Bad operation");
|
||||
_operatorApprovals[msg.sender][to] = approved;
|
||||
emit ApprovalForAll(msg.sender, to, approved);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Tells whether an operator is approved by a given owner
|
||||
* @param owner owner address which you want to query the approval of
|
||||
* @param operator operator address which you want to query the approval of
|
||||
* @return bool whether the given operator is approved by the given owner
|
||||
*/
|
||||
function isApprovedForAll(address owner, address operator) external view returns (bool) {
|
||||
return _operatorApprovals[owner][operator];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Transfers the ownership of a given token ID to another address
|
||||
* Usage of this method is discouraged, use `safeTransferFrom` whenever possible
|
||||
* Requires the msg sender to be the owner, approved, or operator
|
||||
* @param from current owner of the token
|
||||
* @param to address to receive the ownership of the given token ID
|
||||
* @param tokenId uint256 ID of the token to be transferred
|
||||
*/
|
||||
function transferFrom(address from, address to, uint256 tokenId) external approvedOrOwner(tokenId) {
|
||||
transfer(from, to, tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Safely transfers the ownership of a given token ID to another address
|
||||
* If the target address is a contract, it must implement `onERC721Received`,
|
||||
* which is called upon a safe transfer, and return the magic value
|
||||
* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
|
||||
* the transfer is reverted.
|
||||
*
|
||||
* Requires the msg sender to be the owner, approved, or operator
|
||||
* @param from current owner of the token
|
||||
* @param to address to receive the ownership of the given token ID
|
||||
* @param tokenId uint256 ID of the token to be transferred
|
||||
*/
|
||||
function safeTransferFrom(address from, address to, uint256 tokenId)
|
||||
external
|
||||
approvedOrOwner(tokenId)
|
||||
safe(from, to, tokenId, "")
|
||||
{
|
||||
transfer(from, to, tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Safely transfers the ownership of a given token ID to another address
|
||||
* If the target address is a contract, it must implement `onERC721Received`,
|
||||
* which is called upon a safe transfer, and return the magic value
|
||||
* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
|
||||
* the transfer is reverted.
|
||||
* Requires the msg sender to be the owner, approved, or operator
|
||||
* @param from current owner of the token
|
||||
* @param to address to receive the ownership of the given token ID
|
||||
* @param tokenId uint256 ID of the token to be transferred
|
||||
* @param _data bytes data to send along with a safe transfer check
|
||||
*/
|
||||
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata _data)
|
||||
external
|
||||
approvedOrOwner(tokenId)
|
||||
safe(from, to, tokenId, _data)
|
||||
{
|
||||
transfer(from, to, tokenId);
|
||||
}
|
||||
|
||||
function tokensOwnedBy(address owner) external view returns (uint256[] memory tokenList) {
|
||||
return _ownedTokens[owner];
|
||||
}
|
||||
/**
|
||||
* @dev Gets the token ID at a given index of the tokens list of the requested owner
|
||||
* @param owner address owning the tokens list to be accessed
|
||||
* @param index uint256 representing the index to be accessed of the requested tokens list
|
||||
* @return uint256 token ID at the given index of the tokens list owned by the requested address
|
||||
*/
|
||||
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256) {
|
||||
require(index < _ownedTokens[owner].length, "Index out of bounds");
|
||||
return _ownedTokens[owner][index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns whether the specified token exists
|
||||
* @param tokenId uint256 ID of the token to query the existence of
|
||||
* @return whether the token exists
|
||||
*/
|
||||
function exists(uint256 tokenId) internal view returns (bool) {
|
||||
address owner = _tokenOwner[tokenId];
|
||||
return owner != address(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function to mint a new token
|
||||
* Reverts if the given token ID already exists
|
||||
* @param to The address that will own the minted token
|
||||
* @param tokenId uint256 ID of the token to be minted
|
||||
*/
|
||||
function mint(address to, uint256 tokenId) internal {
|
||||
require(to != address(0), "Bad address");
|
||||
require(!exists(tokenId), "Bad operation");
|
||||
|
||||
_tokenOwner[tokenId] = to;
|
||||
addOwnedTokens(_ownedTokens[to], tokenId);
|
||||
emit Transfer(address(0), to, tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function to burn a specific token
|
||||
* Reverts if the token does not exist
|
||||
* Deprecated, use _burn(uint256) instead.
|
||||
* @param owner owner of the token to burn
|
||||
* @param tokenId uint256 ID of the token being burned
|
||||
*/
|
||||
function burn(address owner, uint256 tokenId) internal {
|
||||
require(getOwner(tokenId) == owner, "Unauthorized");
|
||||
|
||||
delete _tokenApprovals[tokenId];
|
||||
|
||||
_tokenOwner[tokenId] = address(0);
|
||||
removeOwnedTokens(_ownedTokens[owner], tokenId);
|
||||
emit Transfer(owner, address(0), tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function to burn a specific token
|
||||
* Reverts if the token does not exist
|
||||
* @param tokenId uint256 ID of the token being burned
|
||||
*/
|
||||
function burn(uint256 tokenId) internal {
|
||||
burn(getOwner(tokenId), tokenId);
|
||||
}
|
||||
/**
|
||||
* @dev Internal function to transfer ownership of a given token ID to another address.
|
||||
* As opposed to transferFrom, this imposes no restrictions on msg.sender.
|
||||
* @param from current owner of the token
|
||||
* @param to address to receive the ownership of the given token ID
|
||||
* @param tokenId uint256 ID of the token to be transferred
|
||||
*/
|
||||
function transfer(address from, address to, uint256 tokenId) internal {
|
||||
require(getOwner(tokenId) == from, "Unauthorized");
|
||||
require(to != address(0), "Bad address");
|
||||
|
||||
delete _tokenApprovals[tokenId];
|
||||
|
||||
_tokenOwner[tokenId] = to;
|
||||
removeOwnedTokens(_ownedTokens[from], tokenId);
|
||||
addOwnedTokens(_ownedTokens[to], tokenId);
|
||||
emit Transfer(from, to, tokenId);
|
||||
}
|
||||
|
||||
function addOwnedTokens(uint256[] storage tokenList, uint256 _tokenId) internal {
|
||||
_ownedTokensPos[_tokenId] = tokenList.push(_tokenId);
|
||||
}
|
||||
|
||||
function removeOwnedTokens(uint256[] storage tokenList, uint256 _tokenId) internal {
|
||||
uint pos = _ownedTokensPos[_tokenId];
|
||||
if(pos == 0) {
|
||||
return;
|
||||
}
|
||||
delete _ownedTokensPos[_tokenId];
|
||||
uint256 movedElement = tokenList[tokenList.length-1]; //tokenId;
|
||||
tokenList[pos-1] = movedElement;
|
||||
tokenList.length--;
|
||||
_ownedTokensPos[movedElement] = pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets the owner of the specified token ID
|
||||
* @param tokenId uint256 ID of the token to query the owner of
|
||||
* @return owner address currently marked as the owner of the given token ID
|
||||
*/
|
||||
function getOwner(uint256 tokenId) internal view returns (address) {
|
||||
address owner = _tokenOwner[tokenId];
|
||||
require(owner != address(0), "Bad token");
|
||||
return owner;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
import "./ERC20Token.sol";
|
||||
|
||||
contract StandardToken is ERC20Token {
|
||||
|
||||
uint256 public totalSupply;
|
||||
mapping (address => uint256) balances;
|
||||
mapping (address => mapping (address => uint256)) allowed;
|
||||
|
||||
constructor() internal { }
|
||||
|
||||
function transfer(
|
||||
address _to,
|
||||
uint256 _value
|
||||
)
|
||||
external
|
||||
returns (bool success)
|
||||
{
|
||||
return transfer(msg.sender, _to, _value);
|
||||
}
|
||||
|
||||
function approve(address _spender, uint256 _value)
|
||||
external
|
||||
returns (bool success)
|
||||
{
|
||||
allowed[msg.sender][_spender] = _value;
|
||||
emit Approval(msg.sender, _spender, _value);
|
||||
return true;
|
||||
}
|
||||
|
||||
function transferFrom(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _value
|
||||
)
|
||||
external
|
||||
returns (bool success)
|
||||
{
|
||||
if (balances[_from] >= _value &&
|
||||
allowed[_from][msg.sender] >= _value &&
|
||||
_value > 0) {
|
||||
allowed[_from][msg.sender] -= _value;
|
||||
return transfer(_from, _to, _value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function allowance(address _owner, address _spender)
|
||||
external
|
||||
view
|
||||
returns (uint256 remaining)
|
||||
{
|
||||
return allowed[_owner][_spender];
|
||||
}
|
||||
|
||||
function balanceOf(address _owner)
|
||||
external
|
||||
view
|
||||
returns (uint256 balance)
|
||||
{
|
||||
return balances[_owner];
|
||||
}
|
||||
|
||||
function mint(
|
||||
address _to,
|
||||
uint256 _amount
|
||||
)
|
||||
internal
|
||||
{
|
||||
balances[_to] += _amount;
|
||||
totalSupply += _amount;
|
||||
emit Transfer(address(0x0), _to, _amount);
|
||||
}
|
||||
|
||||
function transfer(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _value
|
||||
)
|
||||
internal
|
||||
returns (bool success)
|
||||
{
|
||||
if (balances[_from] >= _value && _value > 0) {
|
||||
balances[_from] -= _value;
|
||||
balances[_to] += _value;
|
||||
emit Transfer(_from, _to, _value);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
pragma solidity >=0.5.0 <0.6.0;
|
||||
|
||||
import "./StandardToken.sol";
|
||||
|
||||
/**
|
||||
* @notice ERC20Token for test scripts, can be minted by anyone.
|
||||
*/
|
||||
contract TestToken is StandardToken {
|
||||
|
||||
constructor() public { }
|
||||
|
||||
/**
|
||||
* @notice any caller can mint any `_amount`
|
||||
* @param _amount how much to be minted
|
||||
*/
|
||||
function mint(uint256 _amount) public {
|
||||
mint(msg.sender, _amount);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
pragma solidity >=0.5.0 <0.6.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);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"contracts": ["contracts/**"],
|
||||
"app": {
|
||||
"js/dapp.js": ["app/dapp.js"],
|
||||
"js/index.js": ["app/index.js"],
|
||||
"index.html": "app/index.html",
|
||||
"images/": ["app/images/**"]
|
||||
},
|
||||
"buildDir": "dist/",
|
||||
"config": "config/",
|
||||
"versions": {
|
||||
"web3": "1.0.0-beta",
|
||||
"solc": "0.5.4",
|
||||
"ipfs-api": "17.2.4",
|
||||
"p-iteration": "1.1.7"
|
||||
},
|
||||
"plugins": {}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "status-contracts",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"solidity-coverage": "./node_modules/.bin/solidity-coverage",
|
||||
"test": "embark test",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/status-im/contracts.git"
|
||||
},
|
||||
"author": "Status Research & Development GMBH",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/status-im/contracts/issues"
|
||||
},
|
||||
"homepage": "https://github.com/status-im/contracts#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"elliptic-curve": "^0.1.0",
|
||||
"ethereumjs-util": "^5.1.5",
|
||||
"react": "^16.3.2",
|
||||
"react-blockies": "^1.4.0",
|
||||
"react-bootstrap": "0.32.1",
|
||||
"react-dom": "^16.3.2",
|
||||
"web3": "^1.0.0-beta.34"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
exports.Test = (Controlled) => {
|
||||
describe("Controlled", async function() {
|
||||
this.timeout(0);
|
||||
var accounts;
|
||||
before(function(done) {
|
||||
web3.eth.getAccounts().then(function (res) {
|
||||
accounts = res;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should start with msg.sender as controller", async function() {
|
||||
var controller = await Controlled.methods.controller().call();
|
||||
assert(controller, accounts[0]);
|
||||
});
|
||||
|
||||
it("should allow controller to set new controller", async function() {
|
||||
await Controlled.methods.changeController(accounts[1]).send({from: accounts[0]});
|
||||
var controller = await Controlled.methods.controller().call();
|
||||
assert(controller, accounts[1]);
|
||||
});
|
||||
|
||||
it("should set back to original controller", async function() {
|
||||
await Controlled.methods.changeController(accounts[0]).send({from: accounts[1]});
|
||||
var controller = await Controlled.methods.controller().call();
|
||||
assert(controller, accounts[0]);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
|
||||
const ERC20Receiver = require('Embark/contracts/ERC20Receiver');
|
||||
|
||||
exports.config = {
|
||||
contracts : {
|
||||
"ERC20Receiver": {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.Test = (ERC20Token) => {
|
||||
describe("ERC20Token", function() {
|
||||
|
||||
var accounts;
|
||||
before(function(done) {
|
||||
web3.eth.getAccounts().then(function (res) {
|
||||
accounts = res;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should transfer 1 token", async function() {
|
||||
let initialBalance0 = await ERC20Token.methods.balanceOf(accounts[0]).call();
|
||||
let initialBalance1 = await ERC20Token.methods.balanceOf(accounts[1]).call();
|
||||
await ERC20Token.methods.transfer(accounts[1],1).send({from: accounts[0]});
|
||||
let result0 = await ERC20Token.methods.balanceOf(accounts[0]).call();
|
||||
let result1 = await ERC20Token.methods.balanceOf(accounts[1]).call();
|
||||
|
||||
assert.equal(result0, +initialBalance0-1, "account 0 balance unexpected");
|
||||
assert.equal(result1, +initialBalance1+1, "account 1 balance unexpected");
|
||||
});
|
||||
|
||||
it("should set approved amount", async function() {
|
||||
await ERC20Token.methods.approve(accounts[2],10000000).send({from: accounts[0]});
|
||||
let result = await ERC20Token.methods.allowance(accounts[0], accounts[2]).call();
|
||||
assert.equal(result, 10000000);
|
||||
});
|
||||
|
||||
it("should consume allowance amount", async function() {
|
||||
let initialAllowance = await ERC20Token.methods.allowance(accounts[0], accounts[2]).call();
|
||||
await ERC20Token.methods.transferFrom(accounts[0], accounts[0],1).send({from: accounts[2]});
|
||||
let result = await ERC20Token.methods.allowance(accounts[0], accounts[2]).call();
|
||||
|
||||
assert.equal(result, +initialAllowance-1);
|
||||
});
|
||||
|
||||
it("should transfer approved amount", async function() {
|
||||
let initialBalance0 = await ERC20Token.methods.balanceOf(accounts[0]).call();
|
||||
let initialBalance1 = await ERC20Token.methods.balanceOf(accounts[1]).call();
|
||||
await ERC20Token.methods.transferFrom(accounts[0], accounts[1],1).send({from: accounts[2]});
|
||||
let result0 = await ERC20Token.methods.balanceOf(accounts[0]).call();
|
||||
let result1 = await ERC20Token.methods.balanceOf(accounts[1]).call();
|
||||
|
||||
assert.equal(result0, +initialBalance0-1);
|
||||
assert.equal(result1, +initialBalance1+1);
|
||||
});
|
||||
|
||||
|
||||
it("should unset approved amount", async function() {
|
||||
await ERC20Token.methods.approve(accounts[2],0).send({from: accounts[0]});
|
||||
let result = await ERC20Token.methods.allowance(accounts[0], accounts[2]).call();
|
||||
assert.equal(result, 0);
|
||||
});
|
||||
|
||||
it("should deposit approved amount to contract ERC20Receiver", async function() {
|
||||
//ERC20Receiver = await ERC20Receiver.deploy().send();
|
||||
//console.log(ERC20Receiver.address);
|
||||
await ERC20Token.methods.approve(ERC20Receiver.address, 10).send({from: accounts[0]});
|
||||
await ERC20Receiver.methods.depositToken(ERC20Token.address, 10).send({from: accounts[0]});
|
||||
let result = await ERC20Receiver.methods.tokenBalanceOf(ERC20Token.address, accounts[0]).call();
|
||||
assert.equal(result, 10, "ERC20Receiver.tokenBalanceOf("+ERC20Token.address+","+accounts[0]+") wrong");
|
||||
});
|
||||
|
||||
it("should witdraw approved amount from contract ERC20Receiver", async function() {
|
||||
let tokenBalance = await ERC20Receiver.methods.tokenBalanceOf(ERC20Token.address, accounts[0]).call();
|
||||
await ERC20Receiver.methods.withdrawToken(ERC20Token.address, tokenBalance).send({from: accounts[0]});
|
||||
tokenBalance = await ERC20Receiver.methods.tokenBalanceOf(ERC20Token.address, accounts[0]).call();
|
||||
assert.equal(tokenBalance, 0, "ERC20Receiver.tokenBalanceOf("+ERC20Token.address+","+accounts[0]+") wrong");
|
||||
});
|
||||
|
||||
//TODO: include checks for expected events fired
|
||||
|
||||
|
||||
});
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
const utils = require('../utils/testUtils')
|
||||
|
||||
const MiniMeToken = require('Embark/contracts/MiniMeToken');
|
||||
const ERC20TokenSpec = require('./abstract/erc20tokenspec');
|
||||
const ControlledSpec = require('./abstract/controlled');
|
||||
|
||||
config({
|
||||
contracts: {
|
||||
"MiniMeTokenFactory": {
|
||||
},
|
||||
"MiniMeToken": {
|
||||
"args": [
|
||||
"$MiniMeTokenFactory",
|
||||
utils.zeroAddress,
|
||||
0,
|
||||
"TestMiniMeToken",
|
||||
18,
|
||||
"TST",
|
||||
true
|
||||
]
|
||||
},
|
||||
...ERC20TokenSpec.config.contracts
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
contract("MiniMeToken", function() {
|
||||
this.timeout(0);
|
||||
var accounts;
|
||||
before(function(done) {
|
||||
web3.eth.getAccounts().then(function (res) {
|
||||
accounts = res;
|
||||
done();
|
||||
});
|
||||
});
|
||||
var miniMeTokenClone;
|
||||
const b = [];
|
||||
|
||||
it('should generate tokens for address 1', async () => {
|
||||
await MiniMeToken.methods.generateTokens(accounts[1], 10).send();
|
||||
assert.equal(await MiniMeToken.methods.totalSupply().call(), 10);
|
||||
assert.equal(await MiniMeToken.methods.balanceOf(accounts[1]).call(), 10);
|
||||
b[0] = await web3.eth.getBlockNumber();
|
||||
});
|
||||
|
||||
it('should transfer tokens from address 1 to address 3', async () => {
|
||||
await MiniMeToken.methods.transfer(accounts[3], 1).send({from: accounts[1]});
|
||||
assert.equal(await MiniMeToken.methods.totalSupply().call(), 10);
|
||||
assert.equal(await MiniMeToken.methods.balanceOf(accounts[1]).call(), 9);
|
||||
assert.equal(await MiniMeToken.methods.balanceOf(accounts[3]).call(), 1);
|
||||
b[1] = await web3.eth.getBlockNumber();
|
||||
});
|
||||
|
||||
it('should destroy 3 tokens from 1 and 1 from 2', async () => {
|
||||
await MiniMeToken.methods.destroyTokens(accounts[1], 3).send({ from: accounts[0] });
|
||||
assert.equal(await MiniMeToken.methods.totalSupply().call(), 7);
|
||||
assert.equal(await MiniMeToken.methods.balanceOf(accounts[1]).call(), 6);
|
||||
b[2] = await web3.eth.getBlockNumber();
|
||||
});
|
||||
|
||||
|
||||
it('should create the clone token', async () => {
|
||||
const miniMeTokenCloneTx = await MiniMeToken.methods.createCloneToken(
|
||||
'Clone Token 1',
|
||||
18,
|
||||
'MMTc',
|
||||
0,
|
||||
true).send({ from: accounts[0]});
|
||||
let addr = miniMeTokenCloneTx.events.NewCloneToken.returnValues[0];
|
||||
miniMeTokenClone = new web3.eth.Contract(MiniMeToken._jsonInterface, addr);
|
||||
|
||||
b[3] = await web3.eth.getBlockNumber();
|
||||
|
||||
assert.equal(await miniMeTokenClone.methods.parentToken().call(), MiniMeToken.address);
|
||||
assert.equal(await miniMeTokenClone.methods.parentSnapShotBlock().call(), b[3]);
|
||||
assert.equal(await miniMeTokenClone.methods.totalSupply().call(), 7);
|
||||
assert.equal(await MiniMeToken.methods.balanceOf(accounts[1]).call(), 6);
|
||||
|
||||
assert.equal(await miniMeTokenClone.methods.totalSupplyAt(b[2]).call(), 7);
|
||||
assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[3], b[2]).call(), 1);
|
||||
});
|
||||
|
||||
it('should move tokens in the clone token from 2 to 3', async () => {
|
||||
|
||||
await miniMeTokenClone.methods.transfer(accounts[2], 4).send({ from: accounts[1], gas: 1000000 });
|
||||
b[4] = await web3.eth.getBlockNumber();
|
||||
|
||||
assert.equal(await MiniMeToken.methods.balanceOfAt(accounts[1], b[3]).call(), 6);
|
||||
assert.equal(await MiniMeToken.methods.balanceOfAt(accounts[2], b[3]).call(), 0);
|
||||
assert.equal(await miniMeTokenClone.methods.totalSupply().call(), 7);
|
||||
assert.equal(await miniMeTokenClone.methods.balanceOf(accounts[1]).call(), 2);
|
||||
assert.equal(await miniMeTokenClone.methods.balanceOf(accounts[2]).call(), 4);
|
||||
assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[1], b[3]).call(), 6);
|
||||
assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[2], b[3]).call(), 0);
|
||||
assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[1], b[2]).call(), 6);
|
||||
assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[2], b[2]).call(), 0);
|
||||
assert.equal(await miniMeTokenClone.methods.totalSupplyAt(b[3]).call(), 7);
|
||||
assert.equal(await miniMeTokenClone.methods.totalSupplyAt(b[2]).call(), 7);
|
||||
});
|
||||
|
||||
it('should create tokens in the child token', async () => {
|
||||
await miniMeTokenClone.methods.generateTokens(accounts[1], 10).send({ from: accounts[0], gas: 1000000});
|
||||
assert.equal(await miniMeTokenClone.methods.totalSupply().call(), 17);
|
||||
assert.equal(await miniMeTokenClone.methods.balanceOf(accounts[1]).call(), 12);
|
||||
assert.equal(await miniMeTokenClone.methods.balanceOf(accounts[2]).call(), 4);
|
||||
});
|
||||
|
||||
|
||||
it("should mint balances for ERC20TokenSpec", async function() {
|
||||
let initialBalance = 7 * 10 ^ 18;
|
||||
for(i=0;i<accounts.length;i++){
|
||||
await MiniMeToken.methods.generateTokens(accounts[i], initialBalance).send({from: accounts[0]})
|
||||
//assert.equal(await TestToken.methods.balanceOf(accounts[i]).call(), initialBalance);
|
||||
}
|
||||
})
|
||||
ERC20TokenSpec.Test(MiniMeToken);
|
||||
ControlledSpec.Test(MiniMeToken);
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
const Utils = require('../utils/testUtils');
|
||||
const MiniMeToken = require('Embark/contracts/MiniMeToken');
|
||||
const TestStatusNetwork = require('Embark/contracts/TestStatusNetwork');
|
||||
const StickerMarket = require('Embark/contracts/StickerMarket');
|
||||
|
||||
config({
|
||||
contracts: {
|
||||
"MiniMeTokenFactory": {},
|
||||
"MiniMeToken": {
|
||||
"args":["$MiniMeTokenFactory", "0x0", "0x0", "Status Test Token", 18, "STT", true],
|
||||
},
|
||||
"TestStatusNetwork": {
|
||||
"args": ["0x0", "$MiniMeToken"],
|
||||
"onDeploy": [
|
||||
"await MiniMeToken.methods.changeController(TestStatusNetwork.address).send()",
|
||||
"await TestStatusNetwork.methods.setOpen(true).send()",
|
||||
]
|
||||
},
|
||||
"StickerMarket": {
|
||||
"args": ["$MiniMeToken"]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
contract("StickerMarket", function() {
|
||||
this.timeout(0);
|
||||
var accounts;
|
||||
var testPacks;
|
||||
let registeredPacks = [];
|
||||
|
||||
before(function(done) {
|
||||
web3.eth.getAccounts().then(function (res) {
|
||||
accounts = res;
|
||||
testPacks = [
|
||||
{
|
||||
category: ["0x00000000", "0x00000001","0x00000002","0x00000003","0x00000004"],
|
||||
price: "10000000000000000000",
|
||||
donate: "0",
|
||||
contentHash:"0x55c72bf3b3d468c7c36c848a4d49bb11101dc79bc2f6484bf1ef796fc498919a",
|
||||
owner: accounts[1]
|
||||
},
|
||||
{
|
||||
category: ["0x00000000", "0x00000001"],
|
||||
price: "10000000000000000000",
|
||||
donate: "10",
|
||||
contentHash:"0xe434491f185cedfea522bd0b937ce849906833aefa20a76e8e50db194baf34cb",
|
||||
owner: accounts[2]
|
||||
},
|
||||
{
|
||||
category: ["0x00000000", "0x00000001","0x00000002","0x00000004"],
|
||||
price: "10000000000000000000",
|
||||
donate: "100",
|
||||
contentHash:"0xf4c247e858aba2942bf0ed6008c15a387c88c262c70f930ab91799655d71367d",
|
||||
owner: accounts[3]
|
||||
},
|
||||
{
|
||||
category: ["0x00000000", "0x00000002","0x00000003","0x00000004"],
|
||||
price: "10000000000000000000",
|
||||
donate: "1000",
|
||||
contentHash:"0x66c2aec914d17249c66a750303521a51607c38d084ae173976e54ad40473d056",
|
||||
owner: accounts[4]
|
||||
},
|
||||
{
|
||||
category: ["0x00000000", "0x00000001","0x00000002","0x00000004"],
|
||||
price: "10000000000000000000",
|
||||
donate: "10000",
|
||||
contentHash:"0x4e25277a1af127bd1c2fce6aa20ac7eae8fded9c615b7964ebe9e18779765108",
|
||||
owner: accounts[5]
|
||||
},
|
||||
{
|
||||
category: ["0x00000000", "0x00000004"],
|
||||
price: "10000000000000000000",
|
||||
donate: "2",
|
||||
contentHash:"0x659c423e40fc2b4f37ada1dda463aa4455d650d799d82e63af87ac8b714bee66",
|
||||
owner: accounts[6]
|
||||
},
|
||||
{
|
||||
category: ["0x00000000", "0x00000003","0x00000004"],
|
||||
price: "10000000000000000000",
|
||||
donate: "20",
|
||||
contentHash:"0xbbf932b8a154bc1d496ebbfa2acca571119d53a6cb5986d8a187e85ac8a37265",
|
||||
owner: accounts[7]
|
||||
},
|
||||
{
|
||||
category: ["0x00000000", "0x00000003"],
|
||||
price: "10000000000000000000",
|
||||
donate: "200",
|
||||
contentHash:"0x6dd4cbc4a86825506bf85defa071a4e6ac5d76a1b6912863ef0e289327df08d2",
|
||||
owner: accounts[8]
|
||||
}
|
||||
];
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should register packs", async function() {
|
||||
for(let i = 0; i < testPacks.length; i++){
|
||||
let pack = testPacks[i];
|
||||
let reg = await StickerMarket.methods.registerPack(pack.price, pack.donate, pack.category, pack.owner, pack.contentHash).send();
|
||||
registeredPacks.push({id: reg.events.Register.returnValues.packId, data: pack})
|
||||
};
|
||||
for(let i = 0; i < registeredPacks.length; i++){
|
||||
for(let j = 0; j < registeredPacks[i].data.category.length; j++) {
|
||||
assert.notEqual((await StickerMarket.methods.getAvailablePacks(registeredPacks[i].data.category[j]).call()).indexOf(registeredPacks[i].id), -1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it("should categorize packs", async function() {
|
||||
for(let i = 0; i < registeredPacks.length; i++){
|
||||
assert.equal((await StickerMarket.methods.getPackData(registeredPacks[i].id).call()).category.indexOf("0x12345678"),-1);
|
||||
assert.equal((await StickerMarket.methods.getAvailablePacks("0x12345678").call()).indexOf(registeredPacks[i].id), -1);
|
||||
await StickerMarket.methods.addPackCategory(registeredPacks[i].id, "0x12345678").send();
|
||||
assert.notEqual((await StickerMarket.methods.getPackData(registeredPacks[i].id).call()).category.indexOf("0x12345678"),-1);
|
||||
assert.notEqual((await StickerMarket.methods.getAvailablePacks("0x12345678").call()).indexOf(registeredPacks[i].id), -1);
|
||||
registeredPacks[i].data.category.push("0x12345678")
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
it("should uncategorize packs", async function() {
|
||||
for(let i = 0; i < testPacks.length; i++){
|
||||
assert.notEqual((await StickerMarket.methods.getAvailablePacks("0x00000000").call()).indexOf(registeredPacks[i].id), -1);
|
||||
assert.notEqual((await StickerMarket.methods.getPackData(registeredPacks[i].id).call()).category.indexOf("0x00000000"),-1);
|
||||
await StickerMarket.methods.removePackCategory(i, "0x00000000").send();
|
||||
assert.equal((await StickerMarket.methods.getAvailablePacks("0x00000000").call()).indexOf(registeredPacks[i].id), -1);
|
||||
assert.equal((await StickerMarket.methods.getPackData(registeredPacks[i].id).call()).category.indexOf("0x00000000"),-1);
|
||||
registeredPacks[i].data.category = registeredPacks[i].data.category.filter(function(value, index, arr){
|
||||
return value != "0x00000000";
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
it("should mint packs", async function() {
|
||||
let burnRate = 10;
|
||||
await StickerMarket.methods.setBurnRate(burnRate).send();
|
||||
let packBuyer = accounts[2];
|
||||
for(let i = 0; i < registeredPacks.length; i++){
|
||||
await TestStatusNetwork.methods.mint(registeredPacks[i].data.price).send({from: packBuyer });
|
||||
await MiniMeToken.methods.approve(StickerMarket.address, registeredPacks[i].data.price).send({from: packBuyer });
|
||||
let buy = await StickerMarket.methods.buyToken(registeredPacks[i].id, packBuyer).send({from: packBuyer });
|
||||
let tokenId;
|
||||
let toArtist = 0;
|
||||
let donated = 0;
|
||||
let burned = 0;
|
||||
let burnAddress =(await MiniMeToken.methods.controller().call());
|
||||
|
||||
for(let j = 0; j < buy.events.Transfer.length; j++) {
|
||||
if(buy.events.Transfer[j].address == MiniMeToken.address){
|
||||
if(buy.events.Transfer[j].returnValues.to == StickerMarket.address){
|
||||
donated = parseInt(buy.events.Transfer[j].raw.data, 16).toString(10)
|
||||
}else if(buy.events.Transfer[j].returnValues.to == registeredPacks[i].data.owner){
|
||||
toArtist = parseInt(buy.events.Transfer[j].raw.data, 16).toString(10)
|
||||
}else if(buy.events.Transfer[j].returnValues.to == burnAddress){
|
||||
burned = parseInt(buy.events.Transfer[j].raw.data, 16).toString(10)
|
||||
}
|
||||
}else if(buy.events.Transfer[j].address == StickerMarket.address){
|
||||
tokenId = buy.events.Transfer[j].returnValues.tokenId;
|
||||
}
|
||||
}
|
||||
|
||||
assert.equal(registeredPacks[i].data.price, (+toArtist + +donated + +burned), "Bad payment")
|
||||
assert.equal(burned, (registeredPacks[i].data.price * burnRate) / 10000, "Bad burn")
|
||||
assert.equal(donated, ((+registeredPacks[i].data.price - burned) * registeredPacks[i].data.donate)/10000, "Bad donate")
|
||||
assert.equal(toArtist, registeredPacks[i].data.price - (+donated + +burned), "Bad profit")
|
||||
assert.equal(await StickerMarket.methods.ownerOf(tokenId).call(), packBuyer, "Bad owner")
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
it("should mint packs with approveAndCall", async function() {
|
||||
let burnRate = 10;
|
||||
await StickerMarket.methods.setBurnRate(burnRate).send();
|
||||
let packBuyer = accounts[2];
|
||||
for(let i = 0; i < registeredPacks.length; i++){
|
||||
|
||||
await TestStatusNetwork.methods.mint(registeredPacks[i].data.price).send({from: packBuyer });
|
||||
const buyCall = StickerMarket.methods.buyToken(registeredPacks[i].id, packBuyer).encodeABI();
|
||||
let buy = await MiniMeToken.methods.approveAndCall(StickerMarket.address, registeredPacks[i].data.price, buyCall).send({from: packBuyer });
|
||||
let tokenId;
|
||||
let toArtist = 0;
|
||||
let donated = 0;
|
||||
let burned = 0;
|
||||
let burnAddress =(await MiniMeToken.methods.controller().call());
|
||||
|
||||
for(let j = 0; j < buy.events.Transfer.length; j++) {
|
||||
if(buy.events.Transfer[j].address == MiniMeToken.address){
|
||||
if(buy.events.Transfer[j].returnValues[1] == StickerMarket.address){
|
||||
donated = parseInt(buy.events.Transfer[j].raw.data, 16).toString(10)
|
||||
}else if(buy.events.Transfer[j].returnValues[1] == registeredPacks[i].data.owner){
|
||||
toArtist = parseInt(buy.events.Transfer[j].raw.data, 16).toString(10)
|
||||
}else if(buy.events.Transfer[j].returnValues[1] == burnAddress){
|
||||
burned = parseInt(buy.events.Transfer[j].raw.data, 16).toString(10)
|
||||
}
|
||||
}else if(buy.events.Transfer[j].address == StickerMarket.address){
|
||||
tokenId = parseInt(buy.events.Transfer[j].raw.data, 16).toString(10);
|
||||
}
|
||||
}
|
||||
assert.equal(registeredPacks[i].data.price, (+toArtist + +donated + +burned), "Bad payment")
|
||||
assert.equal(burned, (registeredPacks[i].data.price * burnRate) / 10000, "Bad burn")
|
||||
assert.equal(donated, ((+registeredPacks[i].data.price - burned) * registeredPacks[i].data.donate)/10000, "Bad donate")
|
||||
assert.equal(toArtist, registeredPacks[i].data.price - (+donated + +burned), "Bad profit")
|
||||
assert.equal(await StickerMarket.methods.ownerOf(tokenId).call(), packBuyer, "Bad owner")
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
it("should register pack with approveAndCall", async function() {
|
||||
let registerFee = "1000000000000000000";
|
||||
await StickerMarket.methods.setRegisterFee(registerFee).send();
|
||||
await TestStatusNetwork.methods.mint(registerFee).send();
|
||||
let pack = testPacks[0];
|
||||
let regCall = await StickerMarket.methods.registerPack(pack.price, pack.donate, pack.category, pack.owner, pack.contentHash).encodeABI();
|
||||
let packId = await StickerMarket.methods.packCount().call();
|
||||
let reg = await MiniMeToken.methods.approveAndCall(StickerMarket.address, registerFee, regCall).send();
|
||||
|
||||
for(let j = 0; j < pack.category.length; j++) {
|
||||
assert.notEqual((await StickerMarket.methods.getAvailablePacks(pack.category[j]).call()).indexOf(packId), -1);
|
||||
}
|
||||
|
||||
await StickerMarket.methods.purgePack(packId, 0).send();
|
||||
await StickerMarket.methods.setRegisterFee("0").send();
|
||||
});
|
||||
|
||||
it("should purge packs", async function() {
|
||||
var i = 0;
|
||||
await StickerMarket.methods.purgePack(registeredPacks[i].id, 0).send();
|
||||
for(let j = 0; j < registeredPacks[i].data.category.length; j++) {
|
||||
assert.equal((await StickerMarket.methods.getAvailablePacks(registeredPacks[i].data.category[j]).call()).indexOf(registeredPacks[i].id), -1);
|
||||
}
|
||||
|
||||
i = registeredPacks.length-1;
|
||||
await StickerMarket.methods.purgePack(registeredPacks[i].id, 0).send();
|
||||
for(let j = 0; j < registeredPacks[i].data.category.length; j++) {
|
||||
assert.equal((await StickerMarket.methods.getAvailablePacks(registeredPacks[i].data.category[j]).call()).indexOf(registeredPacks[i].id), -1);
|
||||
}
|
||||
|
||||
i = 2;
|
||||
await StickerMarket.methods.purgePack(registeredPacks[i].id, 0).send();
|
||||
for(let j = 0; j < registeredPacks[i].data.category.length; j++) {
|
||||
assert.equal((await StickerMarket.methods.getAvailablePacks(registeredPacks[i].data.category[j]).call()).indexOf(registeredPacks[i].id), -1);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
it("should not mint a pack with price 0", async function() {
|
||||
await StickerMarket.methods.setMarketState(1).send();
|
||||
let testPack = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
||||
let testPackPrice = "0";
|
||||
let packOwner = accounts[1];
|
||||
let packBuyer = accounts[2];
|
||||
let reg = await StickerMarket.methods.registerPack(testPackPrice, 0, ["0x00000000"], packOwner, testPack).send();
|
||||
let packId = reg.events.Register.returnValues.packId;
|
||||
await TestStatusNetwork.methods.mint("1").send({from: packBuyer });
|
||||
await MiniMeToken.methods.approve(StickerMarket.address, "1").send({from: packBuyer });
|
||||
Utils.expectThrow(StickerMarket.methods.buyToken(packId, packBuyer).send({from: packBuyer }));
|
||||
await StickerMarket.methods.purgePack(packId, 0).send();
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,52 @@
|
|||
const Utils = require('../utils/testUtils');
|
||||
const MiniMeToken = require('Embark/contracts/MiniMeToken');
|
||||
const TestStatusNetwork = require('Embark/contracts/TestStatusNetwork');
|
||||
|
||||
config({
|
||||
contracts: {
|
||||
"MiniMeTokenFactory": {},
|
||||
"MiniMeToken": {
|
||||
"args":["$MiniMeTokenFactory", "0x0", "0x0", "Status Test Token", 18, "STT", true],
|
||||
},
|
||||
"TestStatusNetwork": {
|
||||
"deploy": true,
|
||||
"args": ["0x0", "$MiniMeToken"],
|
||||
"onDeploy": [
|
||||
"await MiniMeToken.methods.changeController(TestStatusNetwork.address).send()",
|
||||
"await TestStatusNetwork.methods.setOpen(true).send()",
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
contract("TestStatusNetwork", function() {
|
||||
this.timeout(0);
|
||||
var accounts;
|
||||
before(function(done) {
|
||||
web3.eth.getAccounts().then(function (res) {
|
||||
accounts = res;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should increase totalSupply in mint", async function() {
|
||||
let initialSupply = await MiniMeToken.methods.totalSupply().call();
|
||||
await TestStatusNetwork.methods.mint(100).send();
|
||||
let result = await MiniMeToken.methods.totalSupply().call();
|
||||
assert.equal(result, +initialSupply+100);
|
||||
});
|
||||
|
||||
it("should increase accountBalance in mint", async function() {
|
||||
let initialBalance = await MiniMeToken.methods.balanceOf(accounts[0]).call();
|
||||
await TestStatusNetwork.methods.mint(100).send({from: accounts[0]});
|
||||
let result = await MiniMeToken.methods.balanceOf(accounts[0]).call();
|
||||
assert.equal(result, +initialBalance+100);
|
||||
});
|
||||
|
||||
it("should burn account supply", async function() {
|
||||
let initialBalance = await MiniMeToken.methods.balanceOf(accounts[0]).call();
|
||||
await TestStatusNetwork.methods.destroyTokens(accounts[0], initialBalance).send({from: accounts[0]});
|
||||
assert.equal(await MiniMeToken.methods.totalSupply().call(), 0);
|
||||
assert.equal(await MiniMeToken.methods.balanceOf(accounts[0]).call(), 0);
|
||||
})
|
||||
});
|
|
@ -0,0 +1,239 @@
|
|||
|
||||
// This has been tested with the real Ethereum network and Testrpc.
|
||||
// Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9
|
||||
exports.assertReverts = (contractMethodCall, maxGasAvailable) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
resolve(contractMethodCall())
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
.then(tx => {
|
||||
assert.equal(tx.receipt.gasUsed, maxGasAvailable, "tx successful, the max gas available was not consumed")
|
||||
})
|
||||
.catch(error => {
|
||||
if ((error + "").indexOf("invalid opcode") < 0 && (error + "").indexOf("out of gas") < 0) {
|
||||
// Checks if the error is from TestRpc. If it is then ignore it.
|
||||
// Otherwise relay/throw the error produced by the above assertion.
|
||||
// Note that no error is thrown when using a real Ethereum network AND the assertion above is true.
|
||||
throw error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.listenForEvent = event => new Promise((resolve, reject) => {
|
||||
event({}, (error, response) => {
|
||||
if (!error) {
|
||||
resolve(response.args)
|
||||
} else {
|
||||
reject(error)
|
||||
}
|
||||
event.stopWatching()
|
||||
})
|
||||
});
|
||||
|
||||
exports.eventValues = (receipt, eventName) => {
|
||||
if(receipt.events[eventName])
|
||||
return receipt.events[eventName].returnValues;
|
||||
}
|
||||
|
||||
exports.addressToBytes32 = (address) => {
|
||||
const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + address.slice(2);
|
||||
return "0x" + stringed.substring(stringed.length - 64, stringed.length);
|
||||
}
|
||||
|
||||
|
||||
// OpenZeppelin's expectThrow helper -
|
||||
// Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js
|
||||
exports.expectThrow = 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;
|
||||
assert(
|
||||
invalidOpcode || outOfGas || revert,
|
||||
'Expected throw, got \'' + error + '\' instead',
|
||||
);
|
||||
return;
|
||||
}
|
||||
assert.fail('Expected throw not received');
|
||||
};
|
||||
|
||||
|
||||
|
||||
exports.assertJump = (error) => {
|
||||
assert(error.message.search('revert') > -1, 'Revert should happen');
|
||||
}
|
||||
|
||||
|
||||
var callbackToResolve = function (resolve, reject) {
|
||||
return function (error, value) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
exports.promisify = (func) =>
|
||||
(...args) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (err, data) => err ? reject(err) : resolve(data);
|
||||
func.apply(this, [...args, callback]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// This has been tested with the real Ethereum network and Testrpc.
|
||||
// Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9
|
||||
exports.assertReverts = (contractMethodCall, maxGasAvailable) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
resolve(contractMethodCall())
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
.then(tx => {
|
||||
assert.equal(tx.receipt.gasUsed, maxGasAvailable, "tx successful, the max gas available was not consumed")
|
||||
})
|
||||
.catch(error => {
|
||||
if ((error + "").indexOf("invalid opcode") < 0 && (error + "").indexOf("out of gas") < 0) {
|
||||
// Checks if the error is from TestRpc. If it is then ignore it.
|
||||
// Otherwise relay/throw the error produced by the above assertion.
|
||||
// Note that no error is thrown when using a real Ethereum network AND the assertion above is true.
|
||||
throw error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.listenForEvent = event => new Promise((resolve, reject) => {
|
||||
event({}, (error, response) => {
|
||||
if (!error) {
|
||||
resolve(response.args)
|
||||
} else {
|
||||
reject(error)
|
||||
}
|
||||
event.stopWatching()
|
||||
})
|
||||
});
|
||||
|
||||
exports.eventValues = (receipt, eventName) => {
|
||||
if(receipt.events[eventName])
|
||||
return receipt.events[eventName].returnValues;
|
||||
}
|
||||
|
||||
exports.addressToBytes32 = (address) => {
|
||||
const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + address.slice(2);
|
||||
return "0x" + stringed.substring(stringed.length - 64, stringed.length);
|
||||
}
|
||||
|
||||
|
||||
// OpenZeppelin's expectThrow helper -
|
||||
// Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js
|
||||
exports.expectThrow = 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;
|
||||
assert(
|
||||
invalidOpcode || outOfGas || revert,
|
||||
'Expected throw, got \'' + error + '\' instead',
|
||||
);
|
||||
return;
|
||||
}
|
||||
assert.fail('Expected throw not received');
|
||||
};
|
||||
|
||||
exports.assertJump = (error) => {
|
||||
assert(error.message.search('revert') > -1, 'Revert should happen');
|
||||
}
|
||||
|
||||
var callbackToResolve = function (resolve, reject) {
|
||||
return function (error, value) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
exports.promisify = (func) =>
|
||||
(...args) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (err, data) => err ? reject(err) : resolve(data);
|
||||
func.apply(this, [...args, callback]);
|
||||
});
|
||||
}
|
||||
|
||||
exports.zeroAddress = '0x0000000000000000000000000000000000000000';
|
||||
exports.zeroBytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
||||
exports.timeUnits = {
|
||||
seconds: 1,
|
||||
minutes: 60,
|
||||
hours: 60 * 60,
|
||||
days: 24 * 60 * 60,
|
||||
weeks: 7 * 24 * 60 * 60,
|
||||
years: 365 * 24 * 60 * 60
|
||||
}
|
||||
|
||||
exports.ensureException = function(error) {
|
||||
assert(isException(error), error.toString());
|
||||
};
|
||||
|
||||
function isException(error) {
|
||||
let strError = error.toString();
|
||||
return strError.includes('invalid opcode') || strError.includes('invalid JUMP') || strError.includes('revert');
|
||||
}
|
||||
|
||||
exports.increaseTime = async (amount) => {
|
||||
return new Promise(function(resolve, reject) {
|
||||
web3.currentProvider.sendAsync(
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
method: 'evm_increaseTime',
|
||||
params: [+amount],
|
||||
id: new Date().getSeconds()
|
||||
},
|
||||
async (error) => {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
return reject(err);
|
||||
}
|
||||
await web3.currentProvider.sendAsync(
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
method: 'evm_mine',
|
||||
params: [],
|
||||
id: new Date().getSeconds()
|
||||
}, (error) => {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue