使用智能合约实现自动分账
哈哈笑417
发表于 2023-1-4 14:36:20
310
0
0
文中代码已在 GitHub 上开源。https://github.com/fengluo/fibos-subaccount
设计思路" g" ^, E& L6 D+ [5 J5 C
3 j. [. X% k) v- b6 w' D7 H5 w
在 FIBOS 转账是通过 token 合约的extransfer方法来实现的。extransfer方法在执行的时候会给转账方账户和入账方账户发送通知。所以用户给平台方账户转账的时候,平台账户就会收到通知。所以整体业务逻辑如下:+ Y9 o r, S( n- t7 M
' X' z. c8 i5 |" p5 c
quantity: 10 FO; Q) \% ]# F7 |
memo: 内容提供者账户 quantity: 8 FO) h1 B* v, F) q7 D& R
7 l1 ]( _; M$ W" o; B1 b
用户账户 -------------------> 平台账户 ----------------> 内容提供者账户
$ G& M) ~! A# k6 J" K0 {0 [1 Z
extransfer 2/8 分成 extransfer
1.用户给平台方账户转账,memo 中填写内容提供者的账户名。! O+ V( h# E( \" M% h% f
& ^! j' ?: x: o# u
2.平台方的账户合约监听 extransfer 方法的通知,然后做出分账计算,给对应内容提供者的账户转账对应金额。* h% u, L7 g I. Q
/ [8 q+ K4 w* ^- N" @$ d
整体逻辑很简单,整个合约代码逻辑差不多用20行就可以写完。
编写合约3 L& x7 w# O Z- Z9 _
FIBOS 的智能合约分为 ABI 文件和 JS 合约两部分。ABI 相当于合约接口,JS 合约则是功能实现。本案例目前没有接口设计需求,不过 ABI 文件还是合约不可缺少的部分。所以我们简单创建一下就好。 r; R( z! u! ^6 S( G7 R
; m- t* h) G- g# A
我们先创建一个 contracts 文件夹,合约文件都会放在这里。然后在此文件夹下,创建 subaccount.abi 文件,内容为:* {' g' t5 x+ t2 h& ~4 H
) B% n8 S! w1 }8 P" g$ l' D p
{
"version": "eosio::abi/1.0"7 J( I' f; l( d, f' I4 o
* U7 i) H' e( r! w \( v/ I/ d" H
}
JS 合约部分也没有太复杂。在 contracts 文件夹下创建 subaccount.js 文件,代码为:' }! w: L; B5 ]0 |0 W+ }3 p* ^
; x( E+ f; ^" @4 y
exports.on_extransfer = (from, to, quantity, memo) => {
- w3 I3 A- ]8 e
// 需要在开头做一些判断: |' K1 l& }) L" a6 u& s& E3 j
if (to === action.receiver && action.is_account(memo)) {' Q- U) ^2 U1 _' E! \
, ?& Y# S! e* C Y( c3 Q: P. ~& ~
const num = parseInt(quantity.quantity.split(' ')[0])
// 假设我们约定平台方跟内容提供者是2/8分成。. `0 }1 L; E& H, \
const subnum = (num * 0.8).toFixed(4);
trans.send_inline('eosio.token', 'extransfer', {
1 N. q! ^& ~9 M
from: to,
) B+ d1 a5 A! f" v3 Y
to: memo,
- ^; E" R @: O2 |# S b7 O
quantity: {) b5 Z3 L$ U1 u1 E- |9 H
quantity: `${subnum} ${quantity.quantity.split(' ')[1]}`,+ N3 m- I, \0 x5 _
contract: quantity.contract
},1 _" t, W& m7 p# ?5 }! u( D7 P' v
0 I# H8 r0 ~; k& Z/ `
memo: 'sub account'
' X& A2 w" [1 Y" K$ ~
},
" @2 e! c; q5 z5 a
[# f6 k6 R+ M+ T2 z8 J% ~
{
0 F' @# p0 B- \$ K/ ]! d
// 需要提供合约账户的 active 权限
actor: action.receiver, Y2 B9 c/ F2 I6 B6 I V8 m
, X& \% n* G! o) h$ E5 m+ ^
permission: 'active'7 n$ I' j( d! h9 @0 l J
}
]);
$ n5 N; ?1 _; X& r% Q4 z, U: ]
}
}
* f9 W- j& o5 O$ I2 I1 O
合约代码开头我们需要做一些验证。
( L& U* o. y. K
1.收款方的账户为合约账户,否则因为下面代码执行给内容提供者转账时,因为转帐方也是合约账号会再次收到通知,造成无限递归,超出最大 send_inline 层数而报错。
2.我们用 memo 参数来放内容提供者的账户,所以我们需要对此参数校验一下该账户是否存在防止打错。: F9 U: R' M. ]: X; e0 [
0 h0 }2 a; @. ]( s% Y+ R
合约代码中我们使用 send_inline 调用 eosio.token 合约来执行转帐操作。转帐操作需要对应账户的 active 权限才能执行。为了解决权限滥用问题,FIBOS 定义了一个特殊权限 eosio.code。我们需要在平台合约账户中配置权限,在 active 权限下添加该合约账户的 eosio.code 授权。具体的配置操作会在下面说明。
% c: ~$ @# Q* G0 d J) g! X
在 FIBOS TestNet 上注册账号9 [" R: F) ]$ W4 k
为方便测试,我们在测试网 http://testnet.fibos.fo 上注册三个账户。1 ?4 c; P% B# F* a; C* H' R6 d' k; m
5 v) T- ~ a8 i' ]7 I+ T2 {7 K7 t
用户账号 helloworld11$ e( b& Q" b3 ^* e
2 ?4 G: y1 b) a* y
内容提供者账号 helloworld22
平台合约账号 helloworld33
8 u- [- Q( n6 q; r& k
我们需要记录这三个账号的账户名以及公私钥。以便下面的开发使用。创建一个统一的配置文件来记录这些数据:
const config = {% N8 ]9 u( p1 [7 H4 S
// 平台合约账户的客户端配置
: f) O' C: l) c: x1 @+ _% \( n
client: {
/ p: h/ ?( H( O* M" ?
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',
httpEndpoint: 'http://testnet.fibos.fo',2 { d) r& l8 T; A( g+ G
% Y, @3 ]: j7 \: e- f2 q8 }) Z: @
keyProvider: 'PRIVATE_KEY_OF_helloworld33'3 Q0 L) ?# q1 ~
2 j4 c( m0 c+ v
},
. c; }/ a* F/ b9 V" ]) M2 K7 T# y( W
// 用户账户的客户端配置4 D/ h7 S; A% i% L/ i8 h2 W
callClient:{/ ~- k" M9 _# P' t, ]' ]3 N! w
) Z) U9 |4 h7 Z
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',
# [% {1 t3 Z# U: C: \
httpEndpoint: 'http://testnet.fibos.fo',9 ?) q4 W; K f0 I
$ P+ S/ d& z0 v5 |' ]
keyProvider: 'PRIVATE_KEY_OF_helloworld11'
},. K7 M) F: _8 v$ C5 f A3 c
( A2 [: f B, V: d/ K# A l
// 平台合约账户信息; [) v- W1 {) G
contractAccount: {
name: 'helloworld33',2 f# Z: z$ u" w
+ p$ O' n" F+ q. M9 E! ?8 E
publicKey: 'PUBLIC_KEY_OF_helloworld33',
privateKey: 'PRIVATE_KEY_OF_helloworld33'4 O) r/ v' F9 v% T
},
// 用户账户信息! ]. ?; n n/ V5 {0 L/ u
account1: {7 K4 Z7 P5 s8 G/ c1 m+ Z
name: 'helloworld11',+ _. d. a# b5 V
) f! o% o& ?. ?2 z$ ?) r/ S
publicKey: 'PUBLIC_KEY_OF_helloworld11',. _/ m3 B3 V8 ~, r4 G
% A- x$ W# T/ O5 k/ F9 u
privateKey: 'PRIVATE_KEY_OF_helloworld11'
},
// 内容提供者账户信息1 {+ ^. \/ }# M7 s/ O3 j8 Q
account2: {
$ k% S7 d. I+ O- x
name: 'helloworld22',! @- I) G4 z0 [5 w+ j; X
publicKey: 'PUBLIC_KEY_OF_helloworld22',' N' j. Z z: O8 T( e1 ]# q! n- B) x
0 k% U4 ~/ h/ m; F& a: m; Z5 n
privateKey: 'PRIVATE_KEY_OF_helloworld22'
) p0 j1 P9 a- `$ c
}- M& Y3 D+ U- i8 L ?2 k" _$ `
1 o0 ]# u2 b& L% \9 p
}1 G! k6 ?/ G% h0 @& w& B! S
module.exports = config
+ p8 F6 `* i1 L
配置权限
% O5 A. k+ b1 X9 r, q
在合约代码中,我们调用了 trans.send_inline 函数调用合约 eosio.token 来实现转帐操作,但是转帐操作是需要账户的 active 权限。所以我们需要更新一下合约账户的权限,需要添加调用者的 eosio.code 授权到它的 active 权限。这个调用者自然也是这个合约账户。
/ L! B/ i/ C1 \" D0 d- E5 U
const FIBOS = require('fibos.js');6 ]/ I- N4 {/ {, J4 @, U. m! u
const config = require('./config');
const fibosClient = FIBOS(config.client);
let ctx = fibosClient.contractSync('eosio');5 c$ |# `8 a) U: }- S- ? n
var r = ctx.updateauthSync({
account: config.contractAccount.name,
permission: 'active',
6 g% L2 H% j% i
parent: 'owner',
# g/ Z1 c9 C; y1 J1 B) h$ o k u
auth: {
+ m- M/ e8 B7 |
threshold: 1,
keys: [{+ P2 L; _# W/ i5 A5 q A8 ~' y
2 \. i) k& w3 T# Y8 d/ g
key: config.contractAccount.publicKey,9 D5 `1 V1 Z/ t1 ^
weight: 1, m6 l2 F: U! _/ {# o2 D
. [! T2 t. V0 D0 |
}],3 h1 H+ T5 P6 x" I+ k* d K3 A
' `5 |4 I' c ^4 L, I+ y# j2 W
accounts: [{
6 M0 z3 [; I' x7 M7 W- ^& A
permission: {0 l& p, Z0 R+ Q& y( A
- K) T& v$ z" I- Q4 r
// 将调用者账号的 eosio.code 授权添加到它的 active 权限下。
actor: config.contractAccount.name,3 g3 N$ K9 F+ P3 \( `( a! u4 K
permission: 'eosio.code' L8 D( K+ o9 T
2 ~+ K k! b2 e/ F
},& \- x! @( M& q5 p/ Y
/ H) @6 r" _# R* l% F
weight: 1* ^) v( R8 U0 V* @3 [5 G$ o
0 j! m+ W; G( Z
}]' d8 x# h. D. F
}
},{
h5 U4 \' T6 \9 ~' V
authorization: `${config.contractAccount.name}@owner` //更改账户权限需要使用 owner 权限% |6 c0 J8 s5 o! z. {; e% B# V
});
9 F' d2 @5 {! @ X" n- j' a1 i9 L' |& p
console.log(r);, f2 M% q+ Y* v: S- P
' y1 s2 v# ]* k- R6 _1 d; m
部署合约
const FIBOS = require('fibos.js');
const config = require('./config');: y! ~3 g0 b/ R- d
const fibosClient = FIBOS(config.client);7 }! @; D- y" i( L3 U, r
$ K* J7 d' F8 Z! G
const fs = require('fs');
& ^, |; X6 ~; g! k( ~# V% z$ c3 z m
// setcode
const jsCode = fs.readTextFile(`${__dirname}/contracts/subaccount.js`);
fibosClient.setcodeSync(config.contractAccount.name, 0, 0, fibosClient.compileCode(jsCode));
1 r( o' F4 d" M1 D
// getcode
const code = fibosClient.getCodeSync(config.contractAccount.name, true);8 i+ y2 m7 V2 ]: k' c) q; _: k$ @# O
: K7 a. J, |6 m( C0 q* P
console.log('code:', code);. a- R/ u' d: B1 V& m3 m* y
// setabi* Y1 Y9 ~' c3 z3 K
9 p5 o6 ]( w1 t9 Q7 B5 T E/ L
const abi = JSON.parse(fs.readTextFile(`${__dirname}/contracts/subaccount.abi`));& i6 h; H" s& z* E8 Y7 I
9 J1 }$ ] W9 h7 V5 J1 U y6 n
fibosClient.setabiSync(config.contractAccount.name, abi);# W. w3 X& c' A% @
转账测试
% ^9 f3 y( i8 L1 Q/ v
我们先来写一个脚本 account.js 来查看三个账户的余额。
, y" e- [6 {0 L) d2 c% C0 ]
const FIBOS = require('fibos.js');
const config = require('./config');
const fibosClient = FIBOS(config.callClient);
const account1 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account1.name, 'accounts');
console.log(config.account1.name);
* w# D. ^: b; {+ l& ~' g; i0 i/ [
console.log(account1);
const account2 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account2.name, 'accounts');
console.log(config.account2.name);
% b$ C; S) w z% J. b: E/ H# ?1 m
console.log(account2);) y" o* v) @, k2 x/ ~
$ N# I* K3 |) j& a" X* N
const contractAccount = fibosClient.getTableRowsSync(true, 'eosio.token', config.contractAccount.name, 'accounts'); ]% [9 J+ K! ~! V0 T
console.log(config.contractAccount.name);
% ?1 f" s2 ~( L( F' [& S
console.log(contractAccount);; w" ?& _* ~; t4 f8 A; E8 O
执行 fibos account.js 来查看三个账户信息。 目前我们的账户还没有 FO,所以大致情况是这样的:: S# \' n- H/ o; f) o
, N+ y0 M# U9 J. ^
用户账户:helloworld11 金额:0.0000 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO
& m, H2 q) ^7 C2 ~2 C
测试网会自动给每个账户发放10 EOS 的通证用以测试使用。账户中还并没有 FO 通证。所以我们再来写一个兑换脚本,用1 EOS 换一点 FO 通证。% R2 ?. U2 b( |5 Q% @" U4 w
const FIBOS = require('fibos.js');( k2 A3 s9 p! ?
6 D: \( n8 W) }1 N
const config = require('./config');
const fibosClient = FIBOS(config.callClient);% q1 S# M+ v5 ^3 H
5 b! |7 x& h) B) F! n
let ctx = fibosClient.contractSync('eosio.token');2 s5 d( C3 b1 u
; q* z7 E I# |/ E4 y$ C7 F
const r = ctx.exchangeSync(
config.account1.name,
. N6 }$ S8 ]5 J o: I! X2 x4 t# C" n' Y
'1.0000 EOS@eosio',, o: Z, K4 [3 F; ^* R/ l
% s y4 k; W0 m4 z' {/ @6 |1 s" w
'0.0000 FO@eosio',, Q% f5 K/ N: z1 l6 f( K0 v
'exchange FO to EOS',
{
authorization: config.account1.name% K2 k' C; j. O2 H
- X. ~. {9 F0 c7 j, i. o' f2 z& x
}" {# E. M- P9 B# R
);! m) B, u& |. v( N
console.log(r)
0 s- q* t6 [; r. u3 ?1 s; j
再次执行 fibos account.js 来查看账户信息。目前我们的账户金额大致是这样的:
用户账户:helloworld11 金额:146.4245 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO, A; x1 f4 `$ L, S, d5 n0 [5 K( a* Q; E
下面写个脚本 transfer.js 来执行转帐操作。- B2 w$ A/ o# e6 c3 `
const FIBOS = require('fibos.js');
0 o" z! i5 {# ~5 R+ _6 y; b. o( K
const config = require('./config');% v9 _& l9 E! p( ^
( B9 a8 s9 c! p- c; ?
const fibosClient = FIBOS(config.callClient);
0 X8 W" Q, |- ?
let ctx = fibosClient.contractSync('eosio.token');
2 e4 t+ c8 J% J- c9 h# Z, C' s
const r = ctx.extransferSync(1 Y9 J, ~$ O. {# G4 Y9 m
( B& e# j1 x7 W( J6 y% |9 Q
config.account1.name, // 用户账户7 [3 |9 c- f: J9 r }
config.contractAccount.name, // 平台合约账户
'10.0000 FO@eosio', // 转帐金额
config.account2.name, // 附言填写内容提供者的账户名,平台合约会给它分账6 |, u& M" N. f8 o! ]# P
{. D& [: C: v0 @; q: F- Q# J O
authorization: config.account1.name //提供用户账户的授权7 J" w/ X" Y8 Q
4 [0 P% Z) Q$ j6 _% O/ n
}
* N% H( {; y% u% `3 w& L* X
)
console.log(r)4 Y$ d; p# E: K: U
! W) v7 h, H/ k$ _
我们要从用户账户 account1 给平台合约账户 account3 转帐 10 FO。memo 参数为要分成的内容提供者账户 account2。根据合约中定的2/8分成,平台合约账户 account3 将会分得2 FO,而内容提供者账户 account2 将会获得8 FO。" ` \9 L8 M. J( [
: q4 G2 ^. t9 G3 a
使用命令 fibos transfer.js 执行该脚本完成转帐操作。, r2 M' w% Q0 V
& Y: t2 T2 G# ?# n) L3 S' O- V
下面我们再来看一下目前三个账户情况。执行命令 fibos account.js。三个账户金额大致如下。* w$ v7 O! X% x: j5 u0 C1 K
# `+ R% n q$ o4 y
用户账户:helloworld11 金额:136.4245 FO内容提供者账户:helloworld22 金额:8.0000 FO平台合约账户:helloworld33 金额:2.0000 FO! X1 p& ~' w& [; v0 B3 k* O& R
6 l' t: W; h; H6 X8 q
结果显示,分账账户和平台合约账户如预期那样获得8 FO 和2 FO。
7 l$ j+ {+ T: a2 c. A8 l [& r
) e; X5 c, O. Q+ `! `
综上,我们成功使用了智能合约实现了自动分账。平台方还可以继续根据自己业务需要定制自己的合约。) e; |1 |* i. h9 U2 x# Q" q
文中的代码请参考:https://github.com/fengluo/fibos-subaccount
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人