├── 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 | 
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 |
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 | | Time |
33 | {/* tokenAmount */}
34 | DAPP |
35 | {/* tokenPrice */}
36 | DAPP / ETH |
37 |
38 |
39 | {this.props.filledOrdersLoaded ? (
40 | showFilledOrders(this.props.filledOrders)
41 | ) : (
42 |
43 | )}
44 |
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 | | Time |
67 | DAPP |
68 | DAPP / ETH |
69 |
70 |
71 | {this.props.myFilledOrderLoaded ? (
72 | showMyFilledOrders(this.props.myFilledOrder)
73 | ) : (
74 |
75 | )}
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | | Amount |
84 | DAPP / ETH |
85 | Cancel |
86 |
87 |
88 | {this.props.showMyOpenOrders ? (
89 | showMyOpenOrders(this.props)
90 | ) : (
91 |
92 | )}
93 |
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 |
81 |
82 |
83 |
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 | | Token |
63 | Wallet |
64 | Exchange |
65 |
66 |
67 |
68 |
69 | | ETH |
70 | {etherBalance} |
71 | {exchangeEtherBalance} |
72 |
73 |
74 |
75 |
99 |
100 |
101 |
102 |
103 | | DAPP |
104 | {tokenBalance} |
105 | {exchangeTokenBalance} |
106 |
107 |
108 |
109 |
140 |
141 |
142 |
143 |
144 |
145 | | Token |
146 | Wallet |
147 | Exchange |
148 |
149 |
150 |
151 |
152 | | ETH |
153 | {etherBalance} |
154 | {exchangeEtherBalance} |
155 |
156 |
157 |
158 |
188 |
189 |
190 |
191 |
192 | | DAPP |
193 | {tokenBalance} |
194 | {exchangeTokenBalance} |
195 |
196 |
197 |
198 |
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 | }
--------------------------------------------------------------------------------