智能合约(英语:Smart contract )是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易。这些交易可追踪且不可逆转。智能合约概念于1994年由Nick Szabo首次提出
区块链与传统合同的区别
智能合约可以替代传统合同吗?
智能合约可以做什么事情?
薪酬管理系统
setEmployeeBonus(employee,years,title,contribute,duration)
// [{name:’liuzongyuan’,amount:10000}]
queryBonus(employee)
withdrawBonus(amount)
自动转账
智能合约适合做什么事情?
对不可篡改性有要求的事(比如承诺)
数字资产操作(奖金发放)
溯源相关(因为操作都有记录)
赌博(f3d p3d)
...
智能合约的特点
有哪些限制?
我们的合约跟以太坊有什么区别?
ethereum | gxc | |
---|---|---|
语言 | solidity | c++ |
手续费是否固定 | 是 | 否 |
前端与智能合约的交互?
jsonrpc websocket
合约代码展示
疑问
序列化为什么是前端来做?
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
Type:改动类型
* feat (feature)
* fix (bug fix)
* docs (documentation)
* style (formatting, missing semi colons, …)
* refactor
* test (when adding missing tests)
* chore (maintain)
Scope:改动的地方
Subject:
* 用祈使语气: “change” ,而不是 “changed” 或 “changes”
* 首字母不大写
* 最后不要加.号
Body:
* 用祈使语气: “change” ,而不是 “changed” 或 “changes”
* 如果是改动,写明改动的动机以及和之前的区别
Footer:
1.Breaking changes
所有的Breaking changes都必须在footer声明改动点,改动的原因和迁移指南
BREAKING CHANGE: isolate scope bindings definition has changed and
the inject option for the directive controller injection was removed.
To migrate the code follow the example below:
Before:
scope: {
myAttr: 'attribute',
myBind: 'bind',
myExpression: 'expression',
myEval: 'evaluate',
myAccessor: 'accessor'
}
After:
scope: {
myAttr: '@',
myBind: '@',
myExpression: '&',
// myEval - usually not useful, but in cases where the expression is assignable, you can use '='
myAccessor: '=' // in directive's template change myAccessor() to myAccessor
}
The removed `inject` wasn't generaly useful for directives so there should be no code using it.
2.Referencing issues
关闭的issue应该放到这里
Closes #123, #245, #992
├── .babelrc
├── .electron-vue
│ ├── build.js // 构建客户端的脚本
│ ├── dev-client.js
│ ├── dev-runner.js // npm run dev的script
│ ├── webpack.main.config.js // 主进程配置
│ ├── webpack.renderer.config.js // 渲染进程配置
│ └── webpack.web.config.js
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── README.md
├── appveyor.yml
├── package.json
├── postcss.config.js
├── src
│ ├── index.ejs
│ ├── main // 主进程
│ │ ├── index.dev.js
│ │ ├── index.js // 主进程入口
│ │ └── util
│ └── renderer // 渲染进程
│ ├── App.vue // 入口组件
│ ├── assets // 图片、样式、字体等资源
│ ├── components // vue组件
│ ├── const // 常量(常量及公用的校验rule)
│ ├── filters // vue 过滤器
│ ├── locales // i18n
│ ├── main.js // 入口文件
│ ├── plugins // vue 插件
│ ├── router // vue router
│ ├── services // 与公信链或者服务端交互的逻辑
│ ├── store // vuex
│ ├── template // 工程模板,应该迁移到static
│ └── util
├── static // 放变动较少的文件
│ └── .gitkeep
├── test
│ ├── .eslintrc
│ ├── e2e
│ │ ├── index.js
│ │ ├── specs
│ │ └── utils.js
│ └── unit
│ ├── data
│ ├── index.js
│ ├── karma.conf.js
│ └── specs
├── yarn-error.log
└── yarn.lock
脚手架:electron-vue
mvvm框架:vue
组件库:iview,最近升级到3.0了
打包工具:webpack
演示
// item,单项数据
{
title: 'file name',
id: 'file ID',
content: 'file content',
isDirectory: true,
expand: false, // whether expanded
children: [] // only directory has children
}
// 存放文件目录对应的数据,是一颗树
files: {
id: 1,
isRoot: true,
isDirectory: true,
title: 'root',
children: [item, item, item]
}
// 存放code panel对应的数据,是一个数组
openedFiles: [item, item, item]
vuex modules
return function(store) {
const savedState = shvl.get(options, 'getState', getState)(key, storage);
if (typeof savedState === 'object' && savedState !== null) {
// 用本地的数据替换当前store的数据
store.replaceState(merge(store.state, savedState, {
arrayMerge: options.arrayMerger || function (store, saved) { return saved },
clone: false,
}));
}
// 在每个mutation操作时,都会执行该回调
(options.subscriber || subscriber)(store)(function(mutation, state) {
if ((options.filter || filter)(mutation)) {
// 将当前store存到本地
(options.setState || setState)(
key,
(options.reducer || reducer)(state, options.paths || []),
storage
);
}
});
};
function setState(key, state, storage) {
return storage.setItem(key, JSON.stringify(state));
}
首先需要扩展
{
title: 'file name',
...
render: function(h){
return h('div',{...},[])
}
}
其次需要deep clone
computed: {
data() {
var data = this.filterData(cloneDeep(this.files.children))
return data
}
}
对数据进行操作,反馈到视图上
directoryMenu.append(new MenuItem({
label: this.$t('files.addFile'),
click: () => {
this.appendFile({target: data})
}
}))
// action
appendFile({commit}, payload) {
commit('APPEND_FILE', payload)
}
// mutation
APPEND_FILE(state, {target, opts = {}}) {
let tempNode = new TreeModel()
tempNode = tempNode.parse(util.formatFile(opts))
if (target.isDirectory) {
filesTreeModel.first(idEq(target.id)).addChild(tempNode)
} else {
filesTreeModel.first(idEq(target.id)).parent.addChild(tempNode)
}
}
watch: {
data: {
deep: true,
handler () {
this.stateTree = this.data;
this.flatState = this.compileFlatState();
this.rebuildTree();
}
}
}
引入tree model,在mutation中使用,简化树操作
// good example, no need for deep clone
// init
afterInit(store) {
filesTreeModel = new TreeModel()
filesTreeModel = filesTreeModel.parse(store.state.ContractFiles.files)
}
// action
appendFile({commit}, payload) {
commit('APPEND_FILE', payload)
}
// mutation
APPEND_FILE(state, {target, opts = {}}) {
let tempNode = new TreeModel()
tempNode = tempNode.parse(util.formatFile(opts))
if (target.isDirectory) {
filesTreeModel.first(idEq(target.id)).addChild(tempNode)
} else {
filesTreeModel.first(idEq(target.id)).parent.addChild(tempNode)
}
}
// bad example, use tree model on action
// init tree model
afterInit(store) {
filesTreeModel = new TreeModel()
// 必须克隆,因为model引用store,否则在action中修改tree model会报错
filesTreeModel = filesTreeModel.parse(cloneDeep(store.state.ContractFiles.files))
}
// action
appendFile({commit}, {target, opts = {}}) {
let tempNode = new TreeModel()
tempNode = tempNode.parse(util.formatFile(opts))
if (target.isDirectory) {
filesTreeModel.first(idEq(target.id)).addChild(tempNode)
} else {
filesTreeModel.first(idEq(target.id)).parent.addChild(tempNode)
}
commit('REFRESH_FILES')
}
// mutation
REFRESH_FILES(state) {
// 必须克隆,否则model会被绑定,在action中修改tree model会报错
state.files = cloneDeep(filesTreeModel.model)
}
不要直接对state进行操作
// correct
// modules
export default {
namespaced: true,
state,
mutations,
actions,
getters,
util,
afterInit(store) {
filesTreeModel = new TreeModel()
filesTreeModel = filesTreeModel.parse(store.state.ContractFiles.files)
}
}
// vue store entry
const store = new Vuex.Store({
state,
mutations,
getters,
actions,
plugins: [createPersistedState()],
modules,
strict: process.env.NODE_ENV !== 'production'
})
for (let key in modules) {
modules[key].afterInit && modules[key].afterInit(store)
}
// wrong
const state = {
files: []
}
let filesTreeModel = new TreeModel()
// 这里的state只是一个pure object,不会绑定数据,而且这里也没法拿到最终的数据
filesTreeModel = filesTreeModel.parse(cloneDeep(state.files))
codepanel中修改代码,记得同时修改files
changeFileContent({commit, dispatch}, {target, content} = {}) {
// 修改opened files中的content
commit('CHANGE_CURRENT_OPENED_FILE_CONTENT', {node: target, content})
// 修改files中的content
dispatch('changeFileStatus', {node: target, opts: {content}})
}
交互演示
希望每个模板工程,就是一个文件夹
// https://webpack.js.org/guides/dependency-management/
// 这是webpack提供的能力,会把文件目录单独打包成一个模块,支持打包子目录
const helloTpl = require.context('@/template/hello', true, /\.ejs$/)
helloTpl.keys().map(key => {
return {
title: filter(key),
content: helloTpl(key)
}
})
希望在运行时根据传入的参数进行渲染
const helloTpl = require.context('@/template/hello', true, /\.ejs$/)
helloTpl.keys().map(key => {
return {
title: filter(key),
// 这里每个content拿到的并不是plain text,而是编译好等待渲染的模板
content: helloTpl(key)
}
})
// 渲染
content({renderOpts})
// webpack,ejs-loader并没有用ejs官方的engine,而是用loadsh的template方法作为engine
rules: [
{ test: /\.ejs$/, loader: 'ejs-loader' }
]
选择模板
编译
渲染
添加到文件树
=>
=>
=>
const metaFiles = require.context('@/template', true, /meta\.js$/)
// 主要是生成文件树所需要的结构,目前gxcpp暂时不支持嵌套结构的编译,
// 所以最终生成的children中是平级的结构
util.compile = function (directory) {
let files = tplMap[`${directory}Tpl`]
const reg = /^\.\/(.+)\.ejs$/
files = files.keys().map(key => {
return {
title: reg.exec(key)[1],
content: files(key)
}
})
const ret = {
title: directory,
children: files
}
ret.render = util.render.bind(util, ret)
return ret
}
// 基于编译生成的数据结构,去渲染content,然后返回文件树所需的数据结构
util.render = function (project, opts = {}) {
opts.entry = opts.entry || metaMap[project.title].entry
if (opts.title) {
project.title = opts.title
}
project.children = project.children.map(file => {
// every child file render
file.content = file.content(opts)
return file
})
delete project.render
return project
}
// action
addProject({commit}, project) {
commit('ADD_PROJECT', project)
}
// mutation
ADD_PROJECT(state, project) {
let tempNode = new TreeModel()
tempNode = tempNode.parse(util.formatFiles(project))
filesTreeModel.addChild(tempNode)
}
演示
部署
表单渲染
表单数据收集
数据处理
=>
=>
=>
数据序列化
调用
=>
=>
const deploy_contract = ({from = '', contractName = '', code = '',
abi = '', fee_id = '', password = '', broadcast = true}) => {
...
return new Promise((resolve, reject) => {
// 1.解锁钱包,本质上是根据password和账户信息,经过一系列密码学运算得到私钥
resolve(Promise.all([fetch_account(from),
unlock_wallet(from, password)]).then(results => {
...
// 2.构造交易,会去
let tr = new TransactionBuilder()
tr.add_operation(tr.get_type_operation('create_contract', {
'fee': {
'amount': 0,
'asset_id': fee_id
},
'name': contractName,
'account': fromAcc.id,
vm_type,
vm_version,
code,
abi
}))
// 3.处理交易
return process_transaction(tr, from, password, broadcast).then((resp) => {
// ... handle resp
return resp
})
}))
})
}
const process_transaction = (tr, account, password, broadcast) => {
let walletInfo = null
return new Promise((resolve, reject) => {
// 1.解锁钱包
resolve(unlock_wallet(account, password).then(info => {
walletInfo = info
// 2.设置手续费
return Promise.all([tr.update_head_block(),
tr.set_required_fees()]).then(() => {
// 3.用私钥签名
tr.add_signer(PrivateKey.fromWif(walletInfo.wifKey))
if (broadcast) {
// 4.广播交易
return tr.broadcast()
} else {
return tr
}
})
}))
})
}
deploy_contract({
from: this.currentWallet.account,
fee_id: this.tempAsset.id,
password: this.tempPwd,
broadcast: true,
abi: this.abi,
contractName: this.contractName,
code: this.bytecode
}).then((resp) => {
...
// 将部署过的合约保存在本地
this.appendContract(resp[0].ext)
this.$store.dispatch('updateCurrentBalancesAndAssets')
})
return process_transaction(tr, from, password, broadcast).then((resp) => {
return fetch_account(contractName).then((account) => {
resp[0].ext = {
abi,
from,
contractName,
contractId: account.id,
fee: resp[0].trx.operations[0][1].fee
}
return resp
})
})
deploy_contract({
...
}).then((resp) => {
...
// 将部署过的合约保存在本地
this.appendContract(resp[0].ext)
this.$store.dispatch('updateCurrentBalancesAndAssets')
})
部署
表单渲染
表单数据收集
数据处理
=>
=>
=>
数据序列化
调用
=>
=>
computed: {
...mapState('ContractOperation', {
contracts: state => {
return contractsFilter(cloneDeep(state.contracts))
}
})
}
function contractFilter(contract) {
contract.functions = contract.abi.actions.map((action) => {
var struct = contract.abi.structs.find((struct) => {
return struct.name === action.name
})
return {
...struct,
...action
}
})
return contract
}
<div class="contract" v-for="contract in contracts">
…
<function-card v-for="f in contract.functions" :payable="f.payable"
:abi="contract.abi" :contractName="contract.contractName"
:name="f.name" :fields="f.fields"></function-card>
</div>
部署
表单渲染
表单数据收集
数据处理
=>
=>
=>
数据序列化
调用
=>
=>
部署
表单渲染
表单数据收集
数据处理
=>
=>
=>
数据序列化
调用
=>
=>
/* functionCard.vue */
// 也是层层向上,构建类似这样一个数组,[{name:'xx', type:'xx', value: xx}, ...]
var fields = this.$refs.fields.getFields()
params = await fieldUtil.formatFields2Params(fields)
/* fieldUtil.js */
util.formatFields2Params = async function (fields) {
var ret = {}
for (const field of fields) {
if (util.isArrayType(field.type)) { // 比如asset[]
ret[field.name] = await Promise.all(field.value.map(val => {
return util.formatField(util.getSingleType(field.type), val)
}))
} else {
ret[field.name] = await util.formatField(field.type, field.value)
}
}
return ret
}
util.formatField = async function (type, value) {
let handler = handlers[type]
if (!handler) {
handler = handlers.defaultHandler
}
value = await handler(value)
return value
}
部署
表单渲染
表单数据收集
数据处理
=>
=>
=>
数据序列化
调用
=>
=>
// 上一步得到的处理后的参数数据
params = await fieldUtil.formatFields2Params(fields)
// 序列化参数
data = serializer.serializeCallData(this.name, params, this.abi).toString('hex')
const serializeCallData = (action, params, abi) => {
abi = cloneDeep(abi)
let struct = abi.structs.find(s => s.name === action)
let b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN)
struct.fields.forEach(f => {
let value = params[f.name]
let isArrayFlag = false
if (isArrayType(f.type)) {
isArrayFlag = true
f.type = f.type.split('[')[0]
}
let type = types[f.type]
if (!type) {
let t = abi.types.find(t => t.new_type_name === f.type)
if (t) {
type = types[t.type]
}
}
if (!type) {
type = ops[f.type]
}
if (type) {
if (isArrayFlag) {
type = types.set(type)
}
type.appendByteBuffer(b, value)
}
})
return Buffer.from(b.copy(0, b.offset).toBinary(), 'binary')
}
部署
表单渲染
表单数据收集
数据处理
=>
=>
=>
数据序列化
调用
=>
=>
/* FunctionCard.vue */
call_contract(this.currentWallet.account, this.contractName, {
'method_name': this.name,
'data': data
}, this.tempAsset.id, this.tempPwd, true, this.amount)
/* WalletService.js */
const call_contract = (from, target, act, fee_id, password, broadcast = true, amount = {}) => {
return new Promise((resolve, reject) => {
resolve(Promise.all([fetch_account(from), fetch_account(target),
unlock_wallet(from, password)]).then(results => {
...
let tr = new TransactionBuilder()
let opts = {
'fee': {
'amount': 0,
'asset_id': fee_id
},
'account': fromAcc.id,
'contract_id': contractAccount.id,
'method_name': act.method_name,
'data': act.data
}
...
tr.add_operation(tr.get_type_operation('call_contract', opts))
return process_transaction(tr, from, password, broadcast)
}))
})
}
循环依赖
serializer
iview modal
render
sprites
icons生成
Keep alive
event bus
info panel
gxb打赏账户:jaredliu233