├── test ├── .gitkeep ├── helpers.js ├── Token.test.js └── Exchange.test.js ├── .babelrc ├── .gitignore ├── public ├── view.jpg ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── src ├── components │ ├── Spinner.js │ ├── Content.js │ ├── Navbar.js │ ├── PriceChart.js │ ├── Trades.js │ ├── App.js │ ├── OrderBook.js │ ├── App.css │ ├── MyTransactions.js │ ├── NewOrder.js │ ├── PriceChart.config.js │ └── Balance.js ├── contracts │ ├── Migrations.sol │ ├── Token.sol │ └── Exchange.sol ├── index.js ├── store │ ├── configureStore.js │ ├── action.js │ ├── reducers.js │ ├── interactions.js │ └── selectors.js ├── helpers.js └── abis │ └── Migrations.json ├── sol相关.md ├── .vscode └── settings.json ├── README.md ├── package.json ├── eip20标准.md ├── web3.md ├── scripts.md ├── truffle-config copy.js ├── truffle-config.js ├── scripts ├── tmep.js ├── seed-exchange.js └── seed-exchange2.js ├── test相关.md └── 错误.md /test/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2", "stage-3"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | 39001 4 | 5 | .env 6 | 7 | -------------------------------------------------------------------------------- /public/view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Linhieng/learn-dapp/HEAD/public/view.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Linhieng/learn-dapp/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Linhieng/learn-dapp/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Linhieng/learn-dapp/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /src/components/Spinner.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function({ type }) { 4 | if (type === 'table') 5 | return 6 | 7 | return
8 | } 9 | -------------------------------------------------------------------------------- /sol相关.md: -------------------------------------------------------------------------------- 1 | mapping 是一个基本类型, 叫做映射, 可以类比为键值对 2 | 3 | 比如: 4 | ```js 5 | mapping(address => uint256) public balanceOf; 6 | ``` 7 | 声明了一个映射类型, 叫做 balanceOf 8 | ```js 9 | balanceOf[] 10 | balanceOf[] = 11 | ``` 12 | 13 | 14 | ## 其他 15 | 16 | it's always a buy order if you're giving ether tokens 17 | 如果你给出的是 ether,那么这个订单一定是买入 -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | export const EVM_REVERT = 'VM Exception while processing transaction: revert' 2 | export const ETHER_ADDRESS = '0x0000000000000000000000000000000000000000' // 0x 后面 40 个 0 3 | 4 | export const ether = n => { 5 | return new web3.utils.BN( 6 | web3.utils.toWei(n.toString(), 'ether') 7 | ) 8 | } 9 | 10 | // Same as ether 11 | export const tokens = n => ether(n) 12 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const Token = artifacts.require('Token') 2 | const Exchange = artifacts.require('Exchange') 3 | 4 | module.exports = async function(deployer) { 5 | const accounts = await web3.eth.getAccounts() 6 | const feeAccount = accounts[0] 7 | const feePercent = 1 8 | 9 | // 部署到区块链, 部署到 ganache 10 | await deployer.deploy(Token) 11 | await deployer.deploy(Exchange, feeAccount, feePercent) 12 | } 13 | -------------------------------------------------------------------------------- /src/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.22 <0.9.0; 3 | 4 | contract Migrations { 5 | address public owner = msg.sender; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | require( 10 | msg.sender == owner, 11 | "This function is restricted to the contract's owner" 12 | ); 13 | _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | // import { render } from 'react-dom' 4 | import { Provider } from 'react-redux' 5 | import 'bootstrap/dist/css/bootstrap.css' /* 顺序要在 App 之前, 不然它会覆盖 App.css 样式 */ 6 | import App from './components/App' 7 | import configureStore from './store/configureStore' 8 | 9 | // const store = configureStore() 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ) 17 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 和中间件有关 3 | */ 4 | 5 | import { createStore, applyMiddleware, compose } from 'redux' 6 | import { createLogger } from 'redux-logger' 7 | import rootReducer from './reducers' 8 | 9 | const loggerMiddleware = createLogger() 10 | const middleware = [] 11 | 12 | // For Redux Dev Tools 13 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose 14 | 15 | export default function configureStore(preloadedState) { 16 | return createStore( 17 | rootReducer, 18 | preloadedState, 19 | composeEnhancers(applyMiddleware(...middleware, loggerMiddleware)) 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | export const ETHER_ADDRESS = '0x0000000000000000000000000000000000000000' // 0x 后面 40个 0 2 | 3 | export const GREEN = 'success' // bootstrap 中绿色的类名 4 | export const RED = 'danger' // bootstrap 中红色的类名 5 | 6 | export const DECIMALS = 10 ** 18 7 | 8 | // Shortcut to avoid passing around web3 connection 自己计算, 就不用导入 web3 了 杀鸡焉用牛刀 9 | export const ether = (wei) => { 10 | if (wei) return wei / DECIMALS // 18 decimal places 11 | } 12 | 13 | // Tokens and ether have same decimal resolution 14 | export const tokens = ether 15 | 16 | export const formatBalance = (balance) => { 17 | const precision = 100 // 2decimal places 18 | balance = ether(balance) 19 | balance = Math.round(balance * precision) / precision // Use 2 decimal places 20 | 21 | return balance 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "**/Thumbs.db": true, 9 | "node_modules/": true, 10 | ".vscode/": true, 11 | "package-lock.json": true, 12 | "truffle-config copy.js": true, 13 | }, 14 | // 只能在本地窗口或远程窗口中设置 15 | // "http.proxy": "http://127.0.0.1:7890", 16 | // "http.proxyStrictSSL": false 17 | 18 | 19 | "terminal.integrated.profiles.windows": { 20 | "Bash": { 21 | "path": "E:\\Git\\bin\\bash.exe", 22 | } 23 | }, 24 | "terminal.integrated.defaultProfile.windows": "Bash", 25 | "files.autoSave": "off", 26 | "cSpell.words": [ 27 | "apexcharts", 28 | "xaxis", 29 | "yaxis" 30 | ] 31 | } 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 项目介绍 2 | 3 | 该项目是跟着视频学习区块链开发时的代码,以及自己的一些笔记和注释。 4 | 视频版本有点久,已经好几年了,视频中的某些语法已经是错误的或者过时的了。 5 | 不过我这里的代码是新敲的,一些坑已经找到解决方法了。 6 | 7 | 因为没有一个清晰的理解,所以笔记(markdown)文件也有点乱,里面的内容也不太清楚怎么组织。 8 | 9 | ## 总结 10 | 11 | 这个课程真的太多坑了,英语不是难事,重点是时代久远了(19年中旬的,现在22年中旬),技术更新太快了。 12 | 有太多的东西都变了,导致遇到了很多 bug。还有的就是区块链相关的资料基本都是外语的。 13 | 14 | 但总得来说,最终总算是跟完了整个项目,这是我成功部署后的账号, 地址是 `https://ropsten.etherscan.io/address/0xa18C047e08730044E79f696EBf68386b67BD81D2`。就当留个纪念。 15 | 16 | 虽然最终跑完了,但是这个项目,总的来说,web3部分不难,后面更多的是 react 的知识点。还有的就是最终部署,搞了半个上午😣,还好最终成功了。 17 | 18 | 这个项目不会再想更新了,原本中间就打算放弃了,毕竟我刚开始弄这个,是打算学习弄个溯源项目的。 19 | 但是到了中间,发现这个教程,和溯源其实没啥关系,所以就没继续了。 20 | 但是没想到,这个 github 项目居然会有人 star,于是就想着,算了,跟完视频吧。 21 | 此时的代码全部都是能跑的(2022.7.12 12:50) 22 | ![image.png](./public/view.jpg) 23 | 24 | 25 | 网页部署到线上部署,我使用的是部署到[自己的服务器](http://112.74.73.147:39001/) 26 | 该网址 2024年11月18日到期. 27 | 28 | ## 相关链接 29 | 30 | * [youtube 原版视频博主(大概)](https://www.youtube.com/c/DappUniversity/featured) 31 | * [作者新版项目代码](https://github.com/GlitchicaL/project-arcadia) 32 | * [b站搬运教程视频-区块链开发训练营](https://www.bilibili.com/video/BV153411N7to) 33 | -------------------------------------------------------------------------------- /src/components/Content.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { exchangeSelector } from '../store/selectors' 4 | import { loadAllOrders, subscribeToEvents } from '../store/interactions' 5 | 6 | import Trades from './Trades' 7 | import OrderBook from './OrderBook' 8 | import MyTransactions from './MyTransactions' 9 | import PriceChart from './PriceChart' 10 | import Balance from './Balance' 11 | import NewOrder from './NewOrder' 12 | 13 | class Content extends Component { 14 | componentWillMount() { 15 | this.loadBlockchainData(this.props) 16 | } 17 | 18 | async loadBlockchainData(props) { 19 | const { dispatch, exchange } = props 20 | await loadAllOrders(exchange, dispatch) 21 | // 在 Content 中订阅时间, 如果将这段代码写在 MyTransaction.js 也是可以的. 22 | await subscribeToEvents(exchange, dispatch) 23 | } 24 | 25 | render() { 26 | return ( 27 |
28 |
29 | 30 | 31 |
32 | 33 | 34 | 35 |
36 | 37 | 38 |
39 | 40 | 41 |
42 | ) 43 | } 44 | } 45 | 46 | function mapStateToProps(state) { 47 | return { 48 | exchange: exchangeSelector(state), 49 | } 50 | } 51 | 52 | // export default App 53 | export default connect(mapStateToProps)(Content) 54 | -------------------------------------------------------------------------------- /src/components/Navbar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { accountSelector } from '../store/selectors' 4 | 5 | class Navbar extends Component { 6 | render() { 7 | return ( 8 | 36 | ) 37 | } 38 | } 39 | 40 | function mapStateToProps(state) { 41 | return { 42 | account: accountSelector(state), 43 | } 44 | } 45 | 46 | // export default App 47 | export default connect(mapStateToProps)(Navbar) 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "truffle", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "react-scripts start", 8 | "build": "react-scripts build", 9 | "test": "react-scripts test", 10 | "eject": "react-scripts eject" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "babel-polyfill": "6.26.0", 17 | "babel-preset-env": "1.7.0", 18 | "babel-preset-es2015": "6.24.1", 19 | "babel-preset-stage-2": "6.24.1", 20 | "babel-preset-stage-3": "6.24.1", 21 | "babel-register": "6.26.0", 22 | "bootstrap": "^4.3.1", 23 | "chai": "4.2.0", 24 | "chai-as-promised": "7.1.1", 25 | "dotenv": "6.2.0", 26 | "lodash": "^4.17.20", 27 | "moment": "^2.29.0", 28 | "openzeppelin-solidity": "2.1.3", 29 | "react": "^16.8.4", 30 | "react-apexcharts": "^1.3.0", 31 | "react-bootstrap": "^1.3.0", 32 | "react-dom": "^16.8.4", 33 | "react-redux": "^5.1.1", 34 | "react-scripts": "^2.1.3", 35 | "redux": "^3.7.2", 36 | "redux-logger": "^3.0.6", 37 | "reselect": "^4.0.0", 38 | "truffle": "5.5.18", 39 | "truffle-flattener": "^1.3.0", 40 | "truffle-hdwallet-provider": "^1.0.4", 41 | "web3": "^1.5.3" 42 | }, 43 | "browserslist": { 44 | "production": [ 45 | ">0.2%", 46 | "not dead", 47 | "not op_mini all" 48 | ], 49 | "development": [ 50 | "last 1 chrome version", 51 | "last 1 firefox version", 52 | "last 1 safari version" 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/PriceChart.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Chart from 'react-apexcharts' 3 | import Spinner from './Spinner' 4 | import { connect } from 'react-redux' 5 | import { chartOptions } from './PriceChart.config' 6 | import { 7 | priceChartLoadedSelector, 8 | priceChartSelector, 9 | } from '../store/selectors' 10 | 11 | const priceSymbol = (lastPriceChange) => { 12 | let output 13 | if (lastPriceChange === '+') { 14 | output = 15 | } else { 16 | output = 17 | } 18 | return output 19 | } 20 | 21 | const showPriceChart = (priceChart) => ( 22 |
23 |
24 |

25 | DAPP / ETH   {priceSymbol(priceChart.lastPriceChange)}  {' '} 26 | {priceChart.lastPrice} 27 |

