使用智能合约实现自动分账
哈哈笑417
发表于 2023-1-4 14:36:20
328
0
0
文中代码已在 GitHub 上开源。https://github.com/fengluo/fibos-subaccount
_+ @8 V4 `; X) R5 n
设计思路
在 FIBOS 转账是通过 token 合约的extransfer方法来实现的。extransfer方法在执行的时候会给转账方账户和入账方账户发送通知。所以用户给平台方账户转账的时候,平台账户就会收到通知。所以整体业务逻辑如下:
quantity: 10 FO' G0 _- C3 i! ^$ s+ ~
memo: 内容提供者账户 quantity: 8 FO
用户账户 -------------------> 平台账户 ----------------> 内容提供者账户1 n9 b i: w! U+ }3 Z4 D: l7 n. E0 O4 F
. A* J7 {% P* ]- b
extransfer 2/8 分成 extransfer
1.用户给平台方账户转账,memo 中填写内容提供者的账户名。
2.平台方的账户合约监听 extransfer 方法的通知,然后做出分账计算,给对应内容提供者的账户转账对应金额。* ~# V1 G) Z& a, |( ]0 |, ?9 g
( v9 y0 V# z- H
整体逻辑很简单,整个合约代码逻辑差不多用20行就可以写完。
: K8 ~- y6 w( j4 W" S/ `2 Z% Y
编写合约/ A% |/ i0 Y* m" {2 A
FIBOS 的智能合约分为 ABI 文件和 JS 合约两部分。ABI 相当于合约接口,JS 合约则是功能实现。本案例目前没有接口设计需求,不过 ABI 文件还是合约不可缺少的部分。所以我们简单创建一下就好。( K* J7 B; ?. p% P* N3 s1 R0 t
' k, Z4 c) z% M3 R5 d# G, M
我们先创建一个 contracts 文件夹,合约文件都会放在这里。然后在此文件夹下,创建 subaccount.abi 文件,内容为:
{
% r) P @1 w7 G5 ?3 A6 g
"version": "eosio::abi/1.0"
0 s, l& j' o( @& Z: L) K) L
} n6 p0 o6 @! [1 O6 y
JS 合约部分也没有太复杂。在 contracts 文件夹下创建 subaccount.js 文件,代码为:. d. F* k) h& Z. \, O$ C' P+ M
exports.on_extransfer = (from, to, quantity, memo) => {
3 g* _4 j2 h( ^2 J8 K# _! k& b% c
// 需要在开头做一些判断
9 E B4 j6 c. B
if (to === action.receiver && action.is_account(memo)) {+ r8 H s: u2 N( ?, _: x
const num = parseInt(quantity.quantity.split(' ')[0])1 |! c- W3 g! i
7 i, [0 Z9 A7 i
// 假设我们约定平台方跟内容提供者是2/8分成。
! l4 R, e- Q( v* o4 v( h! ~7 a
const subnum = (num * 0.8).toFixed(4);4 R8 q- X7 ?& V! E& l
/ C q& y' H- B9 j7 P: r ]
trans.send_inline('eosio.token', 'extransfer', {" ^& |5 I2 X1 f
6 x) `3 I. H. w( |7 I% Y% Y7 e' q
from: to,& h$ Z; t! I2 V- J2 Z/ C6 K: y
; V8 _0 n4 f. N7 D2 B: M0 }( R
to: memo,. o# r; W& {" [/ V7 @
quantity: {& Z8 `8 c* e9 d4 q$ I u: C
quantity: `${subnum} ${quantity.quantity.split(' ')[1]}`,
) q' a$ W; f% Y* m
contract: quantity.contract7 T, K! L+ E* ]
},
memo: 'sub account'$ L$ t) Q( F/ x* s4 K% {3 i! j4 z
},) L8 T+ Y/ j9 w# W! |
[
/ p' @0 r3 }" K; |! P
{
// 需要提供合约账户的 active 权限4 k/ r! A* ]% H' Z* Y
& `7 x' e) \- S: w2 d) l' s% L
actor: action.receiver,# C3 W: t7 E; j4 D# S1 o! e
permission: 'active'/ _% o# }5 Q- Y; t' B. @" Q2 m3 V
# g/ Y- @3 O. j
}4 h5 {8 t$ e4 q- A% v) K% u) q
2 L+ ^$ n! b0 T, j
]);6 [2 O2 V8 D6 A. M
% l6 G" }: [; \: Q8 @! U6 \! g
}- p R- Q& S' p0 ?5 F. U+ Q
}
合约代码开头我们需要做一些验证。
0 X! Z$ Q3 f7 W, E
1.收款方的账户为合约账户,否则因为下面代码执行给内容提供者转账时,因为转帐方也是合约账号会再次收到通知,造成无限递归,超出最大 send_inline 层数而报错。( I7 V1 w) U! C0 F, c" `( m+ e
* ?4 h* F# `+ m# ]. ?3 h
2.我们用 memo 参数来放内容提供者的账户,所以我们需要对此参数校验一下该账户是否存在防止打错。8 t# I1 g- ?$ r3 j6 ^5 c
' X( P! C' U' s1 h. h: v7 [
合约代码中我们使用 send_inline 调用 eosio.token 合约来执行转帐操作。转帐操作需要对应账户的 active 权限才能执行。为了解决权限滥用问题,FIBOS 定义了一个特殊权限 eosio.code。我们需要在平台合约账户中配置权限,在 active 权限下添加该合约账户的 eosio.code 授权。具体的配置操作会在下面说明。4 N3 V2 O5 _ f. [2 U0 B$ }: n
在 FIBOS TestNet 上注册账号
* K3 L! O# _- ~; G3 N
为方便测试,我们在测试网 http://testnet.fibos.fo 上注册三个账户。: T( ~4 s5 K& a- Z$ e
用户账号 helloworld119 v1 a1 C4 J- b: U/ i _' Q
内容提供者账号 helloworld22
平台合约账号 helloworld33
我们需要记录这三个账号的账户名以及公私钥。以便下面的开发使用。创建一个统一的配置文件来记录这些数据:: m/ ?3 k* c* w0 F! V2 Q
0 ?% g" Q1 i# M. a$ S2 c
const config = {
// 平台合约账户的客户端配置
E' P2 \- [; o* H7 l
client: {# f* E7 \% t; S( `& R& r
% p8 q1 k1 f% S0 ^6 E/ Z' e/ F7 P$ ^
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',
5 F) b7 E* J& {+ x; h
httpEndpoint: 'http://testnet.fibos.fo',) G) i4 T1 ]" k; S9 B) X/ E
keyProvider: 'PRIVATE_KEY_OF_helloworld33'" b! w$ S. I5 U' n/ V7 W1 a* S+ [4 P( C
},
// 用户账户的客户端配置% ~" d9 W, J1 f! [4 | i
/ h) Z& w# C. {/ [* Q
callClient:{! |- A6 c1 e) o2 E8 z6 \. M+ ?
5 `5 M6 Z3 c! U( b) R* A
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',+ P+ Z- v3 o0 M+ K
httpEndpoint: 'http://testnet.fibos.fo',+ @3 a! ~0 E1 M! X) B" k
* P2 |9 `- M1 G( Q# D
keyProvider: 'PRIVATE_KEY_OF_helloworld11'
& L+ R% d( q6 G- J# j$ y
},! e" H! a7 e2 `& i! @6 \
// 平台合约账户信息
contractAccount: {4 H+ H$ k$ N/ B
name: 'helloworld33',
( C" \& I, j& ?! e7 a
publicKey: 'PUBLIC_KEY_OF_helloworld33',3 O) h3 J5 j8 L! u
* y! F5 c; e) W s* j* v' e
privateKey: 'PRIVATE_KEY_OF_helloworld33', U- h- M& S2 _: v. c6 Y
},
- S6 ~" m; I: K/ w+ J) z* r5 z% _
// 用户账户信息
account1: {) i4 D% `6 i7 F3 v( V
name: 'helloworld11',; q1 P2 f3 r$ P! X
5 ~: ]1 u3 d& \ Q' B
publicKey: 'PUBLIC_KEY_OF_helloworld11',
privateKey: 'PRIVATE_KEY_OF_helloworld11'. J& p1 q! g% N( q
},
; u* I8 E& ^& K# I. N$ k* }
// 内容提供者账户信息
account2: {
name: 'helloworld22',
b, e( ?7 i" Y. i" C# n
publicKey: 'PUBLIC_KEY_OF_helloworld22',) U7 {; A( T" }4 S
- G# a5 q1 b# G# y! G
privateKey: 'PRIVATE_KEY_OF_helloworld22'
}
}
/ S2 \, Z9 b( _8 n. t1 w
module.exports = config! r. E: H5 }( w0 S3 s2 O6 t
" n2 @0 V+ U5 K+ o: _4 `0 @
配置权限
- Y$ i% X) [5 k, N) ]3 H
在合约代码中,我们调用了 trans.send_inline 函数调用合约 eosio.token 来实现转帐操作,但是转帐操作是需要账户的 active 权限。所以我们需要更新一下合约账户的权限,需要添加调用者的 eosio.code 授权到它的 active 权限。这个调用者自然也是这个合约账户。
& w& }; H7 k$ W' \1 i
const FIBOS = require('fibos.js');+ ~7 N' u2 v+ q/ T" P
8 M. g: G* C+ x$ R* {+ g- M" s
const config = require('./config');
/ o- h1 x+ f8 C# M0 ?7 G
const fibosClient = FIBOS(config.client);
' ~& G) O& K. w9 e. t
let ctx = fibosClient.contractSync('eosio');4 ?8 `# _4 Y; H8 c4 D
var r = ctx.updateauthSync({2 H; ?( Q4 T2 P0 q% n
) t9 i3 @0 r3 Y" c; a5 `* V
account: config.contractAccount.name,8 B& k5 C4 B w7 j, V! U3 M1 k
( M/ x' A7 u+ U4 v$ `3 @* \
permission: 'active',3 g6 {0 `6 r a/ h" v
parent: 'owner',
1 v) B, a9 j" E: c; N7 M) Z0 F+ G5 P$ P
auth: {
0 n. m3 m% `5 S2 |3 i1 G; w4 c
threshold: 1,
9 @8 x" _9 C& J0 k" D6 j
keys: [{/ j5 u$ G" ?# Y/ K5 A+ A
3 o/ r6 ? r1 T B: R
key: config.contractAccount.publicKey,
- o, `" ~# ~; N0 O$ [7 {, I3 T
weight: 1: J' x* E3 @8 `, f, e. G
}],; w ^+ b, E4 Z. L
accounts: [{* l" | v i6 V5 D
permission: {
// 将调用者账号的 eosio.code 授权添加到它的 active 权限下。
actor: config.contractAccount.name,
) r) ^: M6 D+ y2 O
permission: 'eosio.code'
2 t" _5 z9 ?- p
},
2 Z/ a h3 |) q; H: r/ @
weight: 1
! V$ M& v* x; k
}]
+ u3 v3 M/ B* V5 v& I6 G6 }& t
}; K S4 B* ]$ R Z9 z6 N
" o8 u0 O; N" d* Y! h
},{
& N' S* i1 h2 R& P
authorization: `${config.contractAccount.name}@owner` //更改账户权限需要使用 owner 权限
});+ p# a* V' I/ D7 y( u, e
# m$ p3 _' Y2 W) e* K# Q
console.log(r);1 p2 \$ b- T* ?0 b. j# Q* p
3 O3 x* s) @& o1 ]' k7 E
部署合约3 G- e1 x7 [$ w4 z8 v
V9 k8 F& T' B g5 j* `4 Z, y
const FIBOS = require('fibos.js');: N5 E J" N% \7 G$ o% D
const config = require('./config');
6 G9 `2 A" t; i% ^
const fibosClient = FIBOS(config.client);
+ w% B; @3 ]( H! C/ S; f
const fs = require('fs');
9 k2 v$ x) Q3 s
// setcode
/ t( x! c: U, p& y1 C
const jsCode = fs.readTextFile(`${__dirname}/contracts/subaccount.js`);0 e* ^. N# F& w
; b4 ^9 G* q2 y0 R
fibosClient.setcodeSync(config.contractAccount.name, 0, 0, fibosClient.compileCode(jsCode));
// getcode
const code = fibosClient.getCodeSync(config.contractAccount.name, true);
console.log('code:', code);* Q, b. C2 Q! }9 A0 r7 ], D
6 u) H8 s, {/ \ n* o* f( [8 u
// setabi
const abi = JSON.parse(fs.readTextFile(`${__dirname}/contracts/subaccount.abi`));9 x& e& \& H4 D- |
) O6 u9 Q W+ z( ]
fibosClient.setabiSync(config.contractAccount.name, abi);
3 \$ F3 G6 H) E2 C! a% @
转账测试
2 W: @/ Q- |- b, N: ?# q
我们先来写一个脚本 account.js 来查看三个账户的余额。, y+ y, @ `7 ?
const FIBOS = require('fibos.js');
const config = require('./config');
const fibosClient = FIBOS(config.callClient);
7 R3 p& w! P* K. A1 X/ u
const account1 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account1.name, 'accounts');
4 x4 l; `9 P$ B# S' M4 L# R8 l
console.log(config.account1.name);9 b- R( G9 z+ v) S+ P3 D$ Z
( t4 J: I% L/ t
console.log(account1);
const account2 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account2.name, 'accounts');
* Q. u9 r T0 i3 {- x% F1 F
console.log(config.account2.name); u: P A" G+ N6 ? W1 i1 n1 g$ q
8 W& k. ]- ~% c( {
console.log(account2);
( L% M; R- N J- y0 Z
const contractAccount = fibosClient.getTableRowsSync(true, 'eosio.token', config.contractAccount.name, 'accounts');
2 e. Q: T3 ]0 C6 ^2 p* m9 G
console.log(config.contractAccount.name);
console.log(contractAccount);- t7 }5 }7 E* g! O/ b- ]2 m) x
执行 fibos account.js 来查看三个账户信息。 目前我们的账户还没有 FO,所以大致情况是这样的:
用户账户:helloworld11 金额:0.0000 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO1 k) T. D+ Y; [, d* a- n
' b |9 b' g/ m; G3 e% H; P( [
9 {! y5 Y4 u5 p) R0 M
( z$ |& y" v* G8 O
测试网会自动给每个账户发放10 EOS 的通证用以测试使用。账户中还并没有 FO 通证。所以我们再来写一个兑换脚本,用1 EOS 换一点 FO 通证。1 |# z3 w) X. u5 B
4 D/ j5 O& r C4 c0 q* R
const FIBOS = require('fibos.js');: s) l, Y- G; W: ^0 v0 m* I
. h4 U c! K. I, G# r
const config = require('./config');
+ v- ~9 `1 e. d; t5 {; M
const fibosClient = FIBOS(config.callClient);
let ctx = fibosClient.contractSync('eosio.token');
const r = ctx.exchangeSync(- U# s9 X/ ?0 f2 J. `
: N: k7 B N) J
config.account1.name, W& ^* |/ c' F4 B9 k( C! {% ^
'1.0000 EOS@eosio',
/ e* @ I6 \# l u% e
'0.0000 FO@eosio',
2 ?# y9 o9 e2 N9 c9 q u4 B' t
'exchange FO to EOS',
6 H3 C7 d' H+ j' u
{( o( v5 C& x: e! ?6 n! p( C
authorization: config.account1.name
0 M7 ?, C# @5 V7 y
}
3 K- n9 A* y0 r0 ]) @/ \7 ?
);' R+ M& v/ k9 `! M; z# z
/ w# W! C( Y/ E" J4 ^( ^# B9 R
console.log(r)' O' x/ P$ @1 a' a
再次执行 fibos account.js 来查看账户信息。目前我们的账户金额大致是这样的:" l2 S- `# D& R" Y1 |0 a
' p! W7 m6 r1 B! M7 h4 D
用户账户:helloworld11 金额:146.4245 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO
6 ]) j7 {. r" ^
下面写个脚本 transfer.js 来执行转帐操作。
( {9 Y# s4 Q K/ N+ f1 `/ `
const FIBOS = require('fibos.js');/ u5 _( D; ]* |% y! y. n
const config = require('./config');
0 w; J* `" s$ P( f
const fibosClient = FIBOS(config.callClient);6 r- i+ E0 ~( u# D# q8 C4 B1 a
# f! E4 K( F4 M$ n8 M, C# Y6 Y
let ctx = fibosClient.contractSync('eosio.token');
const r = ctx.extransferSync(! C7 ]3 }' M3 p6 n1 b4 T) h
config.account1.name, // 用户账户2 E1 h' c+ Z8 K# y( c: H/ M
config.contractAccount.name, // 平台合约账户( k+ b" k5 S8 l' N
'10.0000 FO@eosio', // 转帐金额
: P C% _$ ~2 Q/ ] [
config.account2.name, // 附言填写内容提供者的账户名,平台合约会给它分账1 D2 p& b1 s; N3 P$ Q: \6 E/ M
' q- [. N& m1 [: l
{
1 V; P& h2 p# e w1 y, X( v# l# f& @
authorization: config.account1.name //提供用户账户的授权! ]/ P4 {: } i8 ] M8 W2 B0 q! B
3 v2 k. X6 y. u1 M; l- q
}3 B1 p5 v- m' D& M6 Y0 l' ~0 v: t
)
7 x; m0 _+ I0 i8 K
console.log(r)
! ?: V3 z3 H4 U7 o! v1 R5 {, r
我们要从用户账户 account1 给平台合约账户 account3 转帐 10 FO。memo 参数为要分成的内容提供者账户 account2。根据合约中定的2/8分成,平台合约账户 account3 将会分得2 FO,而内容提供者账户 account2 将会获得8 FO。5 R: D8 W& g2 U: @. t4 I& Q
使用命令 fibos transfer.js 执行该脚本完成转帐操作。9 C, Q" P' ~# e9 |' t
下面我们再来看一下目前三个账户情况。执行命令 fibos account.js。三个账户金额大致如下。( {+ ]/ @, m2 _. Q, _7 @6 J
4 R$ l$ |9 @" @
用户账户:helloworld11 金额:136.4245 FO内容提供者账户:helloworld22 金额:8.0000 FO平台合约账户:helloworld33 金额:2.0000 FO
+ \! m4 F* D- I$ l; w$ B+ K
结果显示,分账账户和平台合约账户如预期那样获得8 FO 和2 FO。
3 ?; q0 [3 r2 A$ p4 f
7 \# e$ b1 w8 j9 y% X
综上,我们成功使用了智能合约实现了自动分账。平台方还可以继续根据自己业务需要定制自己的合约。, `# x' T- |8 g8 z! b
文中的代码请参考:https://github.com/fengluo/fibos-subaccount
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人