28 |
29 | 36 |
37 | ) 38 | 39 | class PriceChart extends Component { 40 | render() { 41 | return ( 42 |
43 |
Price Chart
44 |
45 | {this.props.priceChartLoaded ? ( 46 | showPriceChart(this.props.priceChart) 47 | ) : ( 48 | 49 | )} 50 |
51 |
52 | ) 53 | } 54 | } 55 | 56 | function mapStateToProps(state) { 57 | return { 58 | priceChartLoaded: priceChartLoadedSelector(state), 59 | priceChart: priceChartSelector(state), 60 | } 61 | } 62 | 63 | // export default App 64 | export default connect(mapStateToProps)(PriceChart) 65 | -------------------------------------------------------------------------------- /src/components/Trades.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import Spinner from './Spinner' 4 | 5 | import { 6 | filledOrdersLoadedSelector, 7 | filledOrdersSelector, 8 | } from '../store/selectors' 9 | 10 | const showFilledOrders = (filledOrders) => ( 11 | 12 | {filledOrders.map((order) => ( 13 | 14 | {order.formattedTimestamp} 15 | {order.tokenAmount} 16 | {order.tokenPrice} 17 | 18 | ))} 19 | 20 | ) 21 | 22 | class Trades extends Component { 23 | render() { 24 | return ( 25 |
26 |
27 |
Trades 历史交易
28 |
29 | 30 | 31 | 32 | 33 | {/* tokenAmount */} 34 | 35 | {/* tokenPrice */} 36 | 37 | 38 | 39 | {this.props.filledOrdersLoaded ? ( 40 | showFilledOrders(this.props.filledOrders) 41 | ) : ( 42 | 43 | )} 44 |
TimeDAPPDAPP / ETH
45 |
46 |
47 |
48 | ) 49 | } 50 | } 51 | 52 | function mapStateToProps(state) { 53 | return { 54 | filledOrders: filledOrdersSelector(state), 55 | filledOrdersLoaded: filledOrdersLoadedSelector(state), 56 | } 57 | } 58 | 59 | export default connect(mapStateToProps)(Trades) 60 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/contracts/Token.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 4 | 5 | contract Token { 6 | using SafeMath for uint; 7 | 8 | // Variables 9 | string public name = "DAPP Token"; 10 | string public symbol = "DAPP"; 11 | uint256 public decimals = 18; 12 | uint256 public totalSupply; 13 | mapping(address => uint256) public balanceOf; 14 | mapping(address => mapping(address => uint256)) public allowance; 15 | 16 | // Events 17 | event Transfer(address indexed from ,address indexed to, uint256 value); 18 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 19 | 20 | constructor() public { 21 | totalSupply = 1000000 * (10 ** decimals); 22 | balanceOf[msg.sender] = totalSupply; 23 | } 24 | 25 | function _transfer (address _from, address _to, uint256 _value) internal returns (bool success) { 26 | require(_to != address(0x0)); 27 | balanceOf[_from] = balanceOf[_from].sub(_value); 28 | balanceOf[_to] = balanceOf[_to].add(_value); 29 | emit Transfer(_from, _to, _value); 30 | return true; 31 | } 32 | 33 | // send balances 34 | function transfer (address _to, uint256 _value) public returns (bool success) { 35 | require(balanceOf[msg.sender] >= _value); 36 | _transfer(msg.sender, _to, _value); 37 | return true; 38 | } 39 | 40 | // Approve tokens 41 | function approve(address _spender, uint256 _value) public returns (bool success) { 42 | require(_spender != address(0x0)); 43 | allowance[msg.sender][_spender] = _value; 44 | emit Approval(msg.sender, _spender, _value); 45 | return true; 46 | } 47 | 48 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { 49 | require(_value <= balanceOf[_from]); 50 | require(_value <= allowance[_from][msg.sender]); 51 | allowance[_from][msg.sender] = allowance[_from][msg.sender].sub(_value); 52 | _transfer(_from, _to, _value); 53 | return true; 54 | } 55 | } -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import './App.css' 3 | 4 | import Navbar from './Navbar' 5 | import Content from './Content' 6 | 7 | import { contractsLoadedSelector } from '../store/selectors' 8 | 9 | import { connect } from 'react-redux' 10 | import { 11 | loadWeb3, 12 | loadAccount, 13 | loadToken, 14 | loadExchange, 15 | } from '../store/interactions' 16 | 17 | class App extends Component { 18 | componentWillMount() { 19 | this.loadBlockchainData(this.props.dispatch) 20 | } 21 | 22 | async loadBlockchainData(dispatch) { 23 | const web3 = loadWeb3(dispatch) 24 | console.log('web3 对象', web3) 25 | 26 | const network_type = await web3.eth.net.getNetworkType() 27 | console.log('账户类型', network_type) 28 | 29 | window.ethereum && 30 | (await window.ethereum.request({ method: 'eth_requestAccounts' })) 31 | const account = await loadAccount(web3, dispatch) 32 | console.log('获取账户', account) 33 | 34 | const networkId = await web3.eth.net.getId() 35 | console.log('网络id', networkId) 36 | 37 | const token = await loadToken(web3, networkId, dispatch) 38 | if (!token) { 39 | window.alert( 40 | 'Token smart contract not detected on the current network. Please select another network with MEtamask. 该网络中不存在 token, 请在 metamask 中切换网络' 41 | ) 42 | } 43 | 44 | const exchange = await loadExchange(web3, networkId, dispatch) 45 | if (!exchange) { 46 | window.alert( 47 | 'Exchange smart contract not detected on the current network. Please select another network with MEtamask. 该网络中不存在 Exchange, 请在 metamask 中切换网络' 48 | ) 49 | } 50 | 51 | const totalSupply = await token.methods.totalSupply().call() 52 | console.log('totalSupply ', totalSupply) 53 | } 54 | 55 | render() { 56 | return ( 57 |
58 | 59 | {this.props.contractsLoaded ? ( 60 | 61 | ) : ( 62 |
63 | )} 64 |
65 | ) 66 | } 67 | } 68 | 69 | function mapStateToProps(state) { 70 | return { 71 | contractsLoaded: contractsLoadedSelector(state), 72 | } 73 | } 74 | 75 | // export default App 76 | export default connect(mapStateToProps)(App) 77 | -------------------------------------------------------------------------------- /src/components/OrderBook.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import Spinner from './Spinner' 4 | 5 | import { fillOrder } from '../store/interactions' 6 | 7 | import { 8 | orderBookSelector, 9 | orderBookLoadedSelector, 10 | exchangeSelector, 11 | accountSelector, 12 | orderFillingSelector, 13 | } from '../store/selectors' 14 | 15 | const renderOrder = (order, dispatch, exchange, account) => ( 16 | { 20 | fillOrder(dispatch, exchange, order, account) 21 | }} 22 | > 23 | {order.tokenAmount} 24 | {order.tokenPrice} 25 | {order.etherAmount} 26 | 27 | ) 28 | 29 | function showOrderBook(props) { 30 | const { dispatch, orderBook, exchange, account } = props 31 | return ( 32 | 33 | {orderBook.sellOrders.map((order) => 34 | renderOrder(order, dispatch, exchange, account) 35 | )} 36 | 37 | DAPP 38 | DAPP / ETH 39 | ETH 40 | 41 | {orderBook.buyOrders.map((order) => 42 | renderOrder(order, dispatch, exchange, account) 43 | )} 44 | 45 | ) 46 | } 47 | 48 | class OrderBook extends Component { 49 | render() { 50 | return ( 51 |
52 |
53 |
Order Book 正在交易的订单
54 |
55 | 56 | {this.props.showOrderBook ? ( 57 | showOrderBook(this.props) 58 | ) : ( 59 | 60 | )} 61 |
62 |
63 |
64 |
65 | ) 66 | } 67 | } 68 | 69 | function mapStateToProps(state) { 70 | const orderBookLoaded = orderBookLoadedSelector(state) 71 | const orderFilling = orderFillingSelector(state) 72 | return { 73 | orderBook: orderBookSelector(state), 74 | showOrderBook: orderBookLoaded && !orderFilling, 75 | exchange: exchangeSelector(state), 76 | account: accountSelector(state), 77 | } 78 | } 79 | 80 | export default connect(mapStateToProps)(OrderBook) 81 | -------------------------------------------------------------------------------- /eip20标准.md: -------------------------------------------------------------------------------- 1 | 智能合约需要满足 [eip20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md) 标准 2 | 3 | 即 Token.sol 中要实现对应的函数 4 | 5 | ## 相关解释 6 | 7 | (不是很熟悉, 有些可能是解释错的) 8 | 9 | ### 变量 10 | * mapping(address => uint256) public balanceOf 11 | balanceOf 映射, 代表的是账户余额, 比如 balanceOf[_from] 就是指 _from 拥有的余额 12 | 13 | * mapping(address => mapping(address => uint256)) public allowance 14 | allowance 映射, 代表的是所允许的交易金额, 比如 allowance[_from][msg.sender] 就是指 _from 允许的交易最大金额 15 | 16 | ### 函数 17 | 18 | * function transfer (address _to, uint256 _value) public returns (bool success) 19 | 调用该函数用来发起交易, _to 是收钱方, value 是金额 20 | 21 | * function approve(address _spender, uint256 _value) public returns (bool success) 22 | 该函数用来限制交易金额, spender 是收钱方, value 是金额 23 | 24 | * function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) 25 | 和 transfer 类似, from 是出钱方, to 是收钱方, value 是金额, 这里面确保了 value 的合法性: 26 | 1. value 小于 from 拥有资金 27 | 2. value 小于 交易 所批准的金额 28 | 29 | ### 事件 30 | 31 | * event Transfer(address indexed from ,address indexed to, uint256 value); 32 | 这个事件代表发生了交易, from 是出钱方, to 收钱方, value 是价格 33 | 34 | * event Approval(address indexed _owner, address indexed _spender, uint256 _value); 35 | 当 approve 函数调用成功时, 必须触发该函数, owner 是出钱方, spender 是收钱方, value 是价格 36 | 37 | ### 其他 38 | 39 | * _transfer 40 | 这个内部函数, 用来实现交易, 主要逻辑就是, from 方资金减少, to 方资金增加, 并且触发 Transfer 时间 41 | 42 | * require() 43 | 断言, 不满足条件则拒绝, 抛出错误 44 | 45 | ## Token.sol 示例代码 (已通过测试) 46 | 47 | ```js 48 | pragma solidity ^0.5.0; 49 | 50 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 51 | 52 | contract Token { 53 | using SafeMath for uint; 54 | 55 | // Variables 56 | string public name = "DAPP Token"; 57 | string public symbol = "DAPP"; 58 | uint256 public decimals = 18; 59 | uint256 public totalSupply; 60 | mapping(address => uint256) public balanceOf; 61 | mapping(address => mapping(address => uint256)) public allowance; 62 | 63 | // Events 64 | event Transfer(address indexed from ,address indexed to, uint256 value); 65 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 66 | 67 | constructor() public { 68 | totalSupply = 1000000 * (10 ** decimals); 69 | balanceOf[msg.sender] = totalSupply; 70 | } 71 | 72 | function _transfer (address _from, address _to, uint256 _value) internal returns (bool success) { 73 | require(_to != address(0x0)); 74 | balanceOf[_from] = balanceOf[_from].sub(_value); 75 | balanceOf[_to] = balanceOf[_to].add(_value); 76 | emit Transfer(_from, _to, _value); 77 | return true; 78 | } 79 | 80 | // send balances 81 | function transfer (address _to, uint256 _value) public returns (bool success) { 82 | require(balanceOf[msg.sender] >= _value); 83 | _transfer(msg.sender, _to, _value); 84 | return true; 85 | } 86 | 87 | // Approve tokens 88 | function approve(address _spender, uint256 _value) public returns (bool success) { 89 | require(_spender != address(0x0)); 90 | allowance[msg.sender][_spender] = _value; 91 | emit Approval(msg.sender, _spender, _value); 92 | return true; 93 | } 94 | 95 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { 96 | require(_value <= balanceOf[_from]); // 要求小于本身拥有 tokens 97 | require(_value <= allowance[_from][msg.sender]); // 要求小于交易 所批准的 tokens 98 | allowance[_from][msg.sender] = allowance[_from][msg.sender].sub(_value); 99 | _transfer(_from, _to, _value); 100 | return true; 101 | } 102 | } 103 | ``` -------------------------------------------------------------------------------- /src/components/App.css: -------------------------------------------------------------------------------- 1 | .content { 2 | background-color: #1d1d1d; 3 | position: absolute; 4 | top: 56px; 5 | left: 0; 6 | right: 0; 7 | bottom: 0; 8 | display: flex; 9 | flex-direction: row; 10 | flex-wrap: nowrap; 11 | align-items: stretch; 12 | justify-content: space-evenly; 13 | padding: 8px; 14 | z-index: 1; 15 | } 16 | 17 | .content > div { 18 | width: 20%; 19 | padding-right: 8px; 20 | } 21 | 22 | .content > div:first-child { 23 | min-width: 220px; 24 | } 25 | .content > div:nth-child(2) { 26 | min-width: 200px; 27 | } 28 | 29 | .content > div:nth-child(3) { 30 | width: 40%; 31 | } 32 | 33 | .content > div:last-child { 34 | padding-right: 0; 35 | min-width: 240px; 36 | } 37 | 38 | 39 | @media (max-width:992px) { 40 | .content { 41 | display: block; 42 | overflow-y: auto; 43 | } 44 | 45 | .content > div { 46 | width: 100%!important; 47 | padding-right: 0; 48 | padding-bottom: 8px; 49 | } 50 | } 51 | 52 | .vertical > div { 53 | height: 100%; 54 | } 55 | 56 | .vertical-split { 57 | display: flex; 58 | flex-direction: column; 59 | align-items: stretch; 60 | justify-content: space-evenly; 61 | flex-wrap: nowrap; 62 | } 63 | 64 | .vertical-split > div { 65 | height: 50%; 66 | } 67 | 68 | .vertical-split > div:first-child { 69 | margin-bottom: 8px; 70 | } 71 | 72 | .vertical-split:last-child > div:first-child { 73 | height: 60px; 74 | } 75 | 76 | .vertical-split:last-child > div:last-child { 77 | height: 40%; 78 | } 79 | 80 | @media (max-width: 992px) { 81 | .vertical-split { 82 | display: block; 83 | } 84 | .vertical-split > div, 85 | .vertical-split > div:first-child { 86 | height: auto; 87 | } 88 | } 89 | 90 | .card { 91 | border-radius: 0px; 92 | display: flex; 93 | flex-direction: column; 94 | align-items: stretch; 95 | justify-content: space-evenly; 96 | flex-wrap: nowrap; 97 | } 98 | 99 | .card-header { 100 | padding: 8px 4px; 101 | } 102 | 103 | .card-body { 104 | flex: auto; 105 | overflow-x: auto; 106 | overflow-y: hidden; 107 | position: relative; 108 | padding: 2px; 109 | } 110 | 111 | .card-body th { 112 | border-top: 0; 113 | } 114 | 115 | .card-body .nav-tabs { 116 | border: #95999c; 117 | } 118 | 119 | .card-body .nav-item.nav-link { 120 | font-size: 80%; 121 | font-weight: bold; 122 | border-color: inherit; 123 | color: inherit; 124 | padding: 4px 8px; 125 | border: 0px #95999c; 126 | border-radius: 0px; 127 | } 128 | 129 | .card-body .nav-item.nav-link.active { 130 | border: 0px; 131 | border-bottom: 1px solid #007bff; 132 | } 133 | 134 | .card-body .price-chart { 135 | height: 100%; 136 | padding-bottom: 60px; 137 | } 138 | 139 | .card-body .price-chart .price{ 140 | padding-left: 20px; 141 | } 142 | 143 | td.cancel-order { 144 | cursor: pointer; 145 | } 146 | 147 | td.order-book-order { 148 | cursor: pointer; 149 | } 150 | 151 | /* =========== */ 152 | 153 | .card-body { 154 | overflow: auto; 155 | padding: 10px; 156 | } 157 | .card-body.hidden-overflow { 158 | overflow: hidden; 159 | } 160 | .card-body::-webkit-scrollbar { 161 | width: 1px; 162 | background-color: #ffffff26; 163 | } 164 | .card-body::-webkit-scrollbar-thumb { 165 | width: 1px; 166 | background-color: #ffffff33; 167 | } 168 | 169 | /* 覆盖 bootstrap 样式 */ 170 | .nav-tabs .nav-link.active { 171 | background-color: transparent; 172 | } 173 | 174 | .price-chart { 175 | overflow: hidden; /* 阻止滚动条的出现 */ 176 | } 177 | 178 | .table-hover:hover { 179 | background: #030d25; 180 | transition: 100ms ease; 181 | } 182 | 183 | .pointer { 184 | cursor: pointer; 185 | } -------------------------------------------------------------------------------- /web3.md: -------------------------------------------------------------------------------- 1 | 2 | 官网 https://web3js.readthedocs.io/ 3 | 4 | ## web3 相关 5 | 6 | ### 7 | 导入 contracts 中的智能合约后的 json 文件 8 | ```js 9 | import Token from '../abis/Token.json' 10 | ``` 11 | 12 | ### 13 | Adding web3.js, 连接, `Web3.givenProvider` 代表连接 metamask 中的网络 14 | ```js 15 | const web3 = new Web3(Web3.givenProvider || "ws://localhost:7545") 16 | ``` 17 | 18 | ### 19 | getNetworkType, 获取网络类型 如果 metamask 有启动, 则获取的是 metamask 的网络类型, 比如 main, Kovan, rinkeby, 如连接的是本地, 这是 private 20 | ```js 21 | web3.eth.net.getNetworkType([callback]) 22 | 23 | const network_type = await web3.eth.net.getNetworkType() 24 | console.log('账户类型', network_type) 25 | ``` 26 | 27 | ### 28 | getAccounts, 获取所有账户 29 | ```js 30 | web3.eth.getAccounts([callback]) 31 | 32 | window.ethereum && (await window.ethereum.request({ method: 'eth_requestAccounts' })) 33 | const account = await web3.eth.getAccounts() 34 | ``` 35 | 注意: 默认情况下 new Web3(Web3.givenProvider) 会输出 0 个账户 36 | 这是因为 metamask 插件改版了 37 | 新版的 metamask 插件默认情况下是开启了隐私模式的 38 | 所以想要获取账户时需要手动关闭隐私模式 39 | 可以通过判断 `window.ethereum` 的有无来判断 metamask 版本是否是新版本 40 | 通过 `await ethereum.enable()` 可以手动关闭隐私模式(允许该网站连接账户) 41 | 不过 `await ethereum.enable()` 现在已经被弃用了,未来可能被删除 42 | [官方文档](https://docs.metamask.io/guide/ethereum-provider.html#ethereum-enable-deprecated) 43 | 可以使用 `ethereum.request({ method: 'eth_requestAccounts' })` 代替 44 | 45 | 46 | 47 | ### 48 | 获取网络 id, ganache 中默认的是 1337 我们可以自己设置 ganache 中的网络id 49 | 这个网络id 的作用是用于获取到网络数据,网络数据总有一个 address ,这个才是我们想要的 50 | ```js 51 | const network_id = await web3.eth.net.getId() 52 | console.log('网络id', network_id) 53 | 54 | const network_data = Token.networks[network_id] 55 | console.log('network_data', network_data) 56 | 57 | const network_address = network_data.address 58 | console.log('network_address: ', network_address) 59 | // 注意这里其实可能会报错,因为切换网络时,可能会因为找不到 network_id 而报错 60 | ``` 61 | 62 | ### 63 | 创建一个智能合约, 并在区块链上进行交互 64 | 创建一个智能合约需要两个值,一个就是 abi,一个就是网络地址,这两个值都已经在前面获取到了 65 | 创建一个智能合约语法如下 66 | ```js 67 | new web3.eth.Contract(jsonInterface[, address][, options]) 68 | 69 | const token = new web3.eth.Contract(Token.abi, network_address) 70 | console.log('token', token) 71 | ``` 72 | 73 | ### 74 | 创建一个智能合约后,我们可以调用它的方法 75 | ```js 76 | const totalSupply = await token.methods.totalSupply().call() 77 | console.log('totalSupply ', totalSupply) 78 | ``` 79 | 注意,如果调用该方法时出错,可能是因为没有编译智能合约,或者因为 ganache 更新了 80 | 重新执行一下 `truffle migrate --reset` 就可以了 81 | 82 | ### getPastEvents 83 | 获取历史事件, 每次智能合约执行时, 都会有一个事件产生, 通过这个 api 可以获取所有事件。 84 | 这些事件,可以构成我们的历史交易、历史订单 85 | [文档](https://web3js.readthedocs.io/en/v1.7.3/web3-eth-contract.html?highlight=getPastEvents#getpastevents) 86 | 87 | ```js 88 | myContract.getPastEvents(event[, options][, callback]) 89 | 90 | await exchange.getPastEvents('Cancel', { fromBlock: 0, toBlock: 'latest', }) 91 | ``` 92 | 93 | ## methods.myMethod.send 94 | 调用智能合约中的方法, [该函数有多种实现方式](https://web3js.readthedocs.io/en/v1.7.3/web3-eth-contract.html?highlight=methods.myMethod.send#methods-mymethod-send) 95 | 96 | ```js 97 | myContract.methods.myMethod([param1[, param2[, ...]]]).send(options[, callback]) 98 | 99 | exchange.methods.cancelOrder(order.id).send({from: account}) 100 | .on('transactionHash', hash => { 101 | dispatch(orderCancelling()) 102 | }) 103 | .on('error', error => { 104 | console.log(error) 105 | window.alert('There was an error ! 取消订单失败') 106 | }) 107 | ``` 108 | 109 | ## 订阅事件 110 | 合约中有定义一些事件, 通过订阅中这是事件,当事件发生变化时,我们可以获取该消息. 111 | ```js 112 | myContract.events.MyEvent([options][, callback]) 113 | 114 | exchange.events.Cancel({}, (error, event) => { 115 | dispatch(orderCancelled(event.returnValues)) 116 | }) 117 | ``` 118 | 119 | ## 获取 balance 120 | 121 | ```js 122 | web3.eth.getBalance(address [, defaultBlock] [, callback]) 123 | 124 | ``` 125 | 126 | 127 | 128 | ## abis 129 | 130 | Tokens.js 中的 networks 是智能合约连接的网络 131 | 132 | -------------------------------------------------------------------------------- /src/components/MyTransactions.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { Tab, Tabs } from 'react-bootstrap' 4 | import Spinner from './Spinner' 5 | import { 6 | myFilledOrderLoadedSelector, 7 | myFilledOrderSelector, 8 | myOpenOrdersLoadedSelector, 9 | myOpenOrdersSelector, 10 | exchangeSelector, 11 | accountSelector, 12 | orderCancellingSelector, 13 | } from '../store/selectors' 14 | import { cancelOrder } from '../store/interactions' 15 | 16 | const showMyFilledOrders = (myFilledOrders) => ( 17 | 18 | {myFilledOrders.map((order) => ( 19 | 20 | {order.formattedTimestamp} 21 | 22 | {order.orderSign} 23 | {order.tokenAmount} 24 | 25 | {order.tokenPrice} 26 | 27 | ))} 28 | 29 | ) 30 | 31 | const showMyOpenOrders = (props) => { 32 | const { myOpenOrders, dispatch, exchange, account } = props 33 | return ( 34 | 35 | {myOpenOrders.map((order) => ( 36 | 37 | 38 | {order.tokenAmount} 39 | 40 | {order.tokenPrice} 41 | { 44 | cancelOrder(dispatch, exchange, order, account) 45 | }} 46 | > 47 | × 48 | 49 | 50 | ))} 51 | 52 | ) 53 | } 54 | 55 | class MyTransactions extends Component { 56 | render() { 57 | return ( 58 |
59 |
My Transactions 我的交易
60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | {this.props.myFilledOrderLoaded ? ( 72 | showMyFilledOrders(this.props.myFilledOrder) 73 | ) : ( 74 | 75 | )} 76 |
TimeDAPPDAPP / ETH
77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | {this.props.showMyOpenOrders ? ( 89 | showMyOpenOrders(this.props) 90 | ) : ( 91 | 92 | )} 93 |
AmountDAPP / ETHCancel
94 |
95 |
96 |
97 |
98 | ) 99 | } 100 | } 101 | 102 | function mapStateToProps(state) { 103 | const myOpenOrdersLoaded = myOpenOrdersLoadedSelector(state) 104 | const orderCancelling = orderCancellingSelector(state) 105 | return { 106 | myFilledOrderLoaded: myFilledOrderLoadedSelector(state), 107 | myFilledOrder: myFilledOrderSelector(state), 108 | showMyOpenOrders: myOpenOrdersLoaded && !orderCancelling, 109 | myOpenOrders: myOpenOrdersSelector(state), 110 | exchange: exchangeSelector(state), 111 | account: accountSelector(state), 112 | } 113 | } 114 | 115 | // export default App 116 | export default connect(mapStateToProps)(MyTransactions) 117 | -------------------------------------------------------------------------------- /src/store/action.js: -------------------------------------------------------------------------------- 1 | /** 2 | * action 中的函数,代表行为。 3 | * 这些函数需要通过 dispatch 来调用 4 | * 在此项目中, action.js 中的函数暴露给 interactions.js 使用 5 | * 这里的 action 和 reducers.js 有关系 6 | * 7 | * action: 行为, 对 store 的操作均通过行为来实现, 也就是都需要调用这里面的 action 函数 8 | * 调用不是直接调用, 需要使用 dispatch(action function) 来调用 9 | */ 10 | 11 | // WEB3 12 | export function web3Loaded(connection) { 13 | return { 14 | type: 'WEB3_LOADED', 15 | connection, 16 | } 17 | } 18 | 19 | export function web3AccountLoaded(account) { 20 | return { 21 | type: 'WEB3_ACCOUNT_LOADED', 22 | account, 23 | } 24 | } 25 | 26 | // TOKEN 27 | export function tokenLoaded(contract) { 28 | return { 29 | type: 'TOKEN_LOADED', 30 | contract, 31 | } 32 | } 33 | 34 | // EXCHANGE 35 | export function exchangeLoaded(contract) { 36 | return { 37 | type: 'EXCHANGE_LOADED', 38 | contract, 39 | } 40 | } 41 | 42 | export function cancelledOrdersLoaded(cancelledOrders) { 43 | return { 44 | type: 'CANCELLED_ORDERS_LOADED', 45 | cancelledOrders, 46 | } 47 | } 48 | 49 | export function filledOrdersLoaded(filledOrders) { 50 | return { 51 | type: 'FILLED_ORDERS_LOADED', 52 | filledOrders, 53 | } 54 | } 55 | 56 | export function allOrdersLoaded(allOrders) { 57 | return { 58 | type: 'ALL_ORDERS_LOADED', 59 | allOrders, 60 | } 61 | } 62 | 63 | export function orderCancelling() { 64 | return { 65 | type: 'ORDER_CANCELLING', 66 | } 67 | } 68 | 69 | export function orderCancelled(order) { 70 | return { 71 | type: 'ORDER_CANCELLED', 72 | order, 73 | } 74 | } 75 | 76 | export function orderFilling() { 77 | return { 78 | type: 'ORDER_FILLING', 79 | } 80 | } 81 | export function orderFilled(order) { 82 | return { 83 | type: 'ORDER_FILLED', 84 | order, 85 | } 86 | } 87 | 88 | export function etherBalanceLoaded(balance) { 89 | return { 90 | type: 'ETHER_BALANCE_LOADED', 91 | balance, 92 | } 93 | } 94 | export function tokenBalanceLoaded(balance) { 95 | return { 96 | type: 'TOKEN_BALANCE_LOADED', 97 | balance, 98 | } 99 | } 100 | export function exchangeEtherBalanceLoaded(balance) { 101 | return { 102 | type: 'EXCHANGE_ETHER_BALANCE_LOADED', 103 | balance, 104 | } 105 | } 106 | export function exchangeTokenBalanceLoaded(balance) { 107 | return { 108 | type: 'EXCHANGE_TOKEN_BALANCE_LOADED', 109 | balance, 110 | } 111 | } 112 | export function balancesLoaded() { 113 | return { 114 | type: 'BALANCES_LOADED', 115 | } 116 | } 117 | export function balancesLoading() { 118 | return { 119 | type: 'BALANCES_LOADING', 120 | } 121 | } 122 | 123 | export function etherDepositAmountChanged(amount) { 124 | return { 125 | type: 'ETHER_DEPOSIT_AMOUNT_CHANGED', 126 | amount, 127 | } 128 | } 129 | 130 | export function etherWithdrawAmountChanged(amount) { 131 | return { 132 | type: 'ETHER_WITHDRAW_AMOUNT_CHANGED', 133 | amount, 134 | } 135 | } 136 | 137 | export function tokenDepositAmountChanged(amount) { 138 | return { 139 | type: 'TOKEN_DEPOSIT_AMOUNT_CHANGED', 140 | amount, 141 | } 142 | } 143 | 144 | export function tokenWithdrawAmountChanged(amount) { 145 | return { 146 | type: 'TOKEN_WITHDRAW_AMOUNT_CHANGED', 147 | amount, 148 | } 149 | } 150 | 151 | // Buy order 152 | export function buyOrderAmountChanged(amount) { 153 | return { 154 | type: 'BUY_ORDER_AMOUNT_CHANGED', 155 | amount, 156 | } 157 | } 158 | export function buyOrderPriceChanged(price) { 159 | return { 160 | type: 'BUY_ORDER_PRICE_CHANGED', 161 | price, 162 | } 163 | } 164 | export function buyOrderMaking(price) { 165 | return { 166 | type: 'BUY_ORDER_MAKING', 167 | price, 168 | } 169 | } 170 | export function orderMade(order) { 171 | return { 172 | type: 'ORDER_MADE', 173 | order, 174 | } 175 | } 176 | 177 | // Sell Order 178 | export function sellOrderAmountChanged(amount) { 179 | return { 180 | type: 'SELL_ORDER_AMOUNT_CHANGED', 181 | amount, 182 | } 183 | } 184 | export function sellOrderPriceChanged(price) { 185 | return { 186 | type: 'SELL_ORDER_PRICE_CHANGED', 187 | price, 188 | } 189 | } 190 | export function sellOrderMaking(price) { 191 | return { 192 | type: 'SELL_ORDER_MAKING', 193 | price, 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/store/reducers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dispatch 执行 action 3 | * 此 action 就是这里的函数的 action 参数 4 | * 5 | * action 是行为 6 | * interaction 是获取数据 7 | * interaction 通过调用 dispatch 去 执行 action 8 | * 而 reducers 的作用是拦截?在将数据存入 redux 的时候,会经过这里, 在这里进行一些处理, 比如增加一个 loaded 代表已经加载完成 9 | */ 10 | 11 | import { combineReducers } from 'redux' 12 | 13 | function web3(state = {}, action) { 14 | switch (action.type) { 15 | case 'WEB3_LOADED': 16 | return { ...state, connection: action.connection } 17 | case 'WEB3_ACCOUNT_LOADED': 18 | return { ...state, account: action.account } 19 | case 'ETHER_BALANCE_LOADED': 20 | return { ...state, balance: action.balance } 21 | default: 22 | return state 23 | } 24 | } 25 | 26 | function token(state = {}, action) { 27 | switch (action.type) { 28 | case 'TOKEN_LOADED': 29 | return { ...state, loaded: true, contract: action.contract } 30 | case 'TOKEN_BALANCE_LOADED': 31 | return { ...state, balance: action.balance } 32 | default: 33 | return state 34 | } 35 | } 36 | 37 | function exchange(state = {}, action) { 38 | switch (action.type) { 39 | case 'EXCHANGE_LOADED': 40 | return { ...state, loaded: true, contract: action.contract } 41 | case 'CANCELLED_ORDERS_LOADED': 42 | return { 43 | ...state, 44 | cancelledOrders: { loaded: true, data: action.cancelledOrders }, 45 | } 46 | case 'FILLED_ORDERS_LOADED': 47 | return { 48 | ...state, 49 | filledOrders: { loaded: true, data: action.filledOrders }, 50 | } 51 | case 'ALL_ORDERS_LOADED': 52 | return { ...state, allOrders: { loaded: true, data: action.allOrders } } 53 | case 'ORDER_CANCELLING': 54 | return { ...state, orderCancelling: true } 55 | case 'ORDER_CANCELLED': 56 | return { 57 | ...state, 58 | orderCancelling: false, 59 | cancelledOrders: { 60 | ...state.cancelledOrders, 61 | data: [...state.cancelledOrders.data, action.order], 62 | }, 63 | } 64 | 65 | case 'ORDER_FILLING': 66 | return { ...state, orderFilling: true } 67 | case 'ORDER_FILLED': 68 | return { 69 | ...state, 70 | orderFilling: false, 71 | filledOrders: { 72 | ...state.filledOrders, 73 | data: [...state.filledOrders.data, action.order], 74 | }, 75 | } 76 | 77 | case 'EXCHANGE_ETHER_BALANCE_LOADED': 78 | return { ...state, etherBalance: action.balance } 79 | case 'EXCHANGE_TOKEN_BALANCE_LOADED': 80 | return { ...state, tokenBalance: action.balance } 81 | case 'BALANCES_LOADING': 82 | return { ...state, balancesLoading: true } 83 | case 'BALANCES_LOADED': 84 | return { ...state, balancesLoading: false } 85 | case 'ETHER_DEPOSIT_AMOUNT_CHANGED': 86 | return { ...state, etherDepositAmount: action.amount } 87 | case 'ETHER_WITHDRAW_AMOUNT_CHANGED': 88 | return { ...state, etherWithdrawAmount: action.amount } 89 | case 'TOKEN_DEPOSIT_AMOUNT_CHANGED': 90 | return { ...state, tokenDepositAmount: action.amount } 91 | case 'TOKEN_WITHDRAW_AMOUNT_CHANGED': 92 | return { ...state, tokenWithdrawAmount: action.amount } 93 | case 'BUY_ORDER_AMOUNT_CHANGED': 94 | return { 95 | ...state, 96 | buyOrder: { ...state.buyOrder, amount: action.amount }, 97 | } 98 | case 'BUY_ORDER_PRICE_CHANGED': 99 | return { ...state, buyOrder: { ...state.buyOrder, price: action.price } } 100 | case 'BUY_ORDER_MAKING': 101 | return { 102 | ...state, 103 | buyOrder: { 104 | ...state.buyOrder, 105 | amount: null, 106 | price: null, 107 | making: true, 108 | }, 109 | } 110 | 111 | case 'ORDER_MADE': 112 | // Prevent duplicate orders 113 | const index = state.allOrders.data.findIndex( 114 | (order) => order.id === action.order.id 115 | ) 116 | const data = 117 | index === -1 118 | ? [...state.allOrders.data, action.order] 119 | : state.allOrders.data 120 | return { 121 | ...state, 122 | allOrders: { 123 | ...state.allOrders, 124 | data, 125 | }, 126 | buyOrder: { 127 | ...state.buyOrder, 128 | making: false, 129 | }, 130 | sellOrder: { 131 | ...state.sellOrder, 132 | making: false, 133 | }, 134 | } 135 | 136 | case 'SELL_ORDER_AMOUNT_CHANGED': 137 | return { 138 | ...state, 139 | sellOrder: { ...state.sellOrder, amount: action.amount }, 140 | } 141 | case 'SELL_ORDER_PRICE_CHANGED': 142 | return { 143 | ...state, 144 | sellOrder: { ...state.sellOrder, price: action.price }, 145 | } 146 | case 'SELL_ORDER_MAKING': 147 | return { 148 | ...state, 149 | sellOrder: { 150 | ...state.sellOrder, 151 | amount: null, 152 | price: null, 153 | making: true, 154 | }, 155 | } 156 | 157 | default: 158 | return state 159 | } 160 | } 161 | 162 | const rootReducer = combineReducers({ 163 | web3, 164 | token, 165 | exchange, 166 | }) 167 | 168 | export default rootReducer 169 | -------------------------------------------------------------------------------- /scripts.md: -------------------------------------------------------------------------------- 1 | test 中的是测试 2 | scripts 中的就是实际的脚本 3 | 4 | 在执行 scripts 前, 需要先将合约部署到 ganache 5 | 当 ganache 中重置了账户时, 也需要重新将合约部署到 ganache 6 | 7 | ```bash 8 | truffle migrate --reset 9 | ``` 10 | 11 | 执行 scripts 中的脚本: 12 | ```bash 13 | truffle exec scripts/seed-exchange.js 14 | ``` 15 | 16 | 如果执行时出现错误, 试试新建一个 ganache, 并且重新 `truffle migrate --reset` 一下 17 | 18 | 执行 scripts 中的脚本还有一个作用, 那就是在 react 中调用 web3 时, 可以有相关测试数据看到, 而不是所有数据都是 null 19 | 20 | ## 编译智能合约 21 | 22 | 编译, 编译后的内容才能被区块链识别. 编译后的格式是 json 格式 23 | ```bash 24 | $ truffle compile 25 | 26 | Compiling your contracts... 27 | =========================== 28 | > Compiling .\src\contracts\Migrations.sol 29 | > Compiling .\src\contracts\Token.sol 30 | > Artifacts written to D:\truffle\src\abis 31 | > Compiled successfully using: 32 | - solc: 0.5.0+commit.1d4f565a.Emscripten.clang 33 | ``` 34 | 编译后的路径是根据 `truffle-config.js` 中的配置决定的 35 | ```js 36 | module.exports = { 37 | contracts_directory: './src/contracts/', /* 要编译的智能合约所在位置 */ 38 | contracts_build_directory: './src/abis/', /* 编译后的地址 */ 39 | } 40 | ``` 41 | 42 | ## migrate 43 | migrations 文件夹不太懂,它和迁移、部署智能合约有关 44 | 45 | 1_initial_migration 是迁移 Migrations.sol 46 | 2_deploy_contracts 是部署 Token.sol, Exchange 47 | ```bash 48 | truffle migrate --reset 49 | ``` 50 | 此命令会消耗 gas,即 ganache 中的 eth 会减少 51 | 52 | 53 | ## truffle 控制台交互 54 | ```bash 55 | $ truffle console 56 | truffle(development)> 57 | ``` 58 | 进入后可以直接使用相关命令进行, 如 `const name = await token.name()` 59 | 60 | ### truffle migrate 61 | 62 | `truffle migrate --reset` 会执行 migrations 文件夹下的内容, 并且按照序号执行 63 | ```bash 64 | $ truffle migrate 65 | Compiling your contracts... 66 | =========================== 67 | > Compiling .\src\contracts\Exchange.sol 68 | > Compiling .\src\contracts\Migrations.sol 69 | > Compiling .\src\contracts\Migrations.sol 70 | > Compiling .\src\contracts\Token.sol 71 | > Compiling .\src\contracts\Token.sol 72 | > Artifacts written to D:\truffle\src\abis 73 | > Compiled successfully using: 74 | - solc: 0.5.0+commit.1d4f565a.Emscripten.clang 75 | 76 | 77 | Starting migrations... 78 | ====================== 79 | > Network name: 'development' 80 | > Network id: 5777 81 | > Block gas limit: 6721975 (0x6691b7) 82 | 83 | 84 | 1_initial_migration.js 85 | ====================== 86 | 87 | Deploying 'Migrations' 88 | ---------------------- 89 | ✓ Transaction submitted successfully. Hash: 0xa068c9e27b01e05d3ef84babf626d80dcf60ca38470c1682dd73239f45b42aae 90 | > transaction hash: 0xa068c9e27b01e05d3ef84babf626d80dcf60ca38470c1682dd73239f45b42aae 91 | > Blocks: 0 Seconds: 0 92 | > contract address: 0xC9eb1EEc15aA36e36D97D34E87790b2710F27bcC 93 | > block number: 1 94 | > block timestamp: 1655649189 95 | > account: 0x85d935Fc07B874af8D338e5201FD2fC56DCaB122 96 | > balance: 99.99586798 97 | > gas used: 206601 (0x32709) 98 | > gas price: 20 gwei 99 | > value sent: 0 ETH 100 | > total cost: 0.00413202 ETH 101 | 102 | ✓ Saving migration to chain. 103 | ✓ Transaction submitted successfully. Hash: 0xc5f28de53b038d74f144096a230e80cd02cda55f3c5cc070ce6137ff6feb603b 104 | > Saving migration to chain. 105 | > Saving artifacts 106 | ------------------------------------- 107 | > Total cost: 0.00413202 ETH 108 | 109 | 110 | 2_deploy_contracts.js 111 | ===================== 112 | 113 | Deploying 'Token' 114 | ----------------- 115 | ✓ Transaction submitted successfully. Hash: 0xcec928ef0fa35fcfed5f1c3eb29451b4dfc1759fd7cded87a50fd730033b2f85 116 | > transaction hash: 0xcec928ef0fa35fcfed5f1c3eb29451b4dfc1759fd7cded87a50fd730033b2f85 117 | > Blocks: 0 Seconds: 0 118 | > contract address: 0x1992ffc8AcDEAF88452524ba5063A9edfaFdC80b 119 | > block number: 3 120 | > block timestamp: 1655649190 121 | > account: 0x85d935Fc07B874af8D338e5201FD2fC56DCaB122 122 | > balance: 99.978675 123 | > gas used: 817294 (0xc788e) 124 | > gas price: 20 gwei 125 | > value sent: 0 ETH 126 | > total cost: 0.01634588 ETH 127 | 128 | 129 | Deploying 'Exchange' 130 | -------------------- 131 | ✓ Transaction submitted successfully. Hash: 0x5c90e032504f4a031f3541953a1b805fe06b73c46e622d935352db8ddad59816 132 | > transaction hash: 0x5c90e032504f4a031f3541953a1b805fe06b73c46e622d935352db8ddad59816 133 | > Blocks: 0 Seconds: 0 134 | > contract address: 0x31ea20E38B61e7A6491C2B0562388a53E33775ae 135 | > block number: 4 136 | > block timestamp: 1655649191 137 | > account: 0x85d935Fc07B874af8D338e5201FD2fC56DCaB122 138 | > balance: 99.93880762 139 | > gas used: 1993369 (0x1e6a99) 140 | > gas price: 20 gwei 141 | > value sent: 0 ETH 142 | > total cost: 0.03986738 ETH 143 | 144 | ✓ Saving migration to chain. 145 | ✓ Transaction submitted successfully. Hash: 0x7ad9590b11846626147209ea0e0c90a42f0f3815cf5d11b266089de8f33036cb 146 | > Saving migration to chain. 147 | > Saving artifacts 148 | ------------------------------------- 149 | > Total cost: 0.05621326 ETH 150 | 151 | Summary 152 | ======= 153 | > Total deployments: 3 154 | > Final cost: 0.06034528 ETH 155 | ``` 156 | -------------------------------------------------------------------------------- /src/components/NewOrder.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { Tabs, Tab } from 'react-bootstrap' 4 | import Spinner from './Spinner' 5 | 6 | import { 7 | exchangeSelector, 8 | tokenSelector, 9 | accountSelector, 10 | web3Selector, 11 | buyOrderSelector, 12 | sellOrderSelector, 13 | } from '../store/selectors' 14 | 15 | import { 16 | buyOrderAmountChanged, 17 | buyOrderPriceChanged, 18 | sellOrderAmountChanged, 19 | sellOrderPriceChanged, 20 | } from '../store/action' 21 | 22 | import { makeBuyOrder, makeSellOrder } from '../store/interactions' 23 | 24 | const showForm = (props) => { 25 | const { 26 | dispatch, 27 | exchange, 28 | token, 29 | web3, 30 | buyOrder, 31 | account, 32 | sellOrder, 33 | } = props 34 | 35 | return ( 36 | 37 | 38 |
{ 40 | event.preventDefault() 41 | makeBuyOrder(dispatch, exchange, token, web3, buyOrder, account) 42 | }} 43 | > 44 |
45 | 46 |
47 | 52 | dispatch(buyOrderAmountChanged(e.target.value)) 53 | } 54 | required 55 | /> 56 |
57 |
58 |
59 | 60 |
61 | dispatch(buyOrderPriceChanged(e.target.value))} 66 | required 67 | /> 68 |
69 |
70 | 73 | 74 | Total:{' '} 75 | {buyOrder.amount * buyOrder.price 76 | ? buyOrder.amount * buyOrder.price 77 | : '?'}{' '} 78 | ETH 79 | 80 |
81 |
82 | 83 |
{ 85 | event.preventDefault() 86 | makeSellOrder(dispatch, exchange, token, web3, sellOrder, account) 87 | }} 88 | > 89 |
90 | 91 |
92 | 97 | dispatch(sellOrderAmountChanged(e.target.value)) 98 | } 99 | required 100 | /> 101 |
102 |
103 |
104 | 105 |
106 | 111 | dispatch(sellOrderPriceChanged(e.target.value)) 112 | } 113 | required 114 | /> 115 |
116 |
117 | 120 | 121 | Total:{' '} 122 | {sellOrder.amount * sellOrder.price 123 | ? sellOrder.amount * sellOrder.price 124 | : '?'}{' '} 125 | ETH 126 | 127 |
128 |
129 |
130 | ) 131 | } 132 | 133 | class NewOrder extends Component { 134 | render() { 135 | return ( 136 |
137 |
New Order 创建订单
138 |
139 | {this.props.showForm ? showForm(this.props) : } 140 |
141 |
142 | ) 143 | } 144 | } 145 | 146 | function mapStateToProps(state) { 147 | const buyOrder = buyOrderSelector(state) 148 | const sellOrder = sellOrderSelector(state) 149 | return { 150 | exchange: exchangeSelector(state), 151 | token: tokenSelector(state), 152 | account: accountSelector(state), 153 | web3: web3Selector(state), 154 | buyOrder, 155 | sellOrder, 156 | showForm: !buyOrder.making && !sellOrder.making, 157 | } 158 | } 159 | 160 | // export default App 161 | export default connect(mapStateToProps)(NewOrder) 162 | -------------------------------------------------------------------------------- /truffle-config copy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * https://trufflesuite.com/docs/truffle/reference/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura accounts 13 | * are available for free at: infura.io/register. 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | // const HDWalletProvider = require('@truffle/hdwallet-provider'); 22 | // 23 | // const fs = require('fs'); 24 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 25 | 26 | module.exports = { 27 | /** 28 | * Networks define how you connect to your ethereum client and let you set the 29 | * defaults web3 uses to send transactions. If you don't specify one truffle 30 | * will spin up a development blockchain for you on port 9545 when you 31 | * run `develop` or `test`. You can ask a truffle command to use a specific 32 | * network from the command line, e.g 33 | * 34 | * $ truffle test --network 35 | */ 36 | 37 | networks: { 38 | // Useful for testing. The `development` name is special - truffle uses it by default 39 | // if it's defined here and no other network is specified at the command line. 40 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 41 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 42 | // options below to some value. 43 | // 44 | // development: { 45 | // host: "127.0.0.1", // Localhost (default: none) 46 | // port: 8545, // Standard Ethereum port (default: none) 47 | // network_id: "*", // Any network (default: none) 48 | // }, 49 | // Another network with more advanced options... 50 | // advanced: { 51 | // port: 8777, // Custom port 52 | // network_id: 1342, // Custom network 53 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 54 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 55 | // from:
, // Account to send txs from (default: accounts[0]) 56 | // websocket: true // Enable EventEmitter interface for web3 (default: false) 57 | // }, 58 | // Useful for deploying to a public network. 59 | // NB: It's important to wrap the provider as a function. 60 | // ropsten: { 61 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 62 | // network_id: 3, // Ropsten's id 63 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 64 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 65 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 66 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 67 | // }, 68 | // Useful for private networks 69 | // private: { 70 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 71 | // network_id: 2111, // This network is yours, in the cloud. 72 | // production: true // Treats this network as if it was a public net. (default: false) 73 | // } 74 | }, 75 | 76 | // Set default mocha options here, use special reporters etc. 77 | mocha: { 78 | // timeout: 100000 79 | }, 80 | 81 | // Configure your compilers 82 | compilers: { 83 | solc: { 84 | version: "0.8.13", // Fetch exact version from solc-bin (default: truffle's version) 85 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 86 | // settings: { // See the solidity docs for advice about optimization and evmVersion 87 | // optimizer: { 88 | // enabled: false, 89 | // runs: 200 90 | // }, 91 | // evmVersion: "byzantium" 92 | // } 93 | } 94 | }, 95 | 96 | // Truffle DB is currently disabled by default; to enable it, change enabled: 97 | // false to enabled: true. The default storage location can also be 98 | // overridden by specifying the adapter settings, as shown in the commented code below. 99 | // 100 | // NOTE: It is not possible to migrate your contracts to truffle DB and you should 101 | // make a backup of your artifacts to a safe location before enabling this feature. 102 | // 103 | // After you backed up your artifacts you can utilize db by running migrate as follows: 104 | // $ truffle migrate --reset --compile-all 105 | // 106 | // db: { 107 | // enabled: false, 108 | // host: "127.0.0.1", 109 | // adapter: { 110 | // name: "sqlite", 111 | // settings: { 112 | // directory: ".db" 113 | // } 114 | // } 115 | // } 116 | }; 117 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * https://trufflesuite.com/docs/truffle/reference/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura accounts 13 | * are available for free at: infura.io/register. 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | // const HDWalletProvider = require('@truffle/hdwallet-provider'); 22 | // 23 | // const fs = require('fs'); 24 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 25 | 26 | require('babel-register') 27 | require('babel-polyfill') 28 | require('dotenv').config() 29 | const HDWalletProvider = require('truffle-hdwallet-provider') 30 | const privateKeys = process.env.PRIVATE_KEY || '' 31 | const infuraApiKey = process.env.INFURA_API_KEY 32 | 33 | module.exports = { 34 | /** 35 | * Networks define how you connect to your ethereum client and let you set the 36 | * defaults web3 uses to send transactions. If you don't specify one truffle 37 | * will spin up a development blockchain for you on port 9545 when you 38 | * run `develop` or `test`. You can ask a truffle command to use a specific 39 | * network from the command line, e.g 40 | * 41 | * $ truffle test --network 42 | */ 43 | 44 | networks: { 45 | // Useful for testing. The `development` name is special - truffle uses it by default 46 | // if it's defined here and no other network is specified at the command line. 47 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 48 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 49 | // options below to some value. 50 | // 51 | development: { 52 | // 和 ganache 对应 53 | host: '127.0.0.1', // Localhost (default: none) 54 | port: 7545, // Standard Ethereum port (default: none) 55 | network_id: '*', // Any network (default: none) 56 | }, 57 | ropsten: { 58 | networkCheckTimeout: 60000, // 超过 1min 则提示超时 59 | provider: function() { 60 | return new HDWalletProvider( 61 | privateKeys, // Array of account private keys 62 | `https://ropsten.infura.io/v3/${infuraApiKey}` // Url to an Ethereum Node 63 | ) 64 | }, 65 | gas: 5000000, 66 | gasPrice: 25000000000, 67 | network_id: 3, 68 | }, 69 | // Another network with more advanced options... 70 | // advanced: { 71 | // port: 8777, // Custom port 72 | // network_id: 1342, // Custom network 73 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 74 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 75 | // from:
, // Account to send txs from (default: accounts[0]) 76 | // websocket: true // Enable EventEmitter interface for web3 (default: false) 77 | // }, 78 | // Useful for deploying to a public network. 79 | // NB: It's important to wrap the provider as a function. 80 | // ropsten: { 81 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 82 | // network_id: 3, // Ropsten's id 83 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 84 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 85 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 86 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 87 | // }, 88 | // Useful for private networks 89 | // private: { 90 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 91 | // network_id: 2111, // This network is yours, in the cloud. 92 | // production: true // Treats this network as if it was a public net. (default: false) 93 | // } 94 | }, 95 | 96 | // Set default mocha options here, use special reporters etc. 97 | mocha: { 98 | // timeout: 100000 99 | }, 100 | 101 | // Configure your compilers 102 | contracts_directory: './src/contracts/', 103 | contracts_build_directory: './src/abis/', 104 | compilers: { 105 | solc: { 106 | version: '0.5.0', // Fetch exact version from solc-bin (default: truffle's version) 107 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 108 | // settings: { // See the solidity docs for advice about optimization and evmVersion 109 | optimizer: { 110 | enabled: false, 111 | runs: 200, 112 | }, 113 | // evmVersion: "byzantium" 114 | // } 115 | }, 116 | }, 117 | 118 | // Truffle DB is currently disabled by default; to enable it, change enabled: 119 | // false to enabled: true. The default storage location can also be 120 | // overridden by specifying the adapter settings, as shown in the commented code below. 121 | // 122 | // NOTE: It is not possible to migrate your contracts to truffle DB and you should 123 | // make a backup of your artifacts to a safe location before enabling this feature. 124 | // 125 | // After you backed up your artifacts you can utilize db by running migrate as follows: 126 | // $ truffle migrate --reset --compile-all 127 | // 128 | // db: { 129 | // enabled: false, 130 | // host: "127.0.0.1", 131 | // adapter: { 132 | // name: "sqlite", 133 | // settings: { 134 | // directory: ".db" 135 | // } 136 | // } 137 | // } 138 | } 139 | -------------------------------------------------------------------------------- /scripts/tmep.js: -------------------------------------------------------------------------------- 1 | // 这是一个专门为 truffle 建立的脚本, 代码和测试文件中的代码类似 2 | 3 | const Token = artifacts.require('Token') 4 | const Exchange = artifacts.require('Exchange') 5 | 6 | const ETHER_ADDRESS = '0x0000000000000000000000000000000000000000' // 0x 后面 40 个 0 7 | 8 | const ether = n => { 9 | return new web3.utils.BN( 10 | web3.utils.toWei(n.toString(), 'ether') 11 | ) 12 | } 13 | const tokens = n => ether(n) 14 | 15 | const wait = seconds => { 16 | const milliseconds = seconds * 1000 17 | return new Promise(resolve => setTimeout(resolve, milliseconds)) 18 | } 19 | 20 | 21 | 22 | module.exports = async function(callback) { 23 | try { 24 | 25 | // Fetch accounts from wallet - these are unlocked 先获取一些账户, 这些账户来源于 ganache 26 | const accounts = await web3.eth.getAccounts() 27 | 28 | // Fetch the deployed token 29 | const token = await Token.deployed() 30 | console.log('Token fetched', token.address) 31 | 32 | // Fetch the deployed exchange 33 | const exchange = await Exchange.deployed() 34 | console.log('Exchange fetched', exchange.address) 35 | 36 | console.log('//////////////////////////////////////////////////////////////////') 37 | 38 | // Give tokens to account[1] 39 | const sender = accounts[0] 40 | const receiver = accounts[1] 41 | // let amount = web3.utils.toWei('10000', 'ether') // 10,000 tokens 42 | let amount = ether(10000) // 10,000 tokens 43 | 44 | await token.transfer(receiver, amount, { from: sender }) 45 | console.log(`交易: sender 转账(tokens) 给 receiver`) 46 | console.log(`Transferred ${amount} tokens from ${sender} to ${receiver}`) 47 | 48 | console.log('//////////////////////////////////////////////////////////////////') 49 | // Set up exchange users 50 | const user1 = accounts[0] 51 | const user2 = accounts[1] 52 | 53 | // User1 Deposits Ether 为用户存入一些代币 54 | amount = ether(1) 55 | await exchange.depositEther({ from: user1, value: amount }) 56 | console.log('user1 存款 ether') 57 | console.log(`Deposited ${amount.toString()} Ether from ${user1}`) 58 | 59 | // User2 Approves Tokens 60 | amount = tokens(10000) 61 | await token.approve(exchange.address, amount, { from: user2 }) 62 | console.log('user2 批准 10000 tokens') 63 | console.log(`Approved ${amount.toString()} tokens from ${user2}`) 64 | 65 | // User2 Deposits Tokens 66 | await exchange.depositToken(token.address, amount, { from: user2 }) 67 | console.log('用户2 存了 10000 tokens') 68 | console.log(`Deposited ${amount.toString()} tokens from ${user2}`) 69 | 70 | ////////////////////////////////////////////////////////////// 71 | // Seed a Cancelled Order 72 | // 73 | 74 | // User1 makes order to get tokens 75 | let result 76 | let orderId 77 | result = await exchange.makeOrder(token.address, tokens(100), ETHER_ADDRESS, ether(0.1), { from: user1 }) 78 | console.log('user1 制作(发起)了一个账单, 其中 token 100, ether 0.1') 79 | console.log(`Make order from ${user1}`) 80 | 81 | orderId = result.logs[0].args.id 82 | await exchange.cancelOrder(orderId, { from: user1 }) 83 | console.log('user1 取消了一个账单(刚刚才制作的)') 84 | console.log(`Cancelled order from ${user1}`) 85 | 86 | //////////////////////////////////////////////////////////// 87 | // Seed Filled Orders 88 | // 89 | console.log('///////// Seed Filled Orders ///////////////////////////////////////////') 90 | 91 | // User 1 makes order 92 | result = await exchange.makeOrder(token.address, tokens(100), ETHER_ADDRESS, ether(0.1), { from: user1 }) 93 | console.log('user1 制作了一个账单, 100tokens, 0.1ether') 94 | console.log(`Make order from ${user1}`) 95 | 96 | // User 2 fills order 97 | orderId = result.logs[0].args.id 98 | await exchange.fillOrder(orderId, { from: user2 }) 99 | console.log('user2 填写了账单(收钱)') 100 | console.log(`Filled order from ${user2}`) 101 | 102 | // Wait 1 second 103 | console.log('等一秒......') 104 | await wait(1) 105 | 106 | // User1 makes another order 107 | result = await exchange.makeOrder(token.address, tokens(50), ETHER_ADDRESS, ether(0.01), { from: user1 }) 108 | console.log('user1 又制作了一个账单') 109 | console.log(`Make order from ${user1}`) 110 | 111 | // User 2 fills another order 112 | orderId = result.logs[0].args.id 113 | await exchange.fillOrder(orderId, { from: user2 }) 114 | console.log('user2 又填写了一个账单') 115 | console.log(`Filled order from ${user2}`) 116 | 117 | // Wait 1 second 118 | console.log('等一秒......') 119 | await wait(1) 120 | 121 | // User 1 makes final order 122 | result = await exchange.makeOrder(token.address, tokens(200), ETHER_ADDRESS, ether(0.15), { from: user1 }) 123 | console.log('user1 制作最后一个账单, 200tokens 0.15ether') 124 | console.log(`Make order from ${user1}`) 125 | 126 | // User 2 fills final order 127 | orderId = result.logs[0].args.id 128 | await exchange.fillOrder(orderId, { from: user2 }) 129 | console.log('user2 填写了最后一个账单') 130 | console.log(`Filled order from ${user2}`) 131 | 132 | // Wait 1 second 133 | console.log('等一秒......') 134 | await wait(1) 135 | 136 | ///////////////////////////////////////////////////// 137 | // Seed Open Orders 138 | // 139 | console.log('//////////// Seed Open Orders ///////////////////////////////////////') 140 | 141 | // User1 makes 10 orders 142 | for (let i = 1; i <= 10; i++) { 143 | result = await exchange.makeOrder(token.address, tokens(10 * i), ETHER_ADDRESS, ether(0.01), { from: user1 }) 144 | console.log(`user1 制作第 ${i} 个账单`) 145 | console.log(`Make order from ${user1}`) 146 | // Wait 1 second 147 | console.log('等一秒......') 148 | await wait(1) 149 | } 150 | 151 | // User2 makes 10 orders 152 | for (let i = 1; i <= 10; i++) { 153 | // 为什么这里要将 token 和 ether 互换呢? 154 | result = await exchange.makeOrder(ETHER_ADDRESS, ether(0.01), token.address, tokens(10 * i), { from: user2 }) 155 | console.log(`user2 制作第 ${i} 个账单`) 156 | console.log(`Make order from ${user2}`) 157 | // Wait 1 second 158 | console.log('等一秒......') 159 | await wait(1) 160 | } 161 | 162 | } catch (error) { 163 | console.error(error) 164 | } 165 | 166 | callback() 167 | } -------------------------------------------------------------------------------- /scripts/seed-exchange.js: -------------------------------------------------------------------------------- 1 | // 这是一个专门为 truffle 建立的脚本, 代码和测试文件中的代码类似 2 | 3 | const Token = artifacts.require('Token') 4 | const Exchange = artifacts.require('Exchange') 5 | 6 | const ETHER_ADDRESS = '0x0000000000000000000000000000000000000000' // 0x 后面 40 个 0 7 | 8 | const ether = n => { 9 | return new web3.utils.BN( 10 | web3.utils.toWei(n.toString(), 'ether') 11 | ) 12 | } 13 | const tokens = n => ether(n) 14 | 15 | const wait = seconds => { 16 | const milliseconds = seconds * 1000 17 | return new Promise(resolve => setTimeout(resolve, milliseconds)) 18 | } 19 | 20 | 21 | 22 | module.exports = async function(callback) { 23 | try { 24 | 25 | // Fetch accounts from wallet - these are unlocked 先获取一些账户, 这些账户来源于 ganache 26 | const accounts = await web3.eth.getAccounts() 27 | 28 | // Fetch the deployed token 29 | const token = await Token.deployed() 30 | console.log('Token fetched', token.address) 31 | 32 | // Fetch the deployed exchange 33 | const exchange = await Exchange.deployed() 34 | console.log('Exchange fetched', exchange.address) 35 | 36 | console.log('///// 交易 //////////////////////////////////////////////') 37 | 38 | // Give tokens to account[1] 39 | const sender = accounts[0] 40 | const receiver = accounts[1] 41 | // let amount = web3.utils.toWei('10000', 'ether') // 10,000 tokens 42 | let amount = ether(10000) // 10,000 tokens 43 | 44 | await token.transfer(receiver, amount, { from: sender }) 45 | console.log(`交易: sender 转账(tokens) 给 receiver`) 46 | console.log(`Transferred ${amount} tokens from ${sender} to ${receiver}`) 47 | 48 | console.log('//// 批准代币和存储代币 ////////////////////////////////////////////////////') 49 | // Set up exchange users 50 | const user1 = accounts[0] 51 | const user2 = accounts[1] 52 | 53 | // User1 Deposits Ether 为用户存入一些代币 54 | amount = ether(1) 55 | await exchange.depositEther({ from: user1, value: amount }) 56 | console.log('user1 存款 ether') 57 | console.log(`Deposited ${amount.toString()} Ether from ${user1}`) 58 | 59 | // User2 Approves Tokens 60 | amount = tokens(10000) 61 | await token.approve(exchange.address, amount, { from: user2 }) 62 | console.log('user2 批准 10000 tokens') 63 | console.log(`Approved ${amount.toString()} tokens from ${user2}`) 64 | 65 | // User2 Deposits Tokens 66 | await exchange.depositToken(token.address, amount, { from: user2 }) 67 | console.log('用户2 存了 10000 tokens') 68 | console.log(`Deposited ${amount.toString()} tokens from ${user2}`) 69 | 70 | ////////////////////////////////////////////////////////////// 71 | // Seed a Cancelled Order 72 | // 73 | console.log('/////// Seed a Cancelled Order //////////////////////////////////////////////////') 74 | 75 | // User1 makes order to get tokens 76 | let result 77 | let orderId 78 | result = await exchange.makeOrder(token.address, tokens(100), ETHER_ADDRESS, ether(0.1), { from: user1 }) 79 | console.log('user1 制作(发起)了一个账单, 其中 token 100, ether 0.1') 80 | console.log(`Make order from ${user1}`) 81 | 82 | // console.log(result) 83 | orderId = result.logs[0].args.id 84 | await exchange.cancelOrder(orderId, { from: user1 }) 85 | console.log('user1 取消了一个账单(刚刚才制作的)') 86 | console.log(`Cancelled order from ${user1}`) 87 | 88 | //////////////////////////////////////////////////////////// 89 | // Seed Filled Orders 90 | // 91 | console.log('///////// Seed Filled Orders ///////////////////////////////////////////') 92 | 93 | // User 1 makes order 94 | result = await exchange.makeOrder(token.address, tokens(100), ETHER_ADDRESS, ether(0.1), { from: user1 }) 95 | console.log('user1 制作了一个账单, 100tokens, 0.1ether') 96 | console.log(`Make order from ${user1}`) 97 | 98 | // User 2 fills order 99 | orderId = result.logs[0].args.id 100 | await exchange.fillOrder(orderId, { from: user2 }) 101 | console.log('user2 填写了账单(收钱)') 102 | console.log(`Filled order from ${user2}`) 103 | 104 | // Wait 1 second 105 | console.log('等一秒......') 106 | await wait(1) 107 | 108 | // User1 makes another order 109 | result = await exchange.makeOrder(token.address, tokens(50), ETHER_ADDRESS, ether(0.01), { from: user1 }) 110 | console.log('user1 又制作了一个账单') 111 | console.log(`Make order from ${user1}`) 112 | 113 | // User 2 fills another order 114 | orderId = result.logs[0].args.id 115 | await exchange.fillOrder(orderId, { from: user2 }) 116 | console.log('user2 又填写了一个账单') 117 | console.log(`Filled order from ${user2}`) 118 | 119 | // Wait 1 second 120 | console.log('等一秒......') 121 | await wait(1) 122 | 123 | // User 1 makes final order 124 | result = await exchange.makeOrder(token.address, tokens(200), ETHER_ADDRESS, ether(0.15), { from: user1 }) 125 | console.log('user1 制作最后一个账单, 200tokens 0.15ether') 126 | console.log(`Make order from ${user1}`) 127 | 128 | // User 2 fills final order 129 | orderId = result.logs[0].args.id 130 | await exchange.fillOrder(orderId, { from: user2 }) 131 | console.log('user2 填写了最后一个账单') 132 | console.log(`Filled order from ${user2}`) 133 | 134 | // Wait 1 second 135 | console.log('等一秒......') 136 | await wait(1) 137 | 138 | ///////////////////////////////////////////////////// 139 | // Seed Open Orders 140 | // 141 | console.log('//////////// Seed Open Orders ///////////////////////////////////////') 142 | 143 | // User1 makes 10 orders 144 | for (let i = 1; i <= 10; i++) { 145 | result = await exchange.makeOrder(token.address, tokens(10 * i), ETHER_ADDRESS, ether(0.01), { from: user1 }) 146 | console.log(`user1 制作第 ${i} 个账单`) 147 | console.log(`Make order from ${user1}`) 148 | // Wait 1 second 149 | console.log('等一秒......') 150 | await wait(1) 151 | } 152 | 153 | // User2 makes 10 orders 154 | for (let i = 1; i <= 10; i++) { 155 | // 为什么这里要将 token 和 ether 互换呢? 156 | result = await exchange.makeOrder(ETHER_ADDRESS, ether(0.01), token.address, tokens(10 * i), { from: user2 }) 157 | console.log(`user2 制作第 ${i} 个账单`) 158 | console.log(`Make order from ${user2}`) 159 | // Wait 1 second 160 | console.log('等一秒......') 161 | await wait(1) 162 | } 163 | 164 | } catch (error) { 165 | console.error(error) 166 | } 167 | 168 | callback() 169 | } -------------------------------------------------------------------------------- /test/Token.test.js: -------------------------------------------------------------------------------- 1 | import { tokens, EVM_REVERT } from './helpers' 2 | 3 | const Token = artifacts.require('./Token') 4 | 5 | require('chai') 6 | .use(require('chai-as-promised')) 7 | .should() 8 | 9 | 10 | contract('Token', ([deployer, receiver, exchange]) => { 11 | 12 | const name = 'DAPP Token' 13 | const symbol = 'DAPP' 14 | const decimals = '18' 15 | const totalSupply = tokens(1000000) 16 | let token 17 | 18 | beforeEach(async () => { 19 | token = await Token.new() 20 | }) 21 | 22 | describe('deployment', () => { 23 | it('tracks the name', async () => { 24 | const result = await token.name() 25 | // Check the token name is 'My name' 26 | result.should.equal(name) 27 | }) 28 | it('tracks the symbol', async () => { 29 | const result = await token.symbol() 30 | result.should.equal(symbol) 31 | }) 32 | it('tracks the decimals', async () => { 33 | const result = await token.decimals() 34 | result.toString().should.equal(decimals) 35 | }) 36 | it('tracks the total supply', async () => { 37 | const result = await token.totalSupply() 38 | result.toString().should.equal(totalSupply.toString()) 39 | }) 40 | it('assigns the total supply to the deployer', async () => { 41 | const result = await token.balanceOf(deployer) 42 | result.toString().should.equal(totalSupply.toString()) 43 | }) 44 | }) 45 | 46 | describe('sending tokens', () => { 47 | let result 48 | let amount 49 | 50 | describe('success', () => { 51 | beforeEach(async () => { 52 | amount = tokens(100) 53 | result = await token.transfer(receiver, amount, { from: deployer }) 54 | }) 55 | 56 | it('transfers token balances', async () => { 57 | let balanceOf 58 | balanceOf = await token.balanceOf(deployer) 59 | balanceOf.toString().should.equal(tokens(999900).toString()) 60 | balanceOf = await token.balanceOf(receiver) 61 | balanceOf.toString().should.equal(tokens(100).toString()) 62 | }) 63 | it('emit the Transfer event', async () => { 64 | const log = result.logs[0] 65 | log.event.should.eq('Transfer') 66 | 67 | const event = log.args 68 | event.from.toString().should.equal(deployer, 'from is correct') 69 | event.to.toString().should.equal(receiver, 'to is correct') 70 | event.value.toString().should.equal(amount.toString(), 'value is correct') 71 | }) 72 | }) 73 | 74 | describe('failure', () => { 75 | it('rejects insufficient balances', async () => { 76 | let invalidAmount 77 | invalidAmount = tokens(100000000) // 100 million - greater than total supply 78 | await token.transfer(receiver, invalidAmount, { from: deployer }).should.be.rejectedWith(EVM_REVERT) 79 | 80 | // Attempt transfer tokens, when you have none 81 | invalidAmount = tokens(10) 82 | await token.transfer(deployer, invalidAmount, { from: receiver }).should.be.rejectedWith(EVM_REVERT) 83 | }) 84 | it('rejects invalid recipients', async () => { 85 | await token.transfer(0x0, amount, { from: deployer }).should.be.rejected 86 | }) 87 | }) 88 | }) 89 | 90 | describe('approving tokens', () => { 91 | let result 92 | let amount 93 | 94 | beforeEach(async () => { 95 | amount = tokens(100) 96 | result = await token.approve(exchange, amount, { from: deployer }) 97 | }) 98 | 99 | describe('success', () => { 100 | it('allocates an allowance for delegated token spending on exchange', async () => { 101 | const allowance = await token.allowance(deployer, exchange) 102 | allowance.toString().should.equal(amount.toString()) 103 | }) 104 | it('emit the Approval event', async () => { 105 | const log = result.logs[0] 106 | log.event.should.eq('Approval') 107 | 108 | const event = log.args 109 | event._owner.toString().should.equal(deployer, 'owner is correct') 110 | event._spender.should.equal(exchange, 'spender is correct') 111 | event._value.toString().should.equal(amount.toString(), 'value is correct') 112 | }) 113 | }) 114 | 115 | describe('failure', () => { 116 | it('rejects invalid spenders', async () => { 117 | await token.approve(0x0, amount, { from: deployer }).should.be.rejected 118 | }) 119 | }) 120 | }) 121 | 122 | describe('delegated token transfers', () => { 123 | let result 124 | let amount 125 | 126 | beforeEach(async () => { 127 | amount = tokens(100) 128 | await token.approve(exchange, amount, { from: deployer }) 129 | }) 130 | describe('success', () => { 131 | beforeEach(async () => { 132 | result = await token.transferFrom(deployer, receiver, amount, { from: exchange }) 133 | }) 134 | 135 | it('transfers token balances', async () => { 136 | let balanceOf 137 | balanceOf = await token.balanceOf(deployer) 138 | balanceOf.toString().should.equal(tokens(999900).toString()) 139 | balanceOf = await token.balanceOf(receiver) 140 | balanceOf.toString().should.equal(tokens(100).toString()) 141 | }) 142 | it('resets the allowance', async () => { 143 | const allowance = await token.allowance(deployer, exchange) 144 | allowance.toString().should.equal('0') 145 | }) 146 | it('emit the Transfer event', async () => { 147 | const log = result.logs[0] 148 | log.event.should.eq('Transfer') 149 | 150 | const event = log.args 151 | event.from.toString().should.equal(deployer, 'from is correct') 152 | event.to.toString().should.equal(receiver, 'to is correct') 153 | event.value.toString().should.equal(amount.toString(), 'value is correct') 154 | }) 155 | }) 156 | 157 | describe('failure', () => { 158 | it('rejects insufficient balances', async () => { 159 | let invalidAmount 160 | invalidAmount = tokens(100000000) // 100 million - greater than total supply 161 | await token.transfer(receiver, invalidAmount, { from: deployer }).should.be.rejectedWith(EVM_REVERT) 162 | 163 | // Attempt transfer tokens, when you have none 164 | invalidAmount = tokens(10) 165 | await token.transfer(deployer, invalidAmount, { from: receiver }).should.be.rejectedWith(EVM_REVERT) 166 | }) 167 | it('rejects invalid recipients', async () => { 168 | await token.transfer(0x0, amount, { from: deployer }).should.be.rejected 169 | }) 170 | }) 171 | }) 172 | 173 | }) -------------------------------------------------------------------------------- /scripts/seed-exchange2.js: -------------------------------------------------------------------------------- 1 | // 这是一个专门为 truffle 建立的脚本, 代码和测试文件中的代码类似 2 | 3 | const Token = artifacts.require('Token') 4 | const Exchange = artifacts.require('Exchange') 5 | 6 | const ETHER_ADDRESS = '0x0000000000000000000000000000000000000000' // 0x 后面 40 个 0 7 | 8 | const ether = (n) => { 9 | return new web3.utils.BN(web3.utils.toWei(n.toString(), 'ether')) 10 | } 11 | const tokens = (n) => ether(n) 12 | 13 | const wait = (seconds) => { 14 | const milliseconds = seconds * 1000 15 | return new Promise((resolve) => setTimeout(resolve, milliseconds)) 16 | } 17 | 18 | module.exports = async function(callback) { 19 | try { 20 | // Fetch accounts from wallet - these are unlocked 先获取一些账户, 这些账户来源于 ganache 21 | const accounts = await web3.eth.getAccounts() 22 | 23 | // Fetch the deployed token 24 | const token = await Token.deployed() 25 | console.log('Token fetched', token.address) 26 | 27 | // Fetch the deployed exchange 28 | const exchange = await Exchange.deployed() 29 | console.log('Exchange fetched', exchange.address) 30 | 31 | console.log('///// 交易 //////////////////////////////////////////////') 32 | 33 | // Give tokens to account[1] 34 | const sender = accounts[0] 35 | const receiver = accounts[1] 36 | // let amount = web3.utils.toWei('10000', 'ether') // 10,000 tokens 37 | let amount = ether(100000) // 10,000 tokens 38 | 39 | await token.transfer(receiver, amount, { from: sender }) 40 | console.log(`交易: sender 转账(tokens) 给 receiver`) 41 | console.log(`Transferred ${amount} tokens from ${sender} to ${receiver}`) 42 | 43 | console.log( 44 | '//// 批准代币和存储代币 ////////////////////////////////////////////////////' 45 | ) 46 | // Set up exchange users 47 | const user1 = accounts[0] 48 | const user2 = accounts[1] 49 | 50 | // User1 Deposits Ether 为用户存入一些代币 51 | amount = ether(10) 52 | await exchange.depositEther({ from: user1, value: amount }) 53 | console.log('user1 存款 ether') 54 | console.log(`Deposited ${amount.toString()} Ether from ${user1}`) 55 | 56 | // User2 Approves Tokens 57 | amount = tokens(100000) 58 | await token.approve(exchange.address, amount, { from: user2 }) 59 | console.log('user2 批准 10000 tokens') 60 | console.log(`Approved ${amount.toString()} tokens from ${user2}`) 61 | 62 | // User2 Deposits Tokens 63 | await exchange.depositToken(token.address, amount, { from: user2 }) 64 | console.log('用户2 存了 10000 tokens') 65 | console.log(`Deposited ${amount.toString()} tokens from ${user2}`) 66 | 67 | ////////////////////////////////////////////////////////////// 68 | // Seed a Cancelled Order 69 | // 70 | console.log( 71 | '/////// Seed a Cancelled Order //////////////////////////////////////////////////' 72 | ) 73 | 74 | // User1 makes order to get tokens 75 | let result 76 | let orderId 77 | result = await exchange.makeOrder( 78 | token.address, 79 | tokens(500), 80 | ETHER_ADDRESS, 81 | ether(0.1), 82 | { from: user1 } 83 | ) 84 | console.log('user1 制作(发起)了一个账单, 其中 token 100, ether 0.1') 85 | console.log(`Make order from ${user1}`) 86 | 87 | // console.log(result) 88 | orderId = result.logs[0].args.id 89 | await exchange.cancelOrder(orderId, { from: user1 }) 90 | console.log('user1 取消了一个账单(刚刚才制作的)') 91 | console.log(`Cancelled order from ${user1}`) 92 | 93 | //////////////////////////////////////////////////////////// 94 | // Seed Filled Orders 95 | // 96 | console.log( 97 | '///////// Seed Filled Orders ///////////////////////////////////////////' 98 | ) 99 | 100 | // User 1 makes order 101 | result = await exchange.makeOrder( 102 | token.address, 103 | tokens(500), 104 | ETHER_ADDRESS, 105 | ether(0.1), 106 | { from: user1 } 107 | ) 108 | console.log('user1 制作了一个账单, 100tokens, 0.1ether') 109 | console.log(`Make order from ${user1}`) 110 | 111 | // User 2 fills order 112 | orderId = result.logs[0].args.id 113 | await exchange.fillOrder(orderId, { from: user2 }) 114 | console.log('user2 填写了账单(收钱)') 115 | console.log(`Filled order from ${user2}`) 116 | 117 | // Wait 1 second 118 | console.log('等一秒......') 119 | await wait(1) 120 | 121 | // User1 makes another order 122 | result = await exchange.makeOrder( 123 | token.address, 124 | tokens(5), 125 | ETHER_ADDRESS, 126 | ether(0.1), 127 | { from: user1 } 128 | ) 129 | console.log('user1 又制作了一个账单') 130 | console.log(`Make order from ${user1}`) 131 | 132 | // User 2 fills another order 133 | orderId = result.logs[0].args.id 134 | await exchange.fillOrder(orderId, { from: user2 }) 135 | console.log('user2 又填写了一个账单') 136 | console.log(`Filled order from ${user2}`) 137 | 138 | // Wait 1 second 139 | console.log('等一秒......') 140 | await wait(1) 141 | 142 | // User 1 makes final order 143 | result = await exchange.makeOrder( 144 | token.address, 145 | tokens(2000), 146 | ETHER_ADDRESS, 147 | ether(0.51), 148 | { from: user1 } 149 | ) 150 | console.log('user1 制作最后一个账单, 200tokens 0.15ether') 151 | console.log(`Make order from ${user1}`) 152 | 153 | // User 2 fills final order 154 | orderId = result.logs[0].args.id 155 | await exchange.fillOrder(orderId, { from: user2 }) 156 | console.log('user2 填写了最后一个账单') 157 | console.log(`Filled order from ${user2}`) 158 | 159 | // Wait 1 second 160 | console.log('等一秒......') 161 | await wait(1) 162 | 163 | ///////////////////////////////////////////////////// 164 | // Seed Open Orders 165 | // 166 | console.log( 167 | '//////////// Seed Open Orders ///////////////////////////////////////' 168 | ) 169 | 170 | // User1 makes 10 orders 171 | for (let i = 1; i <= 10; i++) { 172 | result = await exchange.makeOrder( 173 | token.address, 174 | tokens(10 * i), 175 | ETHER_ADDRESS, 176 | ether(0.01), 177 | { from: user1 } 178 | ) 179 | console.log(`user1 制作第 ${i} 个账单`) 180 | console.log(`Make order from ${user1}`) 181 | // Wait 1 second 182 | console.log('等一秒......') 183 | await wait(1) 184 | } 185 | 186 | // User2 makes 10 orders 187 | for (let i = 1; i <= 10; i++) { 188 | // 为什么这里要将 token 和 ether 互换呢? 189 | result = await exchange.makeOrder( 190 | ETHER_ADDRESS, 191 | ether(0.01), 192 | token.address, 193 | tokens(10 * i), 194 | { from: user2 } 195 | ) 196 | console.log(`user2 制作第 ${i} 个账单`) 197 | console.log(`Make order from ${user2}`) 198 | // Wait 1 second 199 | console.log('等一秒......') 200 | await wait(1) 201 | } 202 | } catch (error) { 203 | console.error(error) 204 | } 205 | 206 | callback() 207 | } 208 | -------------------------------------------------------------------------------- /src/contracts/Exchange.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./Token.sol"; 4 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 5 | 6 | // Deposit & Withdraw Funds 存款 取款 7 | // Manage Order - Make or Cancel 账单的发起和取消 8 | // Handle Trades - Charge fees 处理交易 收小费 9 | 10 | // TODO: 11 | // [X] Set the fee account 12 | // [X] Deposit Ether 对应 depositEther 13 | // [X] Withdraw Ether 14 | // [X] Deposit tokens 对应 depositToken 15 | // [X] Withdraw tokens 16 | // [X] Check balances 17 | // [X] Make order 18 | // [X] Cancel order 19 | // [X] Fill order 20 | // [X] Charge fees 21 | 22 | contract Exchange { 23 | using SafeMath for uint; 24 | 25 | // Variables 26 | address public feeAccount; // the account that receives exchange fee 27 | uint256 public feePercent; // the fee percentage 28 | address constant ETHER = address(0); // store Ether in tokens mapping with black address 29 | mapping(address => mapping(address => uint256)) public tokens; // 代币 => (实际用户地址 => 用户持有的代币数量) 30 | mapping(uint256 => _Order) public orders; // a way store the order 31 | uint public orderCount; 32 | mapping(uint256 => bool) public orderCancelled; // 注意取消订单的逻辑: 是新建一个 “已取消的订单” 映射, 而不是在订单中删除对应的订单 33 | mapping(uint256 => bool) public orderFilled; // 完成了的订单将会被标志 34 | 35 | // Event 36 | event Deposit(address token, address user, uint256 amount, uint256 balance); 37 | event Withdraw(address token, address user, uint256 amount, uint256 balance); 38 | event Order ( 39 | uint256 id, 40 | address user, 41 | address tokenGet, 42 | uint256 amountGet, 43 | address tokenGive, 44 | uint256 amountGive, 45 | uint256 timestamp 46 | ); 47 | event Cancel ( 48 | uint256 id, 49 | address user, 50 | address tokenGet, 51 | uint256 amountGet, 52 | address tokenGive, 53 | uint256 amountGive, 54 | uint256 timestamp 55 | ); 56 | event Trade ( 57 | uint256 id, 58 | address user, 59 | address tokenGet, 60 | uint256 amountGet, 61 | address tokenGive, 62 | uint256 amountGive, 63 | address userFill, 64 | uint256 timestamp 65 | ); 66 | 67 | // a way to model the order 68 | struct _Order { 69 | uint256 id; // 订单的唯一 id 70 | address user; // 发出订单的人 71 | address tokenGet; // token address 72 | uint256 amountGet; // toknen amount 73 | address tokenGive; // eg: token give is ether 74 | uint256 amountGive; // eg: ether amount 75 | uint256 timestamp; // 订单时间戳 76 | } 77 | 78 | constructor (address _feeAccount, uint256 _feePercent) public { 79 | feeAccount = _feeAccount; 80 | feePercent = _feePercent; 81 | } 82 | 83 | // Fallback: reverts if Ether is not sent to this smart contract by mistake 如果有人直接送钱给交易所(不是存款), 则要将钱退回. 不过好像注释掉这一个函数也会自动拒绝直接送钱 84 | function() external { 85 | revert(); 86 | } 87 | 88 | function depositEther() payable public { 89 | tokens[ETHER][msg.sender] = tokens[ETHER][msg.sender].add(msg.value); 90 | emit Deposit(ETHER, msg.sender, msg.value, tokens[ETHER][msg.sender]); 91 | } 92 | 93 | function withdrawEther(uint256 _amount) public { 94 | require(tokens[ETHER][msg.sender] >= _amount); // 确保有余额转钱 95 | tokens[ETHER][msg.sender] = tokens[ETHER][msg.sender].sub(_amount); 96 | msg.sender.transfer(_amount); 97 | emit Withdraw(ETHER, msg.sender, _amount, tokens[ETHER][msg.sender]); 98 | } 99 | 100 | // (which token, how much) 101 | function depositToken(address _token, uint _amount) public { 102 | // Don't allow Enter deposits 103 | require(_token != ETHER); 104 | // Send tokens to this contract 105 | require(Token(_token).transferFrom(msg.sender, address(this), _amount)); 106 | // Manage deposit - update balance 107 | tokens[_token][msg.sender] = tokens[_token][msg.sender].add(_amount); 108 | // Emit event 109 | emit Deposit(_token, msg.sender, _amount, tokens[_token][msg.sender]); 110 | } 111 | 112 | function withdrawToken(address _token, uint256 _amount) public { 113 | require(_token != ETHER); 114 | require(tokens[_token][msg.sender] >= _amount); 115 | tokens[_token][msg.sender] = tokens[_token][msg.sender].sub(_amount); 116 | require(Token(_token).transfer(msg.sender, _amount)); 117 | emit Withdraw(_token, msg.sender, _amount, tokens[_token][msg.sender]); 118 | } 119 | 120 | function balanceOf(address _token, address _user) public view returns (uint256) { 121 | return tokens[_token][_user]; 122 | } 123 | 124 | // add the order to storage 125 | function makeOrder(address _tokenGet, uint256 _amountGet, address _tokenGive, uint256 _amountGive) public { 126 | orderCount = orderCount.add(1); 127 | orders[orderCount] = _Order(orderCount, msg.sender, _tokenGet, _amountGet, _tokenGive, _amountGive, now); 128 | emit Order(orderCount, msg.sender, _tokenGet, _amountGet, _tokenGive, _amountGive, now); 129 | } 130 | 131 | function cancelOrder(uint256 _id) public { 132 | _Order storage _order = orders[_id]; // storage 存在存储器中, 作为变量 133 | // must be "my" order 134 | require(address(_order.user) == msg.sender); 135 | // must be a valid order: the order must exist 136 | require(_order.id == _id); 137 | orderCancelled[_id] = true; 138 | emit Cancel(_order.id, msg.sender, _order.tokenGet, _order.amountGet, _order.tokenGive, _order.amountGive, _order.timestamp); 139 | } 140 | 141 | function fillOrder(uint256 _id) public { 142 | require(_id > 0 && _id <= orderCount); // make sure this order id is valid 143 | require(!orderFilled[_id]); 144 | require(!orderCancelled[_id]); 145 | // Fetch the Order 获取订单 146 | _Order storage _order = orders[_id]; 147 | // 执行订单相关操作 148 | _trade(_order.id, _order.user, _order.tokenGet, _order.amountGet, _order.tokenGive, _order.amountGive); 149 | // Mark order as filled. 标记为 filled 150 | orderFilled[_order.id] = true; 151 | } 152 | 153 | function _trade(uint256 _id, address _user, address _tokenGet, uint256 _amountGet, address _tokenGive, uint256 _amountGive) internal { 154 | // msg.sender 是填写订单(fill)的人, _user 是创建订单(make)的人 (?) 155 | // Charge fees 156 | // Fee paid by the user that fills the order. 小费由填写订单(fill)的人支付 157 | // Fee deducted from _amountGet 158 | uint256 _feeAmount = _amountGive.mul(feePercent).div(100); 159 | 160 | // Execute tarde. 这里执行的交易, 就是余额互换(再加上小费). 发送者要发送多少, 我们就要将他们的余额减去多少 161 | tokens[_tokenGet][msg.sender] = tokens[_tokenGet][msg.sender].sub(_amountGet.add(_feeAmount)); 162 | tokens[_tokenGet][_user] = tokens[_tokenGet][_user].add(_amountGet); 163 | tokens[_tokenGet][feeAccount] = tokens[_tokenGet][feeAccount].add(_feeAmount); // 将小费送到 feeAccount 164 | tokens[_tokenGive][_user] = tokens[_tokenGive][_user].sub(_amountGive); // get 增, give 减 165 | tokens[_tokenGive][msg.sender] = tokens[_tokenGive][msg.sender].add(_amountGive); 166 | 167 | // Emit trade event 168 | emit Trade(_id, _user, _tokenGet, _amountGet, _tokenGive, _amountGive, msg.sender, now); 169 | } 170 | 171 | } -------------------------------------------------------------------------------- /test相关.md: -------------------------------------------------------------------------------- 1 | 2 | ## 库 3 | 4 | 测试用的库是 chai 5 | 6 | ```js 7 | require('chai') 8 | .use(require('chai-as-promised')) // 使用 promise 9 | .should() 10 | ``` 11 | 12 | ## 注意 13 | 14 | * 运行测试时, 在部署好合约(✓ Transaction)之前, 对测试代码的修改都会影响到结果 15 | 16 | * 对于异步的操作, 即使没有用到他的返回值, 也需要使用 `await`, 这样才是将异步变成“同步” 17 | 18 | 19 | 20 | ## 相关解释 21 | 22 | * describe 23 | 类似于分组的功能, 每一个 describe 里面可以再有一个 describe, 用来描述本组测试的信息. 24 | describe('failure') 该组中就是测试一些错误请求, 然后期待我们的合约能够判断出这是错误的. 25 | describe('success') 则相反. 26 | 27 | sol 中的 require() 部分, 是断言. 28 | describe('failure') 就是用来测试这些 require 是否断言成功的 29 | 30 | * it 31 | 测试, 第一个参数是该测试的描述, 第二个参数就是测试代码. 32 | 每一个 it 都是相互独立的, 每一个都是全新的开始. 33 | 34 | * beforeEach 35 | 在一个分组 describe 中有多个 it, 每一个 it 都有执行的语句可以抽离到 beforeEach 中. 36 | 即 beforeEach 中的语句, 会在每一个 it 中执行 37 | 38 | * should 39 | should 函数就是用来测试的, 是否通过测试就看 should 后面 40 | 41 | * should.be.rejectedWith(EVM_REVERT) 42 | 我们模拟了一个错误的请求方式, 然后期待我们的合约能够判断出这是错误的, 并且报错的类型与我们制定的一致(EVM_REVERT) 43 | 44 | 比如: AssertionError: expected promise to be rejected with an error including 'VM Exception while processing transaction: revert' but got 'invalid address... 45 | 这个报错就说明了我们的测试不成功, 我们期待程序检测出的错误是 `VM Exception while processing transaction: revert`, 46 | 但是程序给我们检测出的错误却是: invalid address 无效的地址 47 | 48 | 再如: AssertionError: expected promise to be rejected with an error including 'VM Exception while processing transaction: revert' but it was fulfilled with 49 | 报错已经说得很明白了, 我们期待程序发现错误, 结果他却让他直接成功通过了 50 | 51 | 52 | * contract('Exchange', ([deployer, feeAccount, user1]) => {}) 53 | 这一个应该是部署合约, 54 | 55 | * ✓ Transaction submitted successfully. Hash: 0x... 56 | 这一个是成功的交易, 部署合约时会有 4 条交易记录, 每次 `truffle test` 的时候, 57 | 我们会发现在开始测试之前, 总会有 4 条交易, 这个就是用来部署合约的津贴吧. 58 | 59 | * function metadata 函数元数据 60 | 在测试中, 可以看到很多函数都有一个对象参数, 比如 61 | token.transfer(user1, tokens(100), { from: deployer }), 62 | transfer 函数声明格式是这样的 transfer (address _to, uint256 _value) 63 | 可以看到, 并没有第三个参数用来接收 { from: deployer } 64 | 其实 { from: deployer } 这样的, 就是 function metadata, 65 | 并不需要在函数中声明 66 | 67 | ## 测试代码 68 | 69 | ```js 70 | import { tokens, EVM_REVERT } from './helpers' 71 | 72 | const Token = artifacts.require('./Token') 73 | 74 | require('chai') 75 | .use(require('chai-as-promised')) 76 | .should() 77 | 78 | 79 | contract('Token', ([deployer, receiver, exchange]) => { 80 | 81 | const name = 'DAPP Token' 82 | const symbol = 'DAPP' 83 | const decimals = '18' 84 | const totalSupply = tokens(1000000) 85 | let token 86 | 87 | beforeEach(async () => { 88 | token = await Token.new() 89 | }) 90 | 91 | describe('deployment', () => { 92 | it('tracks the name', async () => { 93 | const result = await token.name() 94 | // Check the token name is 'My name' 95 | result.should.equal(name) 96 | }) 97 | it('tracks the symbol', async () => { 98 | const result = await token.symbol() 99 | result.should.equal(symbol) 100 | }) 101 | it('tracks the decimals', async () => { 102 | const result = await token.decimals() 103 | result.toString().should.equal(decimals) 104 | }) 105 | it('tracks the total supply', async () => { 106 | const result = await token.totalSupply() 107 | result.toString().should.equal(totalSupply.toString()) 108 | }) 109 | it('assigns the total supply to the deployer', async () => { 110 | const result = await token.balanceOf(deployer) 111 | result.toString().should.equal(totalSupply.toString()) 112 | }) 113 | }) 114 | 115 | describe('sending tokens', () => { 116 | let result 117 | let amount 118 | 119 | describe('success', () => { 120 | beforeEach(async () => { 121 | amount = tokens(100) 122 | result = await token.transfer(receiver, amount, { from: deployer }) 123 | }) 124 | 125 | it('transfers token balances', async () => { 126 | let balanceOf 127 | balanceOf = await token.balanceOf(deployer) 128 | balanceOf.toString().should.equal(tokens(999900).toString()) 129 | balanceOf = await token.balanceOf(receiver) 130 | balanceOf.toString().should.equal(tokens(100).toString()) 131 | }) 132 | it('emit the Transfer event', async () => { 133 | const log = result.logs[0] 134 | log.event.should.eq('Transfer') 135 | 136 | const event = log.args 137 | event.from.toString().should.equal(deployer, 'from is correct') 138 | event.to.toString().should.equal(receiver, 'to is correct') 139 | event.value.toString().should.equal(amount.toString(), 'value is correct') 140 | }) 141 | }) 142 | 143 | describe('failure', () => { 144 | it('rejects insufficient balances', async () => { 145 | let invalidAmount 146 | invalidAmount = tokens(100000000) // 100 million - greater than total supply 147 | await token.transfer(receiver, invalidAmount, { from: deployer }).should.be.rejectedWith(EVM_REVERT) 148 | 149 | // Attempt transfer tokens, when you have none 150 | invalidAmount = tokens(10) 151 | await token.transfer(deployer, invalidAmount, { from: receiver }).should.be.rejectedWith(EVM_REVERT) 152 | }) 153 | it('rejects invalid recipients', async () => { 154 | await token.transfer(0x0, amount, { from: deployer }).should.be.rejected 155 | }) 156 | }) 157 | }) 158 | 159 | describe('approving tokens', () => { 160 | let result 161 | let amount 162 | 163 | beforeEach(async () => { 164 | amount = tokens(100) 165 | result = await token.approve(exchange, amount, { from: deployer }) 166 | }) 167 | 168 | describe('success', () => { 169 | it('allocates an allowance for delegated token spending on exchange', async () => { 170 | const allowance = await token.allowance(deployer, exchange) 171 | allowance.toString().should.equal(amount.toString()) 172 | }) 173 | it('emit the Approval event', async () => { 174 | const log = result.logs[0] 175 | log.event.should.eq('Approval') 176 | 177 | const event = log.args 178 | event._owner.toString().should.equal(deployer, 'owner is correct') 179 | event._spender.should.equal(exchange, 'spender is correct') 180 | event._value.toString().should.equal(amount.toString(), 'value is correct') 181 | }) 182 | }) 183 | 184 | describe('failure', () => { 185 | it('rejects invalid spenders', async () => { 186 | await token.approve(0x0, amount, { from: deployer }).should.be.rejected 187 | }) 188 | }) 189 | }) 190 | 191 | describe('delegated token transfers', () => { 192 | let result 193 | let amount 194 | 195 | beforeEach(async () => { 196 | amount = tokens(100) 197 | await token.approve(exchange, amount, { from: deployer }) 198 | }) 199 | describe('success', () => { 200 | beforeEach(async () => { 201 | result = await token.transferFrom(deployer, receiver, amount, { from: exchange }) 202 | }) 203 | 204 | it('transfers token balances', async () => { 205 | let balanceOf 206 | balanceOf = await token.balanceOf(deployer) 207 | balanceOf.toString().should.equal(tokens(999900).toString()) 208 | balanceOf = await token.balanceOf(receiver) 209 | balanceOf.toString().should.equal(tokens(100).toString()) 210 | }) 211 | it('resets the allowance', async () => { 212 | const allowance = await token.allowance(deployer, exchange) 213 | allowance.toString().should.equal('0') 214 | }) 215 | it('emit the Transfer event', async () => { 216 | const log = result.logs[0] 217 | log.event.should.eq('Transfer') 218 | 219 | const event = log.args 220 | event.from.toString().should.equal(deployer, 'from is correct') 221 | event.to.toString().should.equal(receiver, 'to is correct') 222 | event.value.toString().should.equal(amount.toString(), 'value is correct') 223 | }) 224 | }) 225 | 226 | describe('failure', () => { 227 | it('rejects insufficient balances', async () => { 228 | let invalidAmount 229 | invalidAmount = tokens(100000000) // 100 million - greater than total supply 230 | await token.transfer(receiver, invalidAmount, { from: deployer }).should.be.rejectedWith(EVM_REVERT) 231 | 232 | // Attempt transfer tokens, when you have none 233 | invalidAmount = tokens(10) 234 | await token.transfer(deployer, invalidAmount, { from: receiver }).should.be.rejectedWith(EVM_REVERT) 235 | }) 236 | it('rejects invalid recipients', async () => { 237 | await token.transfer(0x0, amount, { from: deployer }).should.be.rejected 238 | }) 239 | }) 240 | }) 241 | 242 | }) 243 | ``` -------------------------------------------------------------------------------- /src/components/PriceChart.config.js: -------------------------------------------------------------------------------- 1 | export const chartOptions = { 2 | chart: { 3 | animations: { enabled: false }, 4 | toolbar: { show: true }, 5 | type: 'candlestick', 6 | width: '100px', 7 | }, 8 | tooltip: { 9 | /* 控制鼠标移入时显示的内容 */ 10 | enabled: true, 11 | theme: 'dark', 12 | followCursor: false, 13 | style: { 14 | fontSize: '12px', 15 | fontFamily: undefined, 16 | }, 17 | x: { 18 | show: true, 19 | format: 'MM月dd日', 20 | formatter: undefined, 21 | }, 22 | y: { 23 | show: false, 24 | title: 'price', 25 | }, 26 | marker: { 27 | show: false, 28 | }, 29 | items: { 30 | display: 'flex', 31 | }, 32 | fixed: { 33 | /* 一个方框显示数据 */ 34 | enabled: false, 35 | position: 'bottomLeft', 36 | offsetX: 10, 37 | offsetY: 10, 38 | }, 39 | }, 40 | xaxis: { 41 | type: 'datetime', 42 | labels: { 43 | show: true, 44 | style: { 45 | colors: '#FFF', 46 | fontSize: '8px', 47 | cssClass: 'apexcharts-xaxis-label', 48 | }, 49 | }, 50 | }, 51 | yaxis: { 52 | opposite: true, 53 | logarithmic: true, 54 | forceNiceScale: false, 55 | tickAmount: 10, 56 | position: 'top', 57 | crosshairs: { show: false }, 58 | labels: { 59 | show: true, 60 | minWidth: 0, 61 | maxWidth: 160, 62 | style: { 63 | colors: '#FFF', 64 | fontSize: '8px', 65 | cssClass: 'apexcharts-yaxis-label', 66 | }, 67 | offsetX: 0, 68 | offsetY: 0, 69 | rotate: 0, 70 | }, 71 | }, 72 | } 73 | 74 | export const dummyData = [ 75 | { 76 | data: [ 77 | { 78 | x: new Date(1538778600000), 79 | y: [6629.81, 6650.5, 6623.04, 6633.33], 80 | }, 81 | { 82 | x: new Date(1538780400000), 83 | y: [6632.01, 6643.59, 6620, 6630.11], 84 | }, 85 | { 86 | x: new Date(1538782200000), 87 | y: [6630.71, 6648.95, 6623.34, 6635.65], 88 | }, 89 | { 90 | x: new Date(1538784000000), 91 | y: [6635.65, 6651, 6629.67, 6638.24], 92 | }, 93 | { 94 | x: new Date(1538785800000), 95 | y: [6638.24, 6640, 6620, 6624.47], 96 | }, 97 | { 98 | x: new Date(1538787600000), 99 | y: [6624.53, 6636.03, 6621.68, 6624.31], 100 | }, 101 | { 102 | x: new Date(1538789400000), 103 | y: [6624.61, 6632.2, 6617, 6626.02], 104 | }, 105 | { 106 | x: new Date(1538791200000), 107 | y: [6627, 6627.62, 6584.22, 6603.02], 108 | }, 109 | { 110 | x: new Date(1538793000000), 111 | y: [6605, 6608.03, 6598.95, 6604.01], 112 | }, 113 | { 114 | x: new Date(1538794800000), 115 | y: [6604.5, 6614.4, 6602.26, 6608.02], 116 | }, 117 | { 118 | x: new Date(1538796600000), 119 | y: [6608.02, 6610.68, 6601.99, 6608.91], 120 | }, 121 | { 122 | x: new Date(1538798400000), 123 | y: [6608.91, 6618.99, 6608.01, 6612], 124 | }, 125 | { 126 | x: new Date(1538800200000), 127 | y: [6612, 6615.13, 6605.09, 6612], 128 | }, 129 | { 130 | x: new Date(1538802000000), 131 | y: [6612, 6624.12, 6608.43, 6622.95], 132 | }, 133 | { 134 | x: new Date(1538803800000), 135 | y: [6623.91, 6623.91, 6615, 6615.67], 136 | }, 137 | { 138 | x: new Date(1538805600000), 139 | y: [6618.69, 6618.74, 6610, 6610.4], 140 | }, 141 | { 142 | x: new Date(1538807400000), 143 | y: [6611, 6622.78, 6610.4, 6614.9], 144 | }, 145 | { 146 | x: new Date(1538809200000), 147 | y: [6614.9, 6626.2, 6613.33, 6623.45], 148 | }, 149 | { 150 | x: new Date(1538811000000), 151 | y: [6623.48, 6627, 6618.38, 6620.35], 152 | }, 153 | { 154 | x: new Date(1538812800000), 155 | y: [6619.43, 6620.35, 6610.05, 6615.53], 156 | }, 157 | { 158 | x: new Date(1538814600000), 159 | y: [6615.53, 6617.93, 6610, 6615.19], 160 | }, 161 | { 162 | x: new Date(1538816400000), 163 | y: [6615.19, 6621.6, 6608.2, 6620], 164 | }, 165 | { 166 | x: new Date(1538818200000), 167 | y: [6619.54, 6625.17, 6614.15, 6620], 168 | }, 169 | { 170 | x: new Date(1538820000000), 171 | y: [6620.33, 6634.15, 6617.24, 6624.61], 172 | }, 173 | { 174 | x: new Date(1538821800000), 175 | y: [6625.95, 6626, 6611.66, 6617.58], 176 | }, 177 | { 178 | x: new Date(1538823600000), 179 | y: [6619, 6625.97, 6595.27, 6598.86], 180 | }, 181 | { 182 | x: new Date(1538825400000), 183 | y: [6598.86, 6598.88, 6570, 6587.16], 184 | }, 185 | { 186 | x: new Date(1538827200000), 187 | y: [6588.86, 6600, 6580, 6593.4], 188 | }, 189 | { 190 | x: new Date(1538829000000), 191 | y: [6593.99, 6598.89, 6585, 6587.81], 192 | }, 193 | { 194 | x: new Date(1538830800000), 195 | y: [6587.81, 6592.73, 6567.14, 6578], 196 | }, 197 | { 198 | x: new Date(1538832600000), 199 | y: [6578.35, 6581.72, 6567.39, 6579], 200 | }, 201 | { 202 | x: new Date(1538834400000), 203 | y: [6579.38, 6580.92, 6566.77, 6575.96], 204 | }, 205 | { 206 | x: new Date(1538836200000), 207 | y: [6575.96, 6589, 6571.77, 6588.92], 208 | }, 209 | { 210 | x: new Date(1538838000000), 211 | y: [6588.92, 6594, 6577.55, 6589.22], 212 | }, 213 | { 214 | x: new Date(1538839800000), 215 | y: [6589.3, 6598.89, 6589.1, 6596.08], 216 | }, 217 | { 218 | x: new Date(1538841600000), 219 | y: [6597.5, 6600, 6588.39, 6596.25], 220 | }, 221 | { 222 | x: new Date(1538843400000), 223 | y: [6598.03, 6600, 6588.73, 6595.97], 224 | }, 225 | { 226 | x: new Date(1538845200000), 227 | y: [6595.97, 6602.01, 6588.17, 6602], 228 | }, 229 | { 230 | x: new Date(1538847000000), 231 | y: [6602, 6607, 6596.51, 6599.95], 232 | }, 233 | { 234 | x: new Date(1538848800000), 235 | y: [6600.63, 6601.21, 6590.39, 6591.02], 236 | }, 237 | { 238 | x: new Date(1538850600000), 239 | y: [6591.02, 6603.08, 6591, 6591], 240 | }, 241 | { 242 | x: new Date(1538852400000), 243 | y: [6591, 6601.32, 6585, 6592], 244 | }, 245 | { 246 | x: new Date(1538854200000), 247 | y: [6593.13, 6596.01, 6590, 6593.34], 248 | }, 249 | { 250 | x: new Date(1538856000000), 251 | y: [6593.34, 6604.76, 6582.63, 6593.86], 252 | }, 253 | { 254 | x: new Date(1538857800000), 255 | y: [6593.86, 6604.28, 6586.57, 6600.01], 256 | }, 257 | { 258 | x: new Date(1538859600000), 259 | y: [6601.81, 6603.21, 6592.78, 6596.25], 260 | }, 261 | { 262 | x: new Date(1538861400000), 263 | y: [6596.25, 6604.2, 6590, 6602.99], 264 | }, 265 | { 266 | x: new Date(1538863200000), 267 | y: [6602.99, 6606, 6584.99, 6587.81], 268 | }, 269 | { 270 | x: new Date(1538865000000), 271 | y: [6587.81, 6595, 6583.27, 6591.96], 272 | }, 273 | { 274 | x: new Date(1538866800000), 275 | y: [6591.97, 6596.07, 6585, 6588.39], 276 | }, 277 | { 278 | x: new Date(1538868600000), 279 | y: [6587.6, 6598.21, 6587.6, 6594.27], 280 | }, 281 | { 282 | x: new Date(1538870400000), 283 | y: [6596.44, 6601, 6590, 6596.55], 284 | }, 285 | { 286 | x: new Date(1538872200000), 287 | y: [6598.91, 6605, 6596.61, 6600.02], 288 | }, 289 | { 290 | x: new Date(1538874000000), 291 | y: [6600.55, 6605, 6589.14, 6593.01], 292 | }, 293 | { 294 | x: new Date(1538875800000), 295 | y: [6593.15, 6605, 6592, 6603.06], 296 | }, 297 | { 298 | x: new Date(1538877600000), 299 | y: [6603.07, 6604.5, 6599.09, 6603.89], 300 | }, 301 | { 302 | x: new Date(1538879400000), 303 | y: [6604.44, 6604.44, 6600, 6603.5], 304 | }, 305 | { 306 | x: new Date(1538881200000), 307 | y: [6603.5, 6603.99, 6597.5, 6603.86], 308 | }, 309 | { 310 | x: new Date(1538883000000), 311 | y: [6603.85, 6605, 6600, 6604.07], 312 | }, 313 | { 314 | x: new Date(1538884800000), 315 | y: [6604.98, 6606, 6604.07, 6606], 316 | }, 317 | ], 318 | }, 319 | ] 320 | -------------------------------------------------------------------------------- /src/components/Balance.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import Spinner from './Spinner' 4 | import { Tabs, Tab } from 'react-bootstrap' 5 | 6 | import { 7 | loadBalances, 8 | depositEther, 9 | withdrawEther, 10 | depositToken, 11 | withdrawToken, 12 | } from '../store/interactions' 13 | 14 | import { 15 | web3Selector, 16 | exchangeSelector, 17 | tokenSelector, 18 | accountSelector, 19 | balancesLoadingSelector, 20 | etherBalanceSelector, 21 | tokenBalanceSelector, 22 | exchangeEtherBalanceSelector, 23 | exchangeTokenBalanceSelector, 24 | etherDepositAmountSelector, 25 | etherWithdrawAmountSelector, 26 | tokenDepositAmountSelector, 27 | tokenWithdrawAmountSelector, 28 | } from '../store/selectors' 29 | 30 | import { 31 | etherDepositAmountChanged, 32 | etherWithdrawAmountChanged, 33 | tokenDepositAmountChanged, 34 | tokenWithdrawAmountChanged, 35 | } from '../store/action' 36 | 37 | const showForm = (props) => { 38 | const { 39 | etherBalance, 40 | tokenBalance, 41 | exchangeEtherBalance, 42 | exchangeTokenBalance, 43 | dispatch, 44 | etherDepositAmount, 45 | etherWithdrawAmount, 46 | tokenDepositAmount, 47 | tokenWithdrawAmount, 48 | exchange, 49 | token, 50 | account, 51 | web3, 52 | } = props 53 | 54 | loadBalances(dispatch, web3, exchange, token, account) 55 | 56 | return ( 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
TokenWalletExchange
ETH{etherBalance}{exchangeEtherBalance}
75 |
{ 78 | event.preventDefault() 79 | depositEther(dispatch, exchange, web3, etherDepositAmount, account) 80 | }} 81 | > 82 |
83 | 87 | dispatch(etherDepositAmountChanged(e.target.value)) 88 | } 89 | className="form-control form-control-sm bg-dark text-white" 90 | required 91 | /> 92 |
93 |
94 | 97 |
98 |
99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
DAPP{tokenBalance}{exchangeTokenBalance}
109 |
{ 112 | event.preventDefault() 113 | depositToken( 114 | dispatch, 115 | exchange, 116 | web3, 117 | token, 118 | tokenDepositAmount, 119 | account 120 | ) 121 | }} 122 | > 123 |
124 | 128 | dispatch(tokenDepositAmountChanged(e.target.value)) 129 | } 130 | className="form-control form-control-sm bg-dark text-white" 131 | required 132 | /> 133 |
134 |
135 | 138 |
139 |
140 |
141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 |
TokenWalletExchange
ETH{etherBalance}{exchangeEtherBalance}
158 |
{ 161 | event.preventDefault() 162 | withdrawEther( 163 | dispatch, 164 | exchange, 165 | web3, 166 | etherWithdrawAmount, 167 | account 168 | ) 169 | }} 170 | > 171 |
172 | 176 | dispatch(etherWithdrawAmountChanged(e.target.value)) 177 | } 178 | className="form-control form-control-sm bg-dark text-white" 179 | required 180 | /> 181 |
182 |
183 | 186 |
187 |
188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 |
DAPP{tokenBalance}{exchangeTokenBalance}
198 |
{ 201 | event.preventDefault() 202 | withdrawToken( 203 | dispatch, 204 | exchange, 205 | web3, 206 | token, 207 | tokenWithdrawAmount, 208 | account 209 | ) 210 | }} 211 | > 212 |
213 | 217 | dispatch(tokenWithdrawAmountChanged(e.target.value)) 218 | } 219 | className="form-control form-control-sm bg-dark text-white" 220 | required 221 | /> 222 |
223 |
224 | 227 |
228 |
229 |
230 |
231 | ) 232 | } 233 | 234 | class Balance extends Component { 235 | componentWillMount() { 236 | this.loadBlockchainData(this.props) 237 | } 238 | 239 | async loadBlockchainData(props) { 240 | const { dispatch, web3, exchange, token, account } = props 241 | await loadBalances(dispatch, web3, exchange, token, account) 242 | } 243 | 244 | render() { 245 | return ( 246 |
247 |
Balance 我的余额
248 |
249 | {this.props.showForm ? showForm(this.props) : } 250 |
251 |
252 | ) 253 | } 254 | } 255 | 256 | function mapStateToProps(state) { 257 | const balancesLoading = balancesLoadingSelector(state) 258 | return { 259 | web3: web3Selector(state), 260 | exchange: exchangeSelector(state), 261 | token: tokenSelector(state), 262 | account: accountSelector(state), 263 | etherBalance: etherBalanceSelector(state), 264 | tokenBalance: tokenBalanceSelector(state), 265 | exchangeEtherBalance: exchangeEtherBalanceSelector(state), 266 | exchangeTokenBalance: exchangeTokenBalanceSelector(state), 267 | balancesLoading, 268 | showForm: !balancesLoading, 269 | etherDepositAmount: etherDepositAmountSelector(state), 270 | etherWithdrawAmount: etherWithdrawAmountSelector(state), 271 | tokenDepositAmount: tokenDepositAmountSelector(state), 272 | tokenWithdrawAmount: tokenWithdrawAmountSelector(state), 273 | } 274 | } 275 | 276 | // export default App 277 | export default connect(mapStateToProps)(Balance) 278 | -------------------------------------------------------------------------------- /src/store/interactions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取数据的操作是在这里面 3 | * 在这里面获取到数据后 4 | * 通过 dispatch 调用 action 5 | * 然后就可以将数据存储进 redux 了 6 | * 7 | * interactions: 交互. 这个交互, 指的应该是 action 和 reducers 之间的 8 | * 这里的内容就是加载我们所需要的数据, 从名称也可以看出——load 9 | */ 10 | 11 | import Web3 from 'web3' 12 | import Token from '../abis/Token.json' 13 | import Exchange from '../abis/Exchange.json' 14 | import { 15 | web3Loaded, 16 | web3AccountLoaded, 17 | tokenLoaded, 18 | exchangeLoaded, 19 | cancelledOrdersLoaded, 20 | filledOrdersLoaded, 21 | allOrdersLoaded, 22 | orderCancelling, 23 | orderCancelled, 24 | // 25 | orderFilling, 26 | orderFilled, 27 | // 28 | etherBalanceLoaded, 29 | tokenBalanceLoaded, 30 | exchangeEtherBalanceLoaded, 31 | exchangeTokenBalanceLoaded, 32 | balancesLoaded, 33 | balancesLoading, 34 | buyOrderMaking, 35 | sellOrderMaking, 36 | orderMade, 37 | } from './action' 38 | import { ETHER_ADDRESS } from '../helpers' 39 | 40 | export const loadWeb3 = (dispatch) => { 41 | const web3 = new Web3(Web3.givenProvider || 'ws://localhost:7545') 42 | dispatch(web3Loaded(web3)) 43 | return web3 44 | } 45 | 46 | export const loadAccount = async (web3, dispatch) => { 47 | const accounts = await web3.eth.getAccounts() 48 | const account = accounts[0] 49 | dispatch(web3AccountLoaded(account)) 50 | return account 51 | } 52 | 53 | export const loadToken = async (web3, networkId, dispatch) => { 54 | try { 55 | // 这里没有一步写成,是为了方便理解 56 | const networks = Token.networks 57 | const networkData = networks[networkId] 58 | const networkAddress = networkData.address 59 | const token = new web3.eth.Contract(Token.abi, networkAddress) 60 | dispatch(tokenLoaded(token)) 61 | return token 62 | } catch (error) { 63 | console.error( 64 | 'Contract not deployed to the current network. Please select another network with Metamask' 65 | ) 66 | return null 67 | } 68 | } 69 | 70 | export const loadExchange = async (web3, networkId, dispatch) => { 71 | try { 72 | // exchange 和 token 一致 73 | const exchange = new web3.eth.Contract( 74 | Exchange.abi, 75 | Exchange.networks[networkId].address 76 | ) 77 | dispatch(exchangeLoaded(exchange)) 78 | return exchange 79 | } catch (error) { 80 | console.error( 81 | 'Contract not deployed to the current network. Please select another network with Metamask' 82 | ) 83 | return null 84 | } 85 | } 86 | 87 | export const loadAllOrders = async (exchange, dispatch) => { 88 | // Fetch cancelled orders with the 'Cancel' event stream 获取历史取消的 exchange 89 | const cancelStream = await exchange.getPastEvents('Cancel', { 90 | fromBlock: 0, 91 | toBlock: 'latest', 92 | }) 93 | // Format cancelled orders 只获取我们想要的信息 94 | const cancelledOrders = cancelStream.map((event) => event.returnValues) 95 | // Add cancelled orders to the redux store 96 | dispatch(cancelledOrdersLoaded(cancelledOrders)) 97 | 98 | // Fetch filled orders with the 'Trade' event stream, 获取历史完成的订单 99 | const tradeStream = await exchange.getPastEvents('Trade', { 100 | fromBlock: 0, 101 | toBlock: 'latest', 102 | }) 103 | // Format filled orders 只获取我们想要的信息 104 | const filledOrders = tradeStream.map((event) => event.returnValues) 105 | // Add cancelled orders to the redux store 106 | dispatch(filledOrdersLoaded(filledOrders)) 107 | 108 | // Fetch all orders with the 'Order' event stream 109 | const orderStream = await exchange.getPastEvents('Order', { 110 | fromBlock: 0, 111 | toBlock: 'latest', 112 | }) 113 | const allOrders = orderStream.map((event) => event.returnValues) 114 | dispatch(allOrdersLoaded(allOrders)) 115 | } 116 | 117 | // 取消一个订单 118 | export const cancelOrder = (dispatch, exchange, order, account) => { 119 | exchange.methods 120 | .cancelOrder(order.id) 121 | .send({ from: account }) 122 | .on('transactionHash', (hash) => { 123 | dispatch(orderCancelling()) 124 | }) 125 | .on('error', (error) => { 126 | console.error(error) 127 | window.alert('There was an error ! 取消订单失败') 128 | }) 129 | } 130 | 131 | // 完成一个订单 132 | export const fillOrder = (dispatch, exchange, order, account) => { 133 | // 调用智能合约中的方法, 实现完成订单 134 | exchange.methods 135 | .fillOrder(order.id) 136 | .send({ from: account }) 137 | .on('transactionHash', (hash) => { 138 | // 将“订单正在完成”这个信息存入 store 中 139 | dispatch(orderFilling()) 140 | }) 141 | .on('error', (error) => { 142 | console.error(error) 143 | window.alert('fill order wrong ') 144 | }) 145 | } 146 | 147 | // 订阅事件, 当事件发生变化时, 我们会受到该消息, 前面的 load 148 | export const subscribeToEvents = async (exchange, dispatch) => { 149 | exchange.events.Cancel({}, (error, event) => { 150 | // 对于返回的事件对象中, 我们只需要 returnValues 这部分内容 151 | dispatch(orderCancelled(event.returnValues)) 152 | }) 153 | 154 | // (视频中缺失这部分内容, 但是有实现, 这个是视频中看到的一部分代码, 根据这一部分代码可以猜测出原本的实现逻辑) 155 | exchange.events.Trade({}, (error, event) => { 156 | dispatch(orderFilled(event.returnValues)) 157 | }) 158 | 159 | exchange.events.Deposit({}, (error, event) => { 160 | dispatch(balancesLoaded()) 161 | }) 162 | 163 | exchange.events.Withdraw({}, (error, event) => { 164 | dispatch(balancesLoaded()) 165 | }) 166 | 167 | exchange.events.Order({}, (error, event) => { 168 | dispatch(orderMade(event.returnValues)) 169 | }) 170 | } 171 | 172 | export const loadBalances = async ( 173 | dispatch, 174 | web3, 175 | exchange, 176 | token, 177 | account 178 | ) => { 179 | // Ether balance in wallet 180 | const etherBalance = await web3.eth.getBalance(account) 181 | dispatch(etherBalanceLoaded(etherBalance)) 182 | 183 | // Token balance in wallet 184 | const tokenBalance = await token.methods.balanceOf(account).call() 185 | dispatch(tokenBalanceLoaded(tokenBalance)) 186 | 187 | // Ether balance in exchange 188 | const exchangeEtherBalance = await exchange.methods 189 | .balanceOf(ETHER_ADDRESS, account) 190 | .call() 191 | dispatch(exchangeEtherBalanceLoaded(exchangeEtherBalance)) 192 | 193 | // Token balance in exchange 194 | const exchangeTokenBalance = await exchange.methods 195 | .balanceOf(token.options.address, account) 196 | .call() 197 | dispatch(exchangeTokenBalanceLoaded(exchangeTokenBalance)) 198 | 199 | // Trigger all balances loaded 200 | dispatch(balancesLoaded()) 201 | } 202 | 203 | export const depositEther = (dispatch, exchange, web3, amount, account) => { 204 | exchange.methods 205 | .depositEther() 206 | .send({ 207 | from: account, 208 | value: web3.utils.toWei(amount, 'ether'), 209 | }) 210 | .on('transactionHash', (hash) => { 211 | dispatch(balancesLoading()) 212 | }) 213 | .on('error', (error) => { 214 | console.error(error) 215 | window.alert('DepositEther error!') 216 | }) 217 | } 218 | 219 | export const withdrawEther = (dispatch, exchange, web3, amount, account) => { 220 | exchange.methods 221 | .withdrawEther(web3.utils.toWei(amount, 'ether')) 222 | .send({ from: account }) 223 | .on('transactionHash', (hash) => { 224 | dispatch(balancesLoading()) 225 | }) 226 | .on('error', (error) => { 227 | console.error(error) 228 | window.alert('WithdrawEther error!') 229 | }) 230 | } 231 | 232 | export const depositToken = ( 233 | dispatch, 234 | exchange, 235 | web3, 236 | token, 237 | amount, 238 | account 239 | ) => { 240 | amount = web3.utils.toWei(amount, 'ether') 241 | 242 | // depositToken 需要两步 243 | token.methods 244 | .approve(exchange.options.address, amount) 245 | .send({ from: account }) 246 | .on('transactionHash', (hash) => { 247 | exchange.methods 248 | .depositToken(token.options.address, amount) 249 | .send({ from: account }) 250 | .on('transactionHash', (hash) => { 251 | dispatch(balancesLoading()) 252 | }) 253 | .on('error', (error) => { 254 | console.error(error) 255 | window.alert('depositToken error') 256 | }) 257 | }) 258 | } 259 | 260 | export const withdrawToken = ( 261 | dispatch, 262 | exchange, 263 | web3, 264 | token, 265 | amount, 266 | account 267 | ) => { 268 | amount = web3.utils.toWei(amount, 'ether') 269 | exchange.methods 270 | .withdrawToken(token.options.address, amount) 271 | .send({ from: account }) 272 | .on('transactionHash', (hash) => { 273 | dispatch(balancesLoading()) 274 | }) 275 | .on('error', (error) => { 276 | console.error(error) 277 | window.alert('withdrawToken error') 278 | }) 279 | } 280 | 281 | export const makeBuyOrder = ( 282 | dispatch, 283 | exchange, 284 | token, 285 | web3, 286 | order, 287 | account 288 | ) => { 289 | const tokenGet = token.options.address 290 | const amountGet = web3.utils.toWei(order.amount, 'ether') 291 | const tokenGive = ETHER_ADDRESS 292 | const amountGive = web3.utils.toWei( 293 | (order.amount * order.price).toString(), 294 | 'ether' 295 | ) 296 | 297 | exchange.methods 298 | .makeOrder(tokenGet, amountGet, tokenGive, amountGive) 299 | .send({ from: account }) 300 | .on('transactionHash', (hash) => { 301 | dispatch(buyOrderMaking()) 302 | }) 303 | .on('error', (error) => { 304 | console.error(error) 305 | window.alert('Make buy order error') 306 | }) 307 | } 308 | 309 | export const makeSellOrder = ( 310 | dispatch, 311 | exchange, 312 | token, 313 | web3, 314 | order, 315 | account 316 | ) => { 317 | const tokenGet = ETHER_ADDRESS 318 | const amountGet = web3.utils.toWei( 319 | (order.amount * order.price).toString(), 320 | 'ether' 321 | ) 322 | const tokenGive = token.options.address 323 | const amountGive = web3.utils.toWei(order.amount, 'ether') 324 | 325 | exchange.methods 326 | .makeOrder(tokenGet, amountGet, tokenGive, amountGive) 327 | .send({ from: account }) 328 | .on('transactionHash', (hash) => { 329 | dispatch(sellOrderMaking()) 330 | }) 331 | .on('error', (error) => { 332 | console.error(error) 333 | window.alert('Make sell order error') 334 | }) 335 | } 336 | -------------------------------------------------------------------------------- /错误.md: -------------------------------------------------------------------------------- 1 | 2 | * Transaction submission failed with error -32000: 'VM Exception while processing transaction: revert' 3 | 交易失败, 原因有特别多, 只要不是成功, 基本都会提示这个错误 4 | 5 | * 地址错误 6 | AssertionError: expected promise to be rejected with an error including 'VM Exception while processing transaction: revert' but got 'invalid address (argument="address", value=0, code=INVALID_ARGUMENT, version=address/5.0.5) (argument="_token", value=0, code=INVALID_ARGUMENT, version=abi/5.0.7)' 7 | 都是因为地址写错了, 地址必须使用字符串, 且位数不能错误, 该有几位就要有几位. 8 | 9 | * "before each" hook for "executes the trade & charges fees" 10 | beforeEach 中的代码有问题, 执行出错 11 | 12 | * TypeError: Cannot read property 'args' of undefined 13 | 原因可能是: 没有部署合约到 ganache 14 | 15 | * sol 文件中指定版本时可能会报错 16 | 可以修改 truffle-config.js 文件中的版本号 compilers-solc-version 17 | 18 | * 无法使用 module 语法 19 | 使用 export 导出时, 需要安装 babel 相关包, 并且要配置 .babelrc 文件 20 | 21 | * Error: Internal JSON-RPC error. 22 | 应该是因为我重新部署了一下合约 `truffle migrate --reset` 然后 metamask 中的账户没有更新, 他还有一些缓存. 23 | 解决方法就是将账户 reset 一下就可以了 24 | 25 | * NaN for the `children` attribute. If this is expected, cast the value to a string. 26 | 这是因为代码中的一些字段, 没有对它进行校验, 导致金额是一个 NaN. 27 | 根本原因是, 重新部署了一下后, metamask 中的账户金额有问题, 所以才会出现金额是 NaN 28 | 29 | * fill order 时, 如果 metamask 提示 We were not able to estimate gas. There might be an error in the contract and this transaction may fail. 30 | 试着在 balance 中的 deposit 点 exchange 31 | 32 | * npm i truffle-hdwallet-provider-privkey@1.0.3 33 | 安装这个包时一直报错, 结果发现是版本错误(虽然作者的 gist 仓库写的是 1.0.3) 34 | 但实际上应该使用 0.0.3 才可以: npm i truffle-hdwallet-provider-privkey@0.0.3 35 | 没有用,下载成功后,这个包用不了,它的package.json 中连依赖都没给。 36 | 解决方法时,使用 `const HDWalletProvider = require('truffle-hdwallet-provider')` 37 | 删掉 `truffle-hdwallet-provider-privkey` 38 | 39 | * Expected parameter 'from' not passed to function. 40 | `truffle migrate --network kovan` 是错误, 原因是 privateKeys 错误, 检查发现 41 | .env 中把账号作为了私钥导入. 42 | 43 | * There was a timeout while attempting to connect to the network at undefined. Check to see that your provider is valid. 44 | 可能是因为 .env 中的变量没有加双引号 45 | 46 | * Migrations" could not deploy due to insufficient funds 47 | 一言以蔽之:没钱。因为 kovan 测试的人太多了, 发放的 ether 也少. 48 | 可以换一个测试网络: ropsten 49 | 换了测试网络好, 需要更改 truffle-config.js 中的测试网络名称、provider、network_id 50 | 然后部署时也记得要改成 `truffle migrate --network 配置中的测试网络名称` 51 | 52 | * Invalid JSON RPC response: "" 53 | 不知道是啥, 虽然报错了,但是最终还是可以部署到线上。 54 | 我也不知道改了多少。 55 | 首先我把 `2_deploy_contracts.js` 中的 `feePercent` 从 10 改成了 1。 56 | 然后我设置了 `truffle-config.js` 中的 `networks.ropsten.networkCheckTimeout = 60000`。 57 | 而且 `HDWalletProvider` 的第一个参数, 改成了只有一个账户。 58 | 然后最终再部署一次 `truffle migrate --network ropsten` 终于可以了!!! 59 | (对了,gas: 5000000, gasPrice: 25000000000, 这两个值最好不要改,可能会出现 timeout while attempting to connect to the network 错误) 60 | 下面给出最终成功部署的结果输出。 61 | 62 | ```bash 63 | $ truffle migrate --network ropsten 64 | 65 | Compiling your contracts... 66 | =========================== 67 | > Compiling .\src\contracts\Exchange.sol 68 | > Compiling .\src\contracts\Migrations.sol 69 | > Compiling .\src\contracts\Token.sol 70 | > Compiling openzeppelin-solidity\contracts\math\SafeMath.sol 71 | > Artifacts written to D:\truffle\src\abis 72 | > Compiled successfully using: 73 | - solc: 0.5.0+commit.1d4f565a.Emscripten.clang 74 | WARNING: Ganache forking only supports EIP-1193-compliant providers. Legacy support for send is currently enabled, but will be removed in a future version _without_ a breaking change. To remove this warning, switch to an EIP-1193 provider. This error is probably caused by an old version of Web3's HttpProvider (or ganache < v7) 75 | 76 | 77 | Migrations dry-run (simulation) 78 | =============================== 79 | > Network name: 'ropsten-fork' 80 | > Network id: 3 81 | > Block gas limit: 30000000 (0x1c9c380) 82 | 83 | 84 | 2_deploy_contracts.js 85 | ===================== 86 | debugger [ '0xa18C047e08730044E79f696EBf68386b67BD81D2' ] 87 | 88 | Deploying 'Token' 89 | ----------------- 90 | > block number: 12575653 91 | > block timestamp: 1657600351 92 | > account: 0xa18C047e08730044E79f696EBf68386b67BD81D2 93 | > balance: 4.9524189 94 | > gas used: 824794 (0xc95da) 95 | > gas price: 25 gwei 96 | > value sent: 0 ETH 97 | > total cost: 0.02061985 ETH 98 | 99 | 100 | Deploying 'Exchange' 101 | -------------------- 102 | > block number: 12575654 103 | > block timestamp: 1657600354 104 | > account: 0xa18C047e08730044E79f696EBf68386b67BD81D2 105 | > balance: 4.902499975 106 | > gas used: 1996757 (0x1e77d5) 107 | > gas price: 25 gwei 108 | > value sent: 0 ETH 109 | > total cost: 0.049918925 ETH 110 | 111 | ------------------------------------- 112 | > Total cost: 0.070538775 ETH 113 | 114 | Summary 115 | ======= 116 | > Total deployments: 2 117 | > Final cost: 0.070538775 ETH 118 | 119 | 120 | 121 | 122 | Starting migrations... 123 | ====================== 124 | > Network name: 'ropsten' 125 | > Network id: 3 126 | > Block gas limit: 30000000 (0x1c9c380) 127 | 128 | 129 | 2_deploy_contracts.js 130 | ===================== 131 | 132 | Deploying 'Token' 133 | ----------------- 134 | > transaction hash: 0x7d54db1cb784cfad35e1cedfa563d4cb1aba90259cc0885a3ec1fb5a007aef54 135 | > Blocks: 1 Seconds: 21 136 | > contract address: 0xD5191d2829EF07E29Fd20B1C14398826D39FCf49 137 | > block number: 12575662 138 | > block timestamp: 1657600392 139 | > account: 0xa18C047e08730044E79f696EBf68386b67BD81D2 140 | > balance: 4.9524189 141 | > gas used: 824794 (0xc95da) 142 | > gas price: 25 gwei 143 | > value sent: 0 ETH 144 | > total cost: 0.02061985 ETH 145 | 146 | 147 | Deploying 'Exchange' 148 | -------------------- 149 | > transaction hash: 0x232cb9ffa86a2a9c4cd73d85a8e1d1fa5aee7a193af8fdb02b6c7298d724582e 150 | Error: Invalid JSON RPC response: ""3 151 | at Object.InvalidResponse (D:\truffle\node_modules\truffle-hdwallet-provider\dist\webpack:\truffle-hdwallet-provider\Users\tyler\projects\truffle\node_modules\web3\node_modules\web3-core-helpers\src\errors.js:42:1) 152 | at e.i.onreadystatechange (D:\truffle\node_modules\truffle-hdwallet-provider\dist\webpack:\truffle-hdwallet-provider\Users\tyler\projects\truffle\node_modules\web3\node_modules\web3-providers-http\src\index.js:92:1) 153 | at e.t.dispatchEvent (D:\truffle\node_modules\truffle-hdwallet-provider\dist\webpack:\truffle-hdwallet-provider\Users\tyler\projects\truffle\node_modules\xhr2-cookies\dist\xml-http-request-event-target.js:27:61) 154 | at e._setReadyState (D:\truffle\node_modules\truffle-hdwallet-provider\dist\webpack:\truffle-hdwallet-provider\Users\tyler\projects\truffle\node_modules\xhr2-cookies\dist\xml-http-request.js:208:1) 155 | at e._onHttpRequestError (D:\truffle\node_modules\truffle-hdwallet-provider\dist\webpack:\truffle-hdwallet-provider\Users\tyler\projects\truffle\node_modules\xhr2-cookies\dist\xml-http-request.js:349:1) 156 | at ClientRequest. (D:\truffle\node_modules\truffle-hdwallet-provider\dist\webpack:\truffle-hdwallet-provider\Users\tyler\projects\truffle\node_modules\xhr2-cookies\dist\xml-http-request.js:252:47) 157 | at ClientRequest.emit (events.js:315:20) 158 | at TLSSocket.socketErrorListener (_http_client.js:469:9) 159 | at TLSSocket.emit (events.js:315:20) 160 | at emitErrorNT (internal/streams/destroy.js:106:8) 161 | at emitErrorCloseNT (internal/streams/destroy.js:74:3) 162 | at processTicksAndRejections (internal/process/task_queues.js:80:21) 163 | > Blocks: 1 Seconds: 13 164 | > contract address: 0x64a4A6B0806C3FF09741185D275155d3786432A2 165 | > block number: 12575664 166 | > block timestamp: 1657600428 167 | > account: 0xa18C047e08730044E79f696EBf68386b67BD81D2 168 | > balance: 4.902499975 169 | > gas used: 1996757 (0x1e77d5) 170 | > gas price: 25 gwei 171 | > value sent: 0 ETH 172 | > total cost: 0.049918925 ETH 173 | 174 | Error: Invalid JSON RPC response: "" 175 | at Object.InvalidResponse (D:\truffle\node_modules\truffle-hdwallet-provider\dist\webpack:\truffle-hdwallet-provider\Users\tyler\projects\truffle\node_modules\web3\node_modules\web3-core-helpers\src\errors.js:42:1) 176 | at e.i.onreadystatechange (D:\truffle\node_modules\truffle-hdwallet-provider\dist\webpack:\truffle-hdwallet-provider\Users\tyler\projects\truffle\node_modules\web3\node_modules\web3-providers-http\src\index.js:92:1) 177 | at e.t.dispatchEvent (D:\truffle\node_modules\truffle-hdwallet-provider\dist\webpack:\truffle-hdwallet-provider\Users\tyler\projects\truffle\node_modules\xhr2-cookies\dist\xml-http-request-event-target.js:27:61) 178 | at e._setReadyState (D:\truffle\node_modules\truffle-hdwallet-provider\dist\webpack:\truffle-hdwallet-provider\Users\tyler\projects\truffle\node_modules\xhr2-cookies\dist\xml-http-request.js:208:1) 179 | at e._onHttpRequestError (D:\truffle\node_modules\truffle-hdwallet-provider\dist\webpack:\truffle-hdwallet-provider\Users\tyler\projects\truffle\node_modules\xhr2-cookies\dist\xml-http-request.js:349:1) 180 | at ClientRequest. (D:\truffle\node_modules\truffle-hdwallet-provider\dist\webpack:\truffle-hdwallet-provider\Users\tyler\projects\truffle\node_modules\xhr2-cookies\dist\xml-http-request.js:252:47) 181 | at ClientRequest.emit (events.js:315:20) 182 | at TLSSocket.socketErrorListener (_http_client.js:469:9) 183 | at TLSSocket.emit (events.js:315:20) 184 | at emitErrorNT (internal/streams/destroy.js:106:8) 185 | at emitErrorCloseNT (internal/streams/destroy.js:74:3) 186 | at processTicksAndRejections (internal/process/task_queues.js:80:21) 187 | ✓ Saving migration to chain. 188 | > Saving migration to chain. 189 | > Saving artifacts 190 | ------------------------------------- 191 | > Total cost: 0.070538775 ETH 192 | 193 | Summary 194 | ======= 195 | > Total deployments: 2 196 | > Final cost: 0.070538775 ETH 197 | 198 | 199 | 200 | Linhieng@LEGION-LHE MINGW64 /d/truffle (main) 201 | ``` -------------------------------------------------------------------------------- /src/store/selectors.js: -------------------------------------------------------------------------------- 1 | import { groupBy, reject, get, maxBy, minBy } from 'lodash' 2 | import moment from 'moment' 3 | import { createSelector } from 'reselect' 4 | import { 5 | formatBalance, 6 | ETHER_ADDRESS, 7 | ether, 8 | tokens, 9 | RED, 10 | GREEN, 11 | } from '../helpers' 12 | 13 | // 不直接 state.web3.account 而是用 get 获取, 作用就是防止 web3 为不存在的情况 14 | const account = (state) => get(state, 'web3.account') 15 | // 直接返回 web3.account 16 | export const accountSelector = createSelector(account, (account) => account) 17 | 18 | // 同上, 获取 exchange 19 | const web3 = (state) => get(state, 'web3.connection', false) 20 | export const web3Selector = createSelector(web3, (web3) => web3) 21 | 22 | // 同上, 获取 exchange 23 | const token = (state) => get(state, 'token.contract', false) 24 | export const tokenSelector = createSelector(token, (token) => token) 25 | 26 | // 同上, 获取 exchange 27 | const exchange = (state) => get(state, 'exchange.contract', false) 28 | export const exchangeSelector = createSelector(exchange, (exchange) => exchange) 29 | 30 | // 同上, 获取 token 是否加载完成 31 | const tokenLoaded = (state) => get(state, 'token.loaded', false) 32 | export const tokenLoadedSelector = createSelector( 33 | tokenLoaded, 34 | (tokenLoaded) => tokenLoaded 35 | ) 36 | 37 | // 同上, 获取 exchange 是否加载完成 38 | const exchangeLoaded = (state) => get(state, 'exchange.loaded', false) 39 | export const exchangeLoadedSelector = createSelector( 40 | exchangeLoaded, 41 | (exchangeLoaded) => exchangeLoaded 42 | ) 43 | 44 | // 当 token 和 exchange 都加载好时, 代表 contracts 加载好了 45 | export const contractsLoadedSelector = createSelector( 46 | tokenLoaded, 47 | exchangeLoaded, 48 | (tokenLoaded, exchangeLoaded) => tokenLoaded && exchangeLoaded 49 | ) 50 | 51 | // 用于判断完成的订单是否加载完毕 52 | const filledOrdersLoaded = (state) => 53 | get(state, 'exchange.filledOrders.loaded', false) 54 | export const filledOrdersLoadedSelector = createSelector( 55 | filledOrdersLoaded, 56 | (loaded) => loaded 57 | ) 58 | // 获取完成的订单 59 | const filledOrders = (state) => get(state, 'exchange.filledOrders.data', []) 60 | export const filledOrdersSelector = createSelector(filledOrders, (orders) => { 61 | // Sort orders by date ascending for price comparison 日期早的在前面, 方便获取前一天的价格, 计算价格是升是降 62 | orders = orders.sort((a, b) => a.timestamp - b.timestamp) 63 | // decorate order 为该订单添加一些信息, 比如给出时间、是卖出还是买入、金额多少... 64 | orders = decorateFilledOrders(orders) 65 | // Sort orders by date descending for display 按照时间逆序排队 —— 晚在前 66 | orders = orders.sort((a, b) => b.timestamp - a.timestamp) 67 | return orders 68 | }) 69 | 70 | // cancelled orders 71 | const cancelledOrdersLoaded = (state) => 72 | get(state, 'exchange.cancelledOrders.loaded', false) 73 | export const cancelledOrdersLoadedSelector = createSelector( 74 | cancelledOrdersLoaded, 75 | (cancelledOrdersLoaded) => cancelledOrdersLoaded 76 | ) 77 | const cancelledOrders = (state) => 78 | get(state, 'exchange.cancelledOrders.data', []) 79 | export const cancelledOrdersSelector = createSelector( 80 | cancelledOrders, 81 | (cancelledOrders) => cancelledOrders 82 | ) 83 | 84 | // all orders 85 | const allOrdersLoaded = (state) => 86 | get(state, 'exchange.allOrders.loaded', false) 87 | export const allOrdersLoadedSelector = createSelector( 88 | allOrdersLoaded, 89 | (allOrdersLoaded) => allOrdersLoaded 90 | ) 91 | const allOrders = (state) => get(state, 'exchange.allOrders.data', []) 92 | export const allOrdersSelector = createSelector( 93 | allOrders, 94 | (allOrders) => allOrders 95 | ) 96 | 97 | // all filled cancel 订单都加载完毕时,代表 order book (订单簿) 也加载完毕了 98 | const orderBookLoaded = (state) => 99 | cancelledOrdersLoaded(state) && 100 | filledOrdersLoaded(state) && 101 | allOrdersLoaded(state) 102 | export const orderBookLoadedSelector = createSelector( 103 | orderBookLoaded, 104 | (orderBookLoaded) => orderBookLoaded 105 | ) 106 | // 也可以简写成这样: 107 | // export const orderBookLoadedSelector = createSelector( 108 | // cancelledOrdersLoaded, 109 | // filledOrdersLoaded, 110 | // allOrdersLoaded, 111 | // (c, f, a) => c && f && a 112 | // ) 113 | 114 | // 获取正在交易中的订单 115 | const openOrders = (state) => { 116 | // 获取 全部订单、已完成订单、已取消订单。 117 | const all = allOrders(state) 118 | const filled = filledOrders(state) 119 | const cancelled = cancelledOrders(state) 120 | 121 | // 在全部订单中取出已完成订单和已取消订单,剩下的就是 open 订单. reject: 取出返回 false 的元素 122 | const openOrders = reject(all, (order) => { 123 | const orderFilled = filled.some((o) => o.id === order.id) 124 | const orderCancelled = cancelled.some((o) => o.id === order.id) 125 | // 返回 true 则代表该订单是 filled 或者 cancel 的, 这种订单我们不要 126 | return orderFilled || orderCancelled 127 | }) 128 | 129 | return openOrders 130 | } 131 | // 获取展示在 order Book 的订单 (order book: 交易未完成的订单) 132 | export const orderBookSelector = createSelector(openOrders, (orders) => { 133 | // Decorate orders 134 | orders = decorateOrderBookOrders(orders) 135 | orders = groupByOrderBookORders(orders) 136 | 137 | return orders 138 | }) 139 | 140 | // 我的已完成订单 141 | export const myFilledOrderLoadedSelector = createSelector( 142 | filledOrdersLoaded, 143 | (loaded) => loaded 144 | ) 145 | export const myFilledOrderSelector = createSelector( 146 | account, 147 | filledOrders, 148 | (account, filledOrders) => { 149 | // Filter our orders 取出属于 “我的” 150 | filledOrders = filledOrders.filter( 151 | (o) => o.user === account || o.userFill === account 152 | ) 153 | // Sort orders by date descending 154 | filledOrders = filledOrders.sort((a, b) => b.timestamp - a.timestamp) 155 | // Decorate orders - add display attributes 156 | filledOrders = decorateMyFilledOrders(filledOrders, filledOrders) 157 | return filledOrders 158 | } 159 | ) 160 | 161 | // 我的订单簿 162 | export const myOpenOrdersLoadedSelector = createSelector( 163 | orderBookLoaded, 164 | (loaded) => loaded 165 | ) 166 | export const myOpenOrdersSelector = createSelector( 167 | account, 168 | openOrders, 169 | (account, orders) => { 170 | // Filter orders created by current account 取出属于 “我的” 171 | orders = orders.filter((o) => o.user === account) 172 | // Decorate orders - add display attributes 173 | orders = decorateMyOpenOrders(orders, account) 174 | // Sort orders by date descending 175 | orders = orders.sort((a, b) => b.timestamp - a.timestamp) 176 | return orders 177 | } 178 | ) 179 | 180 | export const priceChartLoadedSelector = createSelector( 181 | filledOrdersLoaded, 182 | (loaded) => loaded 183 | ) 184 | export const priceChartSelector = createSelector(filledOrders, (orders) => { 185 | // Sort orders by date ascending to compare history 186 | orders = orders.sort((a, b) => a.timestamp - b.timestamp) 187 | // Decorate orders - add display attributes 188 | orders = orders.map((o) => decorateOrder(o)) 189 | 190 | // Get last 2 order for final price & price change 191 | let secondLastOrder, lastOrder 192 | ;[secondLastOrder, lastOrder] = orders.slice(orders.length - 2, orders.length) 193 | // get last order price 194 | const lastPrice = get(lastOrder, 'tokenPrice', 0) 195 | // get second last order price 196 | const secondLastPrice = get(secondLastOrder, 'tokenPrice', 0) 197 | const lastPriceChange = lastPrice >= secondLastPrice ? '+' : '-' 198 | const series = [{ data: buildGraphData(orders) }] 199 | 200 | return { 201 | lastPrice, 202 | lastPriceChange, 203 | series, 204 | } 205 | }) 206 | 207 | const orderCancelling = (state) => get(state, 'exchange.orderCancelling', false) 208 | export const orderCancellingSelector = createSelector( 209 | orderCancelling, 210 | (status) => status 211 | ) 212 | 213 | // 获取订单是否完成 214 | const orderFilling = (state) => get(state, 'exchange.orderFilling', false) 215 | 216 | export const orderFillingSelector = createSelector( 217 | orderFilling, 218 | (status) => status 219 | ) 220 | 221 | const balancesLoading = (state) => get(state, 'exchange.balancesLoading', true) 222 | export const balancesLoadingSelector = createSelector( 223 | balancesLoading, 224 | (status) => status 225 | ) 226 | const etherBalance = (state) => get(state, 'web3.balance', true) 227 | export const etherBalanceSelector = createSelector(etherBalance, (status) => 228 | formatBalance(status) 229 | ) 230 | const tokenBalance = (state) => get(state, 'token.balance', 0) 231 | export const tokenBalanceSelector = createSelector(tokenBalance, (balance) => 232 | formatBalance(balance) 233 | ) 234 | const exchangeEtherBalance = (state) => get(state, 'exchange.etherBalance', 0) 235 | export const exchangeEtherBalanceSelector = createSelector( 236 | exchangeEtherBalance, 237 | (balance) => formatBalance(balance) 238 | ) 239 | const exchangeTokenBalance = (state) => get(state, 'exchange.tokenBalance', 0) 240 | export const exchangeTokenBalanceSelector = createSelector( 241 | exchangeTokenBalance, 242 | (balance) => formatBalance(balance) 243 | ) 244 | 245 | const etherDepositAmount = (state) => 246 | get(state, 'exchange.etherDepositAmount', null) 247 | export const etherDepositAmountSelector = createSelector( 248 | etherDepositAmount, 249 | (amount) => amount 250 | ) 251 | const etherWithdrawAmount = (state) => 252 | get(state, 'exchange.etherWithdrawAmount', null) 253 | export const etherWithdrawAmountSelector = createSelector( 254 | etherWithdrawAmount, 255 | (amount) => amount 256 | ) 257 | const tokenDepositAmount = (state) => 258 | get(state, 'exchange.tokenDepositAmount', null) 259 | export const tokenDepositAmountSelector = createSelector( 260 | tokenDepositAmount, 261 | (amount) => amount 262 | ) 263 | const tokenWithdrawAmount = (state) => 264 | get(state, 'exchange.tokenWithdrawAmount', null) 265 | export const tokenWithdrawAmountSelector = createSelector( 266 | tokenWithdrawAmount, 267 | (amount) => amount 268 | ) 269 | 270 | const buyOrder = (state) => get(state, 'exchange.buyOrder', {}) 271 | export const buyOrderSelector = createSelector(buyOrder, (order) => order) 272 | 273 | const sellOrder = (state) => get(state, 'exchange.sellOrder', {}) 274 | export const sellOrderSelector = createSelector(sellOrder, (order) => order) 275 | 276 | /* *****************工具函数***************** */ 277 | /* *****************工具函数***************** */ 278 | /* *****************工具函数***************** */ 279 | /* *****************工具函数***************** */ 280 | /* *****************工具函数***************** */ 281 | /* *****************工具函数***************** */ 282 | 283 | const buildGraphData = (orders) => { 284 | // Group the orders by hour for the graph 285 | orders = groupBy(orders, (o) => 286 | moment 287 | .unix(o.timestamp) 288 | .startOf('hour') 289 | .format() 290 | ) 291 | // Get each hour where data exists 292 | const hours = Object.keys(orders) 293 | // Build the graph series 294 | const graphData = hours.map((hour) => { 295 | // Fetch all the orders from current hour 296 | const group = orders[hour] 297 | // Calculate price values - open, high, low, close 298 | const open = group[0] 299 | const high = maxBy(group, 'tokenPrice') 300 | const low = minBy(group, 'tokenPrice') 301 | const close = group[group.length - 1] 302 | 303 | return { 304 | x: new Date(hour), 305 | y: [open.tokenPrice, high.tokenPrice, low.tokenPrice, close.tokenPrice], 306 | } 307 | }) 308 | 309 | return graphData 310 | } 311 | 312 | const decorateMyOpenOrder = (order, account) => { 313 | const orderType = order.tokenGive === ETHER_ADDRESS ? 'buy' : 'sell' 314 | const orderTypeClass = orderType === 'buy' ? GREEN : RED 315 | return { 316 | ...order, 317 | orderType, 318 | orderTypeClass, 319 | } 320 | } 321 | 322 | const decorateMyOpenOrders = (orders, account) => 323 | orders.map((order) => { 324 | order = decorateOrder(order) 325 | order = decorateMyOpenOrder(order, account) 326 | return order 327 | }) 328 | 329 | const decorateMyFilledOrder = (order, account) => { 330 | let orderType 331 | // 332 | if (order.user === account) { 333 | orderType = order.tokenGive === ETHER_ADDRESS ? 'buy' : 'sell' 334 | } else { 335 | orderType = order.tokenGive === ETHER_ADDRESS ? 'sell' : 'buy' 336 | } 337 | 338 | return { 339 | ...order, 340 | orderType, 341 | orderTypeClass: orderType === 'buy' ? GREEN : RED, 342 | orderSign: orderType === 'buy' ? '+' : '-', 343 | } 344 | } 345 | 346 | const decorateMyFilledOrders = (orders, account) => 347 | orders.map((order) => { 348 | order = decorateOrder(order) 349 | order = decorateMyFilledOrder(order, account) 350 | return order 351 | }) 352 | 353 | // 对 order book 订单按照类型 buy 和 sell 进行分类 354 | const groupByOrderBookORders = (orders) => { 355 | // Group orders by 'orderType' 对 order book 进行分组啊, 分为买入订单和卖出订单 356 | orders = groupBy(orders, 'orderType') 357 | // Fetch buy orders. 获取 buy 类型的订单, 在 order.buy 中 358 | let buyOrders = get(orders, 'buy', []) 359 | let sellOrders = get(orders, 'sell', []) 360 | // Sort buy orders by token price. 按照价格降序排序 361 | buyOrders = buyOrders.sort((a, b) => b.tokenPrice - a.tokenPrice) 362 | sellOrders = sellOrders.sort((a, b) => b.tokenPrice - a.tokenPrice) 363 | return { 364 | ...orders, 365 | buyOrders, 366 | sellOrders, 367 | } 368 | } 369 | // 注意有是该函数名是复数 370 | const decorateOrderBookOrders = (orders) => 371 | orders.map((order) => { 372 | // 增加基本的信息 373 | order = decorateOrder(order) 374 | // decorate order book order 添加属于 order book 订单的信息 375 | order = decorateOrderBookOrder(order) 376 | return order 377 | }) 378 | // 添加属于 order book 的信息 379 | const decorateOrderBookOrder = (order) => { 380 | // 判断该订单是买入还是卖出 381 | const orderType = order.tokenGive === ETHER_ADDRESS ? 'buy' : 'sell' 382 | // 如果订单是买入, 则显示为绿色, 卖出则显示为红色 383 | const orderTypeClass = orderType === 'buy' ? GREEN : RED 384 | // 好像没用到... 385 | const orderFillClass = orderType === 'bug' ? 'sell' : 'buy' 386 | return { 387 | ...order, 388 | orderType, 389 | orderTypeClass, 390 | orderFillClass, 391 | } 392 | } 393 | 394 | // 专门为所有已完成的订单添加信息(主要该函数名称是复数的) 395 | const decorateFilledOrders = (orders) => { 396 | // 第一个订单的前一个订单是一样的, 这样能解决前一个订单不存在的情况, 方便后面处理 397 | let previousOrder = orders[0] 398 | return orders.map((order) => { 399 | // 填写一些基本的信息 400 | order = decorateOrder(order) 401 | // 填写一些属于 filled orders 才需要的信息 402 | order = decorateFilledOrder(order, previousOrder) 403 | // Update the previous order once it's decorated 404 | previousOrder = order 405 | return order 406 | }) 407 | } 408 | 409 | // 为单个订单添加基本的信息(通用型) 410 | const decorateOrder = (order) => { 411 | let etherAmount 412 | let tokenAmount 413 | if (order.tokenGive === ETHER_ADDRESS) { 414 | etherAmount = order.amountGive 415 | tokenAmount = order.amountGet 416 | } else { 417 | etherAmount = order.amountGet 418 | tokenAmount = order.amountGive 419 | } 420 | 421 | // Calculate token price to 5 decimal 精确到小数点后 5 位 422 | const precision = 100000 423 | let tokenPrice = etherAmount / tokenAmount 424 | tokenPrice = Math.round(tokenPrice * precision) / precision 425 | 426 | // 将时间戳格式化成时间 427 | const formattedTimestamp = moment.unix(order.timestamp).format('M/D HH:MM:SS') 428 | return { 429 | ...order, 430 | tokenPrice, 431 | etherAmount: ether(etherAmount), 432 | tokenAmount: tokens(tokenAmount), 433 | formattedTimestamp, 434 | } 435 | } 436 | 437 | // 为单个 filled order 添加信息 —— 价格是升了还是降了 438 | const decorateFilledOrder = (order, previousOrder) => { 439 | return { 440 | ...order, 441 | // 订单的价格类名, 升则显示绿色, 降则显示红色 442 | tokenPriceClass: tokenPriceClass(order.tokenPrice, order.id, previousOrder), 443 | } 444 | } 445 | 446 | const tokenPriceClass = (tokenPrice, orderId, previousOrder) => { 447 | // 第一个订单默认是绿色 448 | if (orderId === previousOrder.id) return GREEN 449 | 450 | // Show red price if order price lower than previous order 451 | if (tokenPrice <= previousOrder.tokenPrice) return RED 452 | // Show green price if order price higher than previous order 453 | return GREEN 454 | } 455 | -------------------------------------------------------------------------------- /test/Exchange.test.js: -------------------------------------------------------------------------------- 1 | import { tokens, EVM_REVERT, ETHER_ADDRESS, ether } from './helpers' 2 | 3 | const Token = artifacts.require('./Token') 4 | const Exchange = artifacts.require('./Exchange') 5 | 6 | require('chai') 7 | .use(require('chai-as-promised')) 8 | .should() 9 | 10 | contract('Exchange', ([deployer, feeAccount, user1, user2]) => { 11 | 12 | let token 13 | let exchange 14 | const feePercent = 10 15 | 16 | beforeEach(async () => { 17 | // deploy token 18 | token = await Token.new() 19 | // Transfer some tokens to user1 20 | token.transfer(user1, tokens(100), { from: deployer }) 21 | // deploy exchange 22 | exchange = await Exchange.new(feeAccount, feePercent) 23 | }) 24 | 25 | describe('deployment', () => { 26 | it('tracks the fee account', async () => { 27 | const result = await exchange.feeAccount() 28 | result.should.equal(feeAccount) 29 | }) 30 | it('tacks the fee percentage', async () => { 31 | const result = await exchange.feePercent() 32 | result.toString().should.equal(feePercent.toString()) 33 | }) 34 | }) 35 | 36 | describe('fallback', () => { 37 | it('reverts when Ether is send' ,async () => { 38 | // 直接向交易所发钱, 测试交易所是否会拒绝并退钱. (应该是这样吧) 39 | await exchange.sendTransaction({ value: ether(1), from: user1 }).should.be.rejectedWith(EVM_REVERT) 40 | }) 41 | }) 42 | 43 | describe('depositing Ether', async () => { 44 | let result 45 | let amount 46 | 47 | beforeEach(async () => { 48 | amount = ether(1) 49 | result = await exchange.depositEther({ from: user1, value: amount }) 50 | }) 51 | 52 | it('tracks the Ether deposit', async () => { 53 | // 进行交易, user1 存储, 存到了 exchange 中. balance 是 user1 在交易所的余额 54 | const balance = await exchange.tokens(ETHER_ADDRESS, user1) 55 | balance.toString().should.equal(amount.toString()) 56 | }) 57 | it('emits a Deposit event', async () => { 58 | const log = result.logs[0] 59 | log.event.should.eq('Deposit') 60 | 61 | const event = log.args 62 | event.token.should.equal(ETHER_ADDRESS, 'token address is correct') 63 | event.user.should.equal(user1, 'user address is correct') 64 | event.amount.toString().should.equal(amount.toString(), 'amount is correct') 65 | event.balance.toString().should.equal(amount.toString(), 'balance is correct') 66 | }) 67 | }) 68 | 69 | describe('withdraws Ether func', async () => { 70 | let result 71 | let amount 72 | 73 | beforeEach(async () => { 74 | // Deposit Ether first 75 | amount = ether(1) 76 | result = await exchange.depositEther({ from: user1, value: amount }) 77 | }) 78 | 79 | describe('success', async () => { 80 | beforeEach(async () => { 81 | // Withdraw Ether 82 | result = await exchange.withdrawEther(amount, { from: user1 }) 83 | }) 84 | 85 | it('withdraws Ether funds', async () => { 86 | const balance = await exchange.tokens(ETHER_ADDRESS, user1) 87 | balance.toString().should.equal('0') 88 | }) 89 | it('emits a "Withdraw" event', async () => { 90 | const log = result.logs[0] 91 | log.event.should.eq('Withdraw') 92 | 93 | const event = log.args 94 | event.token.should.equal(ETHER_ADDRESS) 95 | event.user.should.equal(user1) 96 | event.amount.toString().should.equal(amount.toString()) 97 | event.balance.toString().should.equal('0') 98 | }) 99 | }) 100 | 101 | describe('failure', async () => { 102 | it('rejects withdraws for insufficient balance', async () => { 103 | // 测试转账 100 ether, 因为我们没有存入 100 ether, 所以此时交易所应该拒绝这笔交易 104 | await exchange.withdrawEther(ether(100), { from: user1 }) 105 | .should.be.rejectedWith(EVM_REVERT) 106 | }) 107 | }) 108 | }) 109 | 110 | describe('depositing tokens', () => { 111 | let amount 112 | let result 113 | 114 | describe('success', async () => { 115 | beforeEach(async () => { 116 | amount = tokens(10) 117 | // approve token 118 | await token.approve(exchange.address, amount, { from: user1 }) 119 | // deposit token 120 | result = await exchange.depositToken(token.address, amount, { from: user1 }) 121 | }) 122 | it('tracks the token deposit', async () => { 123 | // Check exchange token balance 124 | let balance 125 | balance = await token.balanceOf(exchange.address) 126 | balance.toString().should.equal(amount.toString()) 127 | 128 | // check token on exchange 129 | balance = await exchange.tokens(token.address, user1) 130 | balance.toString().should.equal(amount.toString()) 131 | }) 132 | it('emits a Deposit event', async () => { 133 | const log = result.logs[0] 134 | log.event.should.eq('Deposit') 135 | 136 | const event = log.args 137 | event.token.should.equal(token.address, 'token address is correct') 138 | event.user.should.equal(user1, 'user address is correct') 139 | event.amount.toString().should.equal(amount.toString(), 'amount is correct') 140 | event.balance.toString().should.equal(amount.toString(), 'balance is correct') 141 | }) 142 | 143 | }) 144 | 145 | describe('failure', () => { 146 | it('rejects Ether deposit', async () => { 147 | // 这里的地址是 ETHER, sol 中的 require(_token != ETHER) 将会断言为假 148 | await exchange.depositToken(ETHER_ADDRESS, tokens(10), { from: user1 }) 149 | .should.be.rejectedWith(EVM_REVERT) 150 | }) 151 | it('fails when no tokens are approved', async () => { 152 | // Don't approve any tokens before depositing 尝试在没有获取批准的情况存入代币,这将会导致一个错误 153 | await exchange.depositToken(token.address, tokens(10), { from: user1 }) 154 | .should.be.rejectedWith(EVM_REVERT) 155 | }) 156 | }) 157 | }) 158 | 159 | describe('withdrawing tokens', async () => { 160 | let result 161 | let amount 162 | 163 | describe('success', async () => { 164 | beforeEach(async () => { 165 | // deposit token to exchange from user1 166 | amount = tokens(10) 167 | await token.approve(exchange.address, amount, { from: user1 }) // approve token first 168 | result = await exchange.depositToken(token.address, amount, { from: user1 }) // deposit token secondly 169 | 170 | // withdraw token 171 | result = await exchange.withdrawToken(token.address, amount, { from: user1 }) 172 | }) 173 | 174 | it('withdraw Token func', async () => { 175 | const balance = await exchange.tokens(token.address, user1) // from user1 176 | balance.toString().should.be.equal('0') 177 | }) 178 | it('emits a "Withdraw" event', async () => { 179 | const log = result.logs[0] 180 | log.event.should.eq('Withdraw') 181 | 182 | const event = log.args 183 | event.token.should.equal(token.address) 184 | event.user.should.equal(user1) 185 | event.amount.toString().should.be.equal(amount.toString()) 186 | event.balance.toString().should.be.equal('0') 187 | }) 188 | }) 189 | 190 | describe('failure', async () => { 191 | it('reject Ether withdraws', async () => { // test require(_token != ETHER); 192 | await exchange.withdrawToken(ETHER_ADDRESS, tokens(10), { from: user1 }) 193 | .should.be.rejectedWith(EVM_REVERT) 194 | }) 195 | it('reject for insufficient balances', async () => { // test require(tokens[_token][msg.sender] >= _amount); 196 | // Attempt to withdraw tokens without depositing any first 197 | await exchange.withdrawToken(token.address, tokens(10), { from: user1 }) 198 | .should.be.rejectedWith(EVM_REVERT) 199 | }) 200 | }) 201 | 202 | }) 203 | 204 | describe('checking user balance', async () => { 205 | beforeEach(async () => { 206 | await exchange.depositEther({ from: user1, value: ether(1) }) 207 | }) 208 | 209 | it('returns user balance', async () => { 210 | const result = await exchange.balanceOf(ETHER_ADDRESS, user1) 211 | result.toString().should.equal(ether(1).toString()) 212 | }) 213 | 214 | }) 215 | 216 | describe('making orders', async () => { 217 | let result 218 | 219 | beforeEach(async () => { 220 | result = await exchange.makeOrder(token.address, tokens(1), ETHER_ADDRESS, ether(1), { from: user1 }) 221 | }) 222 | 223 | it('tracks the newly created order', async () => { 224 | const orderCount = await exchange.orderCount() 225 | orderCount.toString().should.equal('1') // 1 是第一个订单的 id 226 | 227 | const order = await exchange.orders('1') // 取出 id 为 1 的订单 228 | order.id.toString().should.equal('1', 'id is correct') 229 | order.user.should.equal(user1, 'user is correct') 230 | order.tokenGet.should.equal(token.address, 'tokenGet is correct') 231 | order.amountGet.toString().should.equal(tokens(1).toString(), 'amountGet is correct') 232 | order.tokenGive.should.equal(ETHER_ADDRESS, 'tokenGive is correct') 233 | order.amountGive.toString().should.equal(ether(1).toString(), 'amountGive is correct') 234 | order.timestamp.toString().length.should.be.at.least(1, 'timestamp is present') 235 | }) 236 | it('emits an "Order" event', async () => { 237 | const log = result.logs[0] 238 | log.event.should.eq('Order') 239 | 240 | const event = log.args 241 | event.id.toString().should.equal('1', 'id is correct') 242 | event.user.should.equal(user1, 'user is correct') 243 | event.tokenGet.should.equal(token.address, 'tokenGet is correct') 244 | event.amountGet.toString().should.equal(tokens(1).toString(), 'amountGet is correct') 245 | event.tokenGive.should.equal(ETHER_ADDRESS, 'tokenGive is correct') 246 | event.amountGive.toString().should.equal(ether(1).toString(), 'amountGive is correct') 247 | event.timestamp.toString().length.should.be.at.least(1, 'timestamp is present') 248 | }) 249 | }) 250 | 251 | describe('order actions', async () => { 252 | beforeEach(async () => { 253 | // user1 deposits ether. user1 存款 1 ether 254 | await exchange.depositEther({ from: user1, value: ether(1) }) 255 | 256 | // deployer give token to user2. deployer 给 user2 发 100 tokens 257 | await token.transfer(user2, tokens(100), { from: deployer }) 258 | // user2 deposits tokens only. user2 只收 2 tokens (?), 并将对 2 tokens 进行存款 259 | await token.approve(exchange.address, tokens(2), { from: user2 }) 260 | await exchange.depositToken(token.address, tokens(2), { from: user2 }) 261 | 262 | // user 1 makes an order to buy tokens with Ether. user1 创建订单, 即 user1 是 “_user”, 订单中 token=1, ether=1 263 | await exchange.makeOrder(token.address, tokens(1), ETHER_ADDRESS, ether(1), { from: user1 }) 264 | }) 265 | 266 | describe('filling orders', async () => { 267 | let result 268 | 269 | describe('success', async () => { 270 | beforeEach(async () => { 271 | console.log('填写订单: filling orders === success === beforeEach === exchange.fillOrder === begin') 272 | // user2 fills order. user2 填写订单, 即 user2 是 “msg.sender”. 他将会接收到 1 token 和 1 ether 273 | result = await exchange.fillOrder('1', { from: user2 }) 274 | console.log('填写订单: filling orders === success === beforeEach === exchange.fillOrder === end') 275 | }) 276 | 277 | it('executes the trade & charges fees', async () => { 278 | let balance 279 | balance = await exchange.balanceOf(token.address, user1) // 获取 user1 的 token 余额 280 | balance.toString().should.equal(tokens(1).toString(), 'user1 received tokens') 281 | balance = await exchange.balanceOf(ETHER_ADDRESS, user2) // 获取 user2 的 ether 余额 282 | balance.toString().should.equal(ether(1).toString(), 'user2 received Ether') 283 | balance = await exchange.balanceOf(ETHER_ADDRESS, user1) // 获取 user1 的 ether 余额 284 | balance.toString().should.equal('0', 'user1 Ether deducted') 285 | balance = await exchange.balanceOf(token.address, user2) // 获取 user2 的 token 余额(扣除了小费) 286 | balance.toString().should.equal(tokens(0.9).toString(), 'user2 tokens deducted with fee applied') 287 | 288 | const feeAccount = await exchange.feeAccount() 289 | balance = await exchange.balanceOf(token.address, feeAccount) 290 | balance.toString().should.equal(tokens(0.1).toString(), 'feeAccount received fee') 291 | }) 292 | it('updates filled orders', async () => { 293 | const orderFilled = await exchange.orderFilled(1) 294 | orderFilled.should.equal(true) 295 | }) 296 | it('emits a "Trade" event', async () => { 297 | const log = result.logs[0] 298 | log.event.should.eq('Trade') 299 | 300 | const event = log.args 301 | event.id.toString().should.equal('1', 'id is correct') 302 | event.user.should.equal(user1, 'user is correct') 303 | event.tokenGet.should.equal(token.address, 'tokenGet is correct') 304 | event.amountGet.toString().should.equal(tokens(1).toString(), 'amountGet is correct') 305 | event.tokenGive.should.equal(ETHER_ADDRESS, 'tokenGive is correct') 306 | event.amountGive.toString().should.equal(ether(1).toString(), 'amountGive is correct') 307 | event.userFill.should.equal(user2, 'userFill is correct') 308 | event.timestamp.toString().length.should.be.at.least(1, 'timestamp is present') 309 | }) 310 | }) 311 | 312 | describe('failure', async () => { 313 | it('rejects invalid order ids', async () => { 314 | const invalidOrderId = 99999 315 | await exchange.fillOrder(invalidOrderId, { from: user2 }).should.be.rejectedWith(EVM_REVERT) 316 | }) 317 | it('rejects already-filled orders', async () => { 318 | // Fill the order 319 | await exchange.fillOrder('1', { from: user2 }).should.be.fulfilled 320 | // Try to fill it again 321 | await exchange.fillOrder('1', { from: user2 }).should.be.rejectedWith(EVM_REVERT) 322 | }) 323 | it('rejects cancelled orders', async () => { 324 | // Cancel the order 325 | await exchange.cancelOrder('1', { from: user1 }).should.be.fulfilled 326 | // Try to fill the order 327 | await exchange.fillOrder('1', { from: user2 }).should.be.rejectedWith(EVM_REVERT) 328 | }) 329 | }) 330 | }) 331 | 332 | describe('cancelling orders', async () => { 333 | let result 334 | 335 | describe('success', async () => { 336 | beforeEach(async () => { 337 | result = await exchange.cancelOrder(1, { from: user1 }) 338 | }) 339 | 340 | it('updates cancelled orders', async () => { 341 | const orderCancelled = await exchange.orderCancelled(1) 342 | orderCancelled.should.equal(true) 343 | }) 344 | it('emits a "Cancel" event', async () => { 345 | const log = result.logs[0] 346 | log.event.should.eq('Cancel') 347 | 348 | const event = log.args 349 | event.id.toString().should.equal('1', 'id is correct') 350 | event.user.should.equal(user1, 'user is correct') 351 | event.tokenGet.should.equal(token.address, 'tokenGet is correct') 352 | event.amountGet.toString().should.equal(tokens(1).toString(), 'amountGet is correct') 353 | event.tokenGive.should.equal(ETHER_ADDRESS, 'tokenGive is correct') 354 | event.amountGive.toString().should.equal(ether(1).toString(), 'amountGive is correct') 355 | event.timestamp.toString().length.should.be.at.least(1, 'timestamp is present') 356 | }) 357 | }) 358 | 359 | describe('failure', async () => { 360 | it('rejects invalid order ids', async () => { 361 | const invalidOrderId = 99999 362 | await exchange.cancelOrder(invalidOrderId, { from: user1 }) 363 | .should.be.rejectedWith(EVM_REVERT) 364 | }) 365 | it('rejects unauthorized cancellations', async () => { 366 | // Try to cancel the order from another user 367 | await exchange.cancelOrder('1', { from: user2 }) 368 | .should.be.rejectedWith(EVM_REVERT) 369 | }) 370 | }) 371 | }) 372 | }) 373 | 374 | 375 | 376 | }) -------------------------------------------------------------------------------- /src/abis/Migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "Migrations", 3 | "abi": [ 4 | { 5 | "constant": true, 6 | "inputs": [], 7 | "name": "last_completed_migration", 8 | "outputs": [ 9 | { 10 | "name": "", 11 | "type": "uint256" 12 | } 13 | ], 14 | "payable": false, 15 | "stateMutability": "view", 16 | "type": "function" 17 | }, 18 | { 19 | "constant": true, 20 | "inputs": [], 21 | "name": "owner", 22 | "outputs": [ 23 | { 24 | "name": "", 25 | "type": "address" 26 | } 27 | ], 28 | "payable": false, 29 | "stateMutability": "view", 30 | "type": "function" 31 | }, 32 | { 33 | "constant": false, 34 | "inputs": [ 35 | { 36 | "name": "completed", 37 | "type": "uint256" 38 | } 39 | ], 40 | "name": "setCompleted", 41 | "outputs": [], 42 | "payable": false, 43 | "stateMutability": "nonpayable", 44 | "type": "function" 45 | } 46 | ], 47 | "metadata": "{\"compiler\":{\"version\":\"0.5.0+commit.1d4f565a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"last_completed_migration\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"completed\",\"type\":\"uint256\"}],\"name\":\"setCompleted\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"methods\":{}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"project:/src/contracts/Migrations.sol\":\"Migrations\"},\"evmVersion\":\"byzantium\",\"libraries\":{},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"project:/src/contracts/Migrations.sol\":{\"keccak256\":\"0x7eaedbb1a3e4e0f585d9063393872f88ded247ca3c3c3c8492ea18e7629a6411\",\"urls\":[\"bzzr://c38ff3b34e1d1e801ca6ebabdc76561ae7ea4f977917aeb6f9d62532ae572f6a\"]}},\"version\":1}", 48 | "bytecode": "0x6080604052336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005057600080fd5b50610264806100606000396000f3fe608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063445df0ac1461005c5780638da5cb5b14610087578063fdacd576146100de575b600080fd5b34801561006857600080fd5b50610071610119565b6040518082815260200191505060405180910390f35b34801561009357600080fd5b5061009c61011f565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3480156100ea57600080fd5b506101176004803603602081101561010157600080fd5b8101908080359060200190929190505050610144565b005b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561022e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001807f546869732066756e6374696f6e206973207265737472696374656420746f207481526020017f686520636f6e74726163742773206f776e65720000000000000000000000000081525060400191505060405180910390fd5b806001819055505056fea165627a7a7230582082217b726bf25059019474c39a4782b974a4caa27fd5613bd005f58e32f7867b0029", 49 | "deployedBytecode": "0x608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063445df0ac1461005c5780638da5cb5b14610087578063fdacd576146100de575b600080fd5b34801561006857600080fd5b50610071610119565b6040518082815260200191505060405180910390f35b34801561009357600080fd5b5061009c61011f565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3480156100ea57600080fd5b506101176004803603602081101561010157600080fd5b8101908080359060200190929190505050610144565b005b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561022e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001807f546869732066756e6374696f6e206973207265737472696374656420746f207481526020017f686520636f6e74726163742773206f776e65720000000000000000000000000081525060400191505060405180910390fd5b806001819055505056fea165627a7a7230582082217b726bf25059019474c39a4782b974a4caa27fd5613bd005f58e32f7867b0029", 50 | "sourceMap": "66:352:2:-;;;113:10;90:33;;;;;;;;;;;;;;;;;;;;66:352;8:9:-1;5:2;;;30:1;27;20:12;5:2;66:352:2;;;;;;;", 51 | "deployedSourceMap": "66:352:2:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;127:36;;8:9:-1;5:2;;;30:1;27;20:12;5:2;127:36:2;;;;;;;;;;;;;;;;;;;;;;;90:33;;8:9:-1;5:2;;;30:1;27;20:12;5:2;90:33:2;;;;;;;;;;;;;;;;;;;;;;;;;;;313:103;;8:9:-1;5:2;;;30:1;27;20:12;5:2;313:103:2;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;313:103:2;;;;;;;;;;;;;;;;;;;;127:36;;;;:::o;90:33::-;;;;;;;;;;;;;:::o;313:103::-;225:5;;;;;;;;;;;211:19;;:10;:19;;;196:101;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;402:9;375:24;:36;;;;313:103;:::o", 52 | "source": "// SPDX-License-Identifier: MIT\npragma solidity >=0.4.22 <0.9.0;\n\ncontract Migrations {\n address public owner = msg.sender;\n uint public last_completed_migration;\n\n modifier restricted() {\n require(\n msg.sender == owner,\n \"This function is restricted to the contract's owner\"\n );\n _;\n }\n\n function setCompleted(uint completed) public restricted {\n last_completed_migration = completed;\n }\n}\n", 53 | "sourcePath": "D:\\truffle\\src\\contracts\\Migrations.sol", 54 | "ast": { 55 | "absolutePath": "project:/src/contracts/Migrations.sol", 56 | "exportedSymbols": { 57 | "Migrations": [ 58 | 811 59 | ] 60 | }, 61 | "id": 812, 62 | "nodeType": "SourceUnit", 63 | "nodes": [ 64 | { 65 | "id": 780, 66 | "literals": [ 67 | "solidity", 68 | ">=", 69 | "0.4", 70 | ".22", 71 | "<", 72 | "0.9", 73 | ".0" 74 | ], 75 | "nodeType": "PragmaDirective", 76 | "src": "32:32:2" 77 | }, 78 | { 79 | "baseContracts": [], 80 | "contractDependencies": [], 81 | "contractKind": "contract", 82 | "documentation": null, 83 | "fullyImplemented": true, 84 | "id": 811, 85 | "linearizedBaseContracts": [ 86 | 811 87 | ], 88 | "name": "Migrations", 89 | "nodeType": "ContractDefinition", 90 | "nodes": [ 91 | { 92 | "constant": false, 93 | "id": 784, 94 | "name": "owner", 95 | "nodeType": "VariableDeclaration", 96 | "scope": 811, 97 | "src": "90:33:2", 98 | "stateVariable": true, 99 | "storageLocation": "default", 100 | "typeDescriptions": { 101 | "typeIdentifier": "t_address", 102 | "typeString": "address" 103 | }, 104 | "typeName": { 105 | "id": 781, 106 | "name": "address", 107 | "nodeType": "ElementaryTypeName", 108 | "src": "90:7:2", 109 | "stateMutability": "nonpayable", 110 | "typeDescriptions": { 111 | "typeIdentifier": "t_address", 112 | "typeString": "address" 113 | } 114 | }, 115 | "value": { 116 | "argumentTypes": null, 117 | "expression": { 118 | "argumentTypes": null, 119 | "id": 782, 120 | "name": "msg", 121 | "nodeType": "Identifier", 122 | "overloadedDeclarations": [], 123 | "referencedDeclaration": 1060, 124 | "src": "113:3:2", 125 | "typeDescriptions": { 126 | "typeIdentifier": "t_magic_message", 127 | "typeString": "msg" 128 | } 129 | }, 130 | "id": 783, 131 | "isConstant": false, 132 | "isLValue": false, 133 | "isPure": false, 134 | "lValueRequested": false, 135 | "memberName": "sender", 136 | "nodeType": "MemberAccess", 137 | "referencedDeclaration": null, 138 | "src": "113:10:2", 139 | "typeDescriptions": { 140 | "typeIdentifier": "t_address_payable", 141 | "typeString": "address payable" 142 | } 143 | }, 144 | "visibility": "public" 145 | }, 146 | { 147 | "constant": false, 148 | "id": 786, 149 | "name": "last_completed_migration", 150 | "nodeType": "VariableDeclaration", 151 | "scope": 811, 152 | "src": "127:36:2", 153 | "stateVariable": true, 154 | "storageLocation": "default", 155 | "typeDescriptions": { 156 | "typeIdentifier": "t_uint256", 157 | "typeString": "uint256" 158 | }, 159 | "typeName": { 160 | "id": 785, 161 | "name": "uint", 162 | "nodeType": "ElementaryTypeName", 163 | "src": "127:4:2", 164 | "typeDescriptions": { 165 | "typeIdentifier": "t_uint256", 166 | "typeString": "uint256" 167 | } 168 | }, 169 | "value": null, 170 | "visibility": "public" 171 | }, 172 | { 173 | "body": { 174 | "id": 797, 175 | "nodeType": "Block", 176 | "src": "190:119:2", 177 | "statements": [ 178 | { 179 | "expression": { 180 | "argumentTypes": null, 181 | "arguments": [ 182 | { 183 | "argumentTypes": null, 184 | "commonType": { 185 | "typeIdentifier": "t_address", 186 | "typeString": "address" 187 | }, 188 | "id": 792, 189 | "isConstant": false, 190 | "isLValue": false, 191 | "isPure": false, 192 | "lValueRequested": false, 193 | "leftExpression": { 194 | "argumentTypes": null, 195 | "expression": { 196 | "argumentTypes": null, 197 | "id": 789, 198 | "name": "msg", 199 | "nodeType": "Identifier", 200 | "overloadedDeclarations": [], 201 | "referencedDeclaration": 1060, 202 | "src": "211:3:2", 203 | "typeDescriptions": { 204 | "typeIdentifier": "t_magic_message", 205 | "typeString": "msg" 206 | } 207 | }, 208 | "id": 790, 209 | "isConstant": false, 210 | "isLValue": false, 211 | "isPure": false, 212 | "lValueRequested": false, 213 | "memberName": "sender", 214 | "nodeType": "MemberAccess", 215 | "referencedDeclaration": null, 216 | "src": "211:10:2", 217 | "typeDescriptions": { 218 | "typeIdentifier": "t_address_payable", 219 | "typeString": "address payable" 220 | } 221 | }, 222 | "nodeType": "BinaryOperation", 223 | "operator": "==", 224 | "rightExpression": { 225 | "argumentTypes": null, 226 | "id": 791, 227 | "name": "owner", 228 | "nodeType": "Identifier", 229 | "overloadedDeclarations": [], 230 | "referencedDeclaration": 784, 231 | "src": "225:5:2", 232 | "typeDescriptions": { 233 | "typeIdentifier": "t_address", 234 | "typeString": "address" 235 | } 236 | }, 237 | "src": "211:19:2", 238 | "typeDescriptions": { 239 | "typeIdentifier": "t_bool", 240 | "typeString": "bool" 241 | } 242 | }, 243 | { 244 | "argumentTypes": null, 245 | "hexValue": "546869732066756e6374696f6e206973207265737472696374656420746f2074686520636f6e74726163742773206f776e6572", 246 | "id": 793, 247 | "isConstant": false, 248 | "isLValue": false, 249 | "isPure": true, 250 | "kind": "string", 251 | "lValueRequested": false, 252 | "nodeType": "Literal", 253 | "src": "238:53:2", 254 | "subdenomination": null, 255 | "typeDescriptions": { 256 | "typeIdentifier": "t_stringliteral_f60fe2d9d123295bf92ecf95167f1fa709e374da35e4c083bd39dc2d82acd8b1", 257 | "typeString": "literal_string \"This function is restricted to the contract's owner\"" 258 | }, 259 | "value": "This function is restricted to the contract's owner" 260 | } 261 | ], 262 | "expression": { 263 | "argumentTypes": [ 264 | { 265 | "typeIdentifier": "t_bool", 266 | "typeString": "bool" 267 | }, 268 | { 269 | "typeIdentifier": "t_stringliteral_f60fe2d9d123295bf92ecf95167f1fa709e374da35e4c083bd39dc2d82acd8b1", 270 | "typeString": "literal_string \"This function is restricted to the contract's owner\"" 271 | } 272 | ], 273 | "id": 788, 274 | "name": "require", 275 | "nodeType": "Identifier", 276 | "overloadedDeclarations": [ 277 | 1063, 278 | 1064 279 | ], 280 | "referencedDeclaration": 1064, 281 | "src": "196:7:2", 282 | "typeDescriptions": { 283 | "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", 284 | "typeString": "function (bool,string memory) pure" 285 | } 286 | }, 287 | "id": 794, 288 | "isConstant": false, 289 | "isLValue": false, 290 | "isPure": false, 291 | "kind": "functionCall", 292 | "lValueRequested": false, 293 | "names": [], 294 | "nodeType": "FunctionCall", 295 | "src": "196:101:2", 296 | "typeDescriptions": { 297 | "typeIdentifier": "t_tuple$__$", 298 | "typeString": "tuple()" 299 | } 300 | }, 301 | "id": 795, 302 | "nodeType": "ExpressionStatement", 303 | "src": "196:101:2" 304 | }, 305 | { 306 | "id": 796, 307 | "nodeType": "PlaceholderStatement", 308 | "src": "303:1:2" 309 | } 310 | ] 311 | }, 312 | "documentation": null, 313 | "id": 798, 314 | "name": "restricted", 315 | "nodeType": "ModifierDefinition", 316 | "parameters": { 317 | "id": 787, 318 | "nodeType": "ParameterList", 319 | "parameters": [], 320 | "src": "187:2:2" 321 | }, 322 | "src": "168:141:2", 323 | "visibility": "internal" 324 | }, 325 | { 326 | "body": { 327 | "id": 809, 328 | "nodeType": "Block", 329 | "src": "369:47:2", 330 | "statements": [ 331 | { 332 | "expression": { 333 | "argumentTypes": null, 334 | "id": 807, 335 | "isConstant": false, 336 | "isLValue": false, 337 | "isPure": false, 338 | "lValueRequested": false, 339 | "leftHandSide": { 340 | "argumentTypes": null, 341 | "id": 805, 342 | "name": "last_completed_migration", 343 | "nodeType": "Identifier", 344 | "overloadedDeclarations": [], 345 | "referencedDeclaration": 786, 346 | "src": "375:24:2", 347 | "typeDescriptions": { 348 | "typeIdentifier": "t_uint256", 349 | "typeString": "uint256" 350 | } 351 | }, 352 | "nodeType": "Assignment", 353 | "operator": "=", 354 | "rightHandSide": { 355 | "argumentTypes": null, 356 | "id": 806, 357 | "name": "completed", 358 | "nodeType": "Identifier", 359 | "overloadedDeclarations": [], 360 | "referencedDeclaration": 800, 361 | "src": "402:9:2", 362 | "typeDescriptions": { 363 | "typeIdentifier": "t_uint256", 364 | "typeString": "uint256" 365 | } 366 | }, 367 | "src": "375:36:2", 368 | "typeDescriptions": { 369 | "typeIdentifier": "t_uint256", 370 | "typeString": "uint256" 371 | } 372 | }, 373 | "id": 808, 374 | "nodeType": "ExpressionStatement", 375 | "src": "375:36:2" 376 | } 377 | ] 378 | }, 379 | "documentation": null, 380 | "id": 810, 381 | "implemented": true, 382 | "kind": "function", 383 | "modifiers": [ 384 | { 385 | "arguments": null, 386 | "id": 803, 387 | "modifierName": { 388 | "argumentTypes": null, 389 | "id": 802, 390 | "name": "restricted", 391 | "nodeType": "Identifier", 392 | "overloadedDeclarations": [], 393 | "referencedDeclaration": 798, 394 | "src": "358:10:2", 395 | "typeDescriptions": { 396 | "typeIdentifier": "t_modifier$__$", 397 | "typeString": "modifier ()" 398 | } 399 | }, 400 | "nodeType": "ModifierInvocation", 401 | "src": "358:10:2" 402 | } 403 | ], 404 | "name": "setCompleted", 405 | "nodeType": "FunctionDefinition", 406 | "parameters": { 407 | "id": 801, 408 | "nodeType": "ParameterList", 409 | "parameters": [ 410 | { 411 | "constant": false, 412 | "id": 800, 413 | "name": "completed", 414 | "nodeType": "VariableDeclaration", 415 | "scope": 810, 416 | "src": "335:14:2", 417 | "stateVariable": false, 418 | "storageLocation": "default", 419 | "typeDescriptions": { 420 | "typeIdentifier": "t_uint256", 421 | "typeString": "uint256" 422 | }, 423 | "typeName": { 424 | "id": 799, 425 | "name": "uint", 426 | "nodeType": "ElementaryTypeName", 427 | "src": "335:4:2", 428 | "typeDescriptions": { 429 | "typeIdentifier": "t_uint256", 430 | "typeString": "uint256" 431 | } 432 | }, 433 | "value": null, 434 | "visibility": "internal" 435 | } 436 | ], 437 | "src": "334:16:2" 438 | }, 439 | "returnParameters": { 440 | "id": 804, 441 | "nodeType": "ParameterList", 442 | "parameters": [], 443 | "src": "369:0:2" 444 | }, 445 | "scope": 811, 446 | "src": "313:103:2", 447 | "stateMutability": "nonpayable", 448 | "superFunction": null, 449 | "visibility": "public" 450 | } 451 | ], 452 | "scope": 812, 453 | "src": "66:352:2" 454 | } 455 | ], 456 | "src": "32:387:2" 457 | }, 458 | "legacyAST": { 459 | "attributes": { 460 | "absolutePath": "project:/src/contracts/Migrations.sol", 461 | "exportedSymbols": { 462 | "Migrations": [ 463 | 811 464 | ] 465 | } 466 | }, 467 | "children": [ 468 | { 469 | "attributes": { 470 | "literals": [ 471 | "solidity", 472 | ">=", 473 | "0.4", 474 | ".22", 475 | "<", 476 | "0.9", 477 | ".0" 478 | ] 479 | }, 480 | "id": 780, 481 | "name": "PragmaDirective", 482 | "src": "32:32:2" 483 | }, 484 | { 485 | "attributes": { 486 | "baseContracts": [ 487 | null 488 | ], 489 | "contractDependencies": [ 490 | null 491 | ], 492 | "contractKind": "contract", 493 | "documentation": null, 494 | "fullyImplemented": true, 495 | "linearizedBaseContracts": [ 496 | 811 497 | ], 498 | "name": "Migrations", 499 | "scope": 812 500 | }, 501 | "children": [ 502 | { 503 | "attributes": { 504 | "constant": false, 505 | "name": "owner", 506 | "scope": 811, 507 | "stateVariable": true, 508 | "storageLocation": "default", 509 | "type": "address", 510 | "visibility": "public" 511 | }, 512 | "children": [ 513 | { 514 | "attributes": { 515 | "name": "address", 516 | "stateMutability": "nonpayable", 517 | "type": "address" 518 | }, 519 | "id": 781, 520 | "name": "ElementaryTypeName", 521 | "src": "90:7:2" 522 | }, 523 | { 524 | "attributes": { 525 | "argumentTypes": null, 526 | "isConstant": false, 527 | "isLValue": false, 528 | "isPure": false, 529 | "lValueRequested": false, 530 | "member_name": "sender", 531 | "referencedDeclaration": null, 532 | "type": "address payable" 533 | }, 534 | "children": [ 535 | { 536 | "attributes": { 537 | "argumentTypes": null, 538 | "overloadedDeclarations": [ 539 | null 540 | ], 541 | "referencedDeclaration": 1060, 542 | "type": "msg", 543 | "value": "msg" 544 | }, 545 | "id": 782, 546 | "name": "Identifier", 547 | "src": "113:3:2" 548 | } 549 | ], 550 | "id": 783, 551 | "name": "MemberAccess", 552 | "src": "113:10:2" 553 | } 554 | ], 555 | "id": 784, 556 | "name": "VariableDeclaration", 557 | "src": "90:33:2" 558 | }, 559 | { 560 | "attributes": { 561 | "constant": false, 562 | "name": "last_completed_migration", 563 | "scope": 811, 564 | "stateVariable": true, 565 | "storageLocation": "default", 566 | "type": "uint256", 567 | "value": null, 568 | "visibility": "public" 569 | }, 570 | "children": [ 571 | { 572 | "attributes": { 573 | "name": "uint", 574 | "type": "uint256" 575 | }, 576 | "id": 785, 577 | "name": "ElementaryTypeName", 578 | "src": "127:4:2" 579 | } 580 | ], 581 | "id": 786, 582 | "name": "VariableDeclaration", 583 | "src": "127:36:2" 584 | }, 585 | { 586 | "attributes": { 587 | "documentation": null, 588 | "name": "restricted", 589 | "visibility": "internal" 590 | }, 591 | "children": [ 592 | { 593 | "attributes": { 594 | "parameters": [ 595 | null 596 | ] 597 | }, 598 | "children": [], 599 | "id": 787, 600 | "name": "ParameterList", 601 | "src": "187:2:2" 602 | }, 603 | { 604 | "children": [ 605 | { 606 | "children": [ 607 | { 608 | "attributes": { 609 | "argumentTypes": null, 610 | "isConstant": false, 611 | "isLValue": false, 612 | "isPure": false, 613 | "isStructConstructorCall": false, 614 | "lValueRequested": false, 615 | "names": [ 616 | null 617 | ], 618 | "type": "tuple()", 619 | "type_conversion": false 620 | }, 621 | "children": [ 622 | { 623 | "attributes": { 624 | "argumentTypes": [ 625 | { 626 | "typeIdentifier": "t_bool", 627 | "typeString": "bool" 628 | }, 629 | { 630 | "typeIdentifier": "t_stringliteral_f60fe2d9d123295bf92ecf95167f1fa709e374da35e4c083bd39dc2d82acd8b1", 631 | "typeString": "literal_string \"This function is restricted to the contract's owner\"" 632 | } 633 | ], 634 | "overloadedDeclarations": [ 635 | 1063, 636 | 1064 637 | ], 638 | "referencedDeclaration": 1064, 639 | "type": "function (bool,string memory) pure", 640 | "value": "require" 641 | }, 642 | "id": 788, 643 | "name": "Identifier", 644 | "src": "196:7:2" 645 | }, 646 | { 647 | "attributes": { 648 | "argumentTypes": null, 649 | "commonType": { 650 | "typeIdentifier": "t_address", 651 | "typeString": "address" 652 | }, 653 | "isConstant": false, 654 | "isLValue": false, 655 | "isPure": false, 656 | "lValueRequested": false, 657 | "operator": "==", 658 | "type": "bool" 659 | }, 660 | "children": [ 661 | { 662 | "attributes": { 663 | "argumentTypes": null, 664 | "isConstant": false, 665 | "isLValue": false, 666 | "isPure": false, 667 | "lValueRequested": false, 668 | "member_name": "sender", 669 | "referencedDeclaration": null, 670 | "type": "address payable" 671 | }, 672 | "children": [ 673 | { 674 | "attributes": { 675 | "argumentTypes": null, 676 | "overloadedDeclarations": [ 677 | null 678 | ], 679 | "referencedDeclaration": 1060, 680 | "type": "msg", 681 | "value": "msg" 682 | }, 683 | "id": 789, 684 | "name": "Identifier", 685 | "src": "211:3:2" 686 | } 687 | ], 688 | "id": 790, 689 | "name": "MemberAccess", 690 | "src": "211:10:2" 691 | }, 692 | { 693 | "attributes": { 694 | "argumentTypes": null, 695 | "overloadedDeclarations": [ 696 | null 697 | ], 698 | "referencedDeclaration": 784, 699 | "type": "address", 700 | "value": "owner" 701 | }, 702 | "id": 791, 703 | "name": "Identifier", 704 | "src": "225:5:2" 705 | } 706 | ], 707 | "id": 792, 708 | "name": "BinaryOperation", 709 | "src": "211:19:2" 710 | }, 711 | { 712 | "attributes": { 713 | "argumentTypes": null, 714 | "hexvalue": "546869732066756e6374696f6e206973207265737472696374656420746f2074686520636f6e74726163742773206f776e6572", 715 | "isConstant": false, 716 | "isLValue": false, 717 | "isPure": true, 718 | "lValueRequested": false, 719 | "subdenomination": null, 720 | "token": "string", 721 | "type": "literal_string \"This function is restricted to the contract's owner\"", 722 | "value": "This function is restricted to the contract's owner" 723 | }, 724 | "id": 793, 725 | "name": "Literal", 726 | "src": "238:53:2" 727 | } 728 | ], 729 | "id": 794, 730 | "name": "FunctionCall", 731 | "src": "196:101:2" 732 | } 733 | ], 734 | "id": 795, 735 | "name": "ExpressionStatement", 736 | "src": "196:101:2" 737 | }, 738 | { 739 | "id": 796, 740 | "name": "PlaceholderStatement", 741 | "src": "303:1:2" 742 | } 743 | ], 744 | "id": 797, 745 | "name": "Block", 746 | "src": "190:119:2" 747 | } 748 | ], 749 | "id": 798, 750 | "name": "ModifierDefinition", 751 | "src": "168:141:2" 752 | }, 753 | { 754 | "attributes": { 755 | "documentation": null, 756 | "implemented": true, 757 | "isConstructor": false, 758 | "kind": "function", 759 | "name": "setCompleted", 760 | "scope": 811, 761 | "stateMutability": "nonpayable", 762 | "superFunction": null, 763 | "visibility": "public" 764 | }, 765 | "children": [ 766 | { 767 | "children": [ 768 | { 769 | "attributes": { 770 | "constant": false, 771 | "name": "completed", 772 | "scope": 810, 773 | "stateVariable": false, 774 | "storageLocation": "default", 775 | "type": "uint256", 776 | "value": null, 777 | "visibility": "internal" 778 | }, 779 | "children": [ 780 | { 781 | "attributes": { 782 | "name": "uint", 783 | "type": "uint256" 784 | }, 785 | "id": 799, 786 | "name": "ElementaryTypeName", 787 | "src": "335:4:2" 788 | } 789 | ], 790 | "id": 800, 791 | "name": "VariableDeclaration", 792 | "src": "335:14:2" 793 | } 794 | ], 795 | "id": 801, 796 | "name": "ParameterList", 797 | "src": "334:16:2" 798 | }, 799 | { 800 | "attributes": { 801 | "parameters": [ 802 | null 803 | ] 804 | }, 805 | "children": [], 806 | "id": 804, 807 | "name": "ParameterList", 808 | "src": "369:0:2" 809 | }, 810 | { 811 | "attributes": { 812 | "arguments": null 813 | }, 814 | "children": [ 815 | { 816 | "attributes": { 817 | "argumentTypes": null, 818 | "overloadedDeclarations": [ 819 | null 820 | ], 821 | "referencedDeclaration": 798, 822 | "type": "modifier ()", 823 | "value": "restricted" 824 | }, 825 | "id": 802, 826 | "name": "Identifier", 827 | "src": "358:10:2" 828 | } 829 | ], 830 | "id": 803, 831 | "name": "ModifierInvocation", 832 | "src": "358:10:2" 833 | }, 834 | { 835 | "children": [ 836 | { 837 | "children": [ 838 | { 839 | "attributes": { 840 | "argumentTypes": null, 841 | "isConstant": false, 842 | "isLValue": false, 843 | "isPure": false, 844 | "lValueRequested": false, 845 | "operator": "=", 846 | "type": "uint256" 847 | }, 848 | "children": [ 849 | { 850 | "attributes": { 851 | "argumentTypes": null, 852 | "overloadedDeclarations": [ 853 | null 854 | ], 855 | "referencedDeclaration": 786, 856 | "type": "uint256", 857 | "value": "last_completed_migration" 858 | }, 859 | "id": 805, 860 | "name": "Identifier", 861 | "src": "375:24:2" 862 | }, 863 | { 864 | "attributes": { 865 | "argumentTypes": null, 866 | "overloadedDeclarations": [ 867 | null 868 | ], 869 | "referencedDeclaration": 800, 870 | "type": "uint256", 871 | "value": "completed" 872 | }, 873 | "id": 806, 874 | "name": "Identifier", 875 | "src": "402:9:2" 876 | } 877 | ], 878 | "id": 807, 879 | "name": "Assignment", 880 | "src": "375:36:2" 881 | } 882 | ], 883 | "id": 808, 884 | "name": "ExpressionStatement", 885 | "src": "375:36:2" 886 | } 887 | ], 888 | "id": 809, 889 | "name": "Block", 890 | "src": "369:47:2" 891 | } 892 | ], 893 | "id": 810, 894 | "name": "FunctionDefinition", 895 | "src": "313:103:2" 896 | } 897 | ], 898 | "id": 811, 899 | "name": "ContractDefinition", 900 | "src": "66:352:2" 901 | } 902 | ], 903 | "id": 812, 904 | "name": "SourceUnit", 905 | "src": "32:387:2" 906 | }, 907 | "compiler": { 908 | "name": "solc", 909 | "version": "0.5.0+commit.1d4f565a.Emscripten.clang" 910 | }, 911 | "networks": { 912 | "3": { 913 | "events": {}, 914 | "links": {}, 915 | "address": "0xF61c82B549FF5B6E32298E51E6FC763acD592C37", 916 | "transactionHash": "0x3ee54b1edcd9a95138c9f0b4d6d6b8aec1c519bc7cbd7288b89d71c6a761c2a2" 917 | } 918 | }, 919 | "schemaVersion": "3.4.6", 920 | "updatedAt": "2022-07-12T04:34:28.235Z", 921 | "networkType": "ethereum", 922 | "devdoc": { 923 | "methods": {} 924 | }, 925 | "userdoc": { 926 | "methods": {} 927 | } 928 | } --------------------------------------------------------------------------------