使用智能合约实现自动分账
哈哈笑417
发表于 2023-1-4 14:36:20
309
0
0
文中代码已在 GitHub 上开源。https://github.com/fengluo/fibos-subaccount: }2 v! C9 U& x$ j$ K B) ~
设计思路
- J& ^& A- O6 T' m1 [( H
在 FIBOS 转账是通过 token 合约的extransfer方法来实现的。extransfer方法在执行的时候会给转账方账户和入账方账户发送通知。所以用户给平台方账户转账的时候,平台账户就会收到通知。所以整体业务逻辑如下:$ ^) s0 ?9 D/ ]
" D2 X* q% N9 D; E2 P' n& l) Z" N: `' L% Y) u
quantity: 10 FO
: M6 D5 M8 A' S
memo: 内容提供者账户 quantity: 8 FO
用户账户 -------------------> 平台账户 ----------------> 内容提供者账户
, d( C: c: u' H6 X, w
extransfer 2/8 分成 extransfer
4 Q1 c/ k6 N0 |4 U/ a" S0 H) F
1.用户给平台方账户转账,memo 中填写内容提供者的账户名。
2.平台方的账户合约监听 extransfer 方法的通知,然后做出分账计算,给对应内容提供者的账户转账对应金额。
# x* ?, ^6 I' W( o+ N/ L
整体逻辑很简单,整个合约代码逻辑差不多用20行就可以写完。
编写合约
0 L N2 Q6 w( F% ]* s7 o
FIBOS 的智能合约分为 ABI 文件和 JS 合约两部分。ABI 相当于合约接口,JS 合约则是功能实现。本案例目前没有接口设计需求,不过 ABI 文件还是合约不可缺少的部分。所以我们简单创建一下就好。
% b1 j, @6 ~0 d* V$ t1 i
我们先创建一个 contracts 文件夹,合约文件都会放在这里。然后在此文件夹下,创建 subaccount.abi 文件,内容为:
# T( C$ F W5 l- _
{
3 V1 Q+ F! N+ e/ R$ Z
"version": "eosio::abi/1.0". s3 J( B) Q3 V
}" _2 a/ N) e" W6 i% g; a }
8 W$ F. F* l1 n, l
JS 合约部分也没有太复杂。在 contracts 文件夹下创建 subaccount.js 文件,代码为:& I- O" y, F0 ], O& o8 U$ n; L
% ]; u @9 Q/ O6 S5 K1 N$ f# }. m0 T
exports.on_extransfer = (from, to, quantity, memo) => {
// 需要在开头做一些判断
if (to === action.receiver && action.is_account(memo)) {
const num = parseInt(quantity.quantity.split(' ')[0])# W5 d* A: R/ ^$ d1 G) |; D: t% \
// 假设我们约定平台方跟内容提供者是2/8分成。7 O3 E, {: u: k0 j) P
+ B# N% D, h# e. D6 b9 z1 H: s
const subnum = (num * 0.8).toFixed(4);
9 c, f1 S% x* R. B
trans.send_inline('eosio.token', 'extransfer', {/ h2 m2 ?. q0 }9 c8 J
- u4 u2 |9 g$ g# |+ o3 T7 G/ Q
from: to,
to: memo,
9 x+ B) F' {0 S/ r% a& J6 [; o
quantity: {/ z' } F* |2 s7 U8 v
" T5 Z3 \) ~& m9 Y5 X( @
quantity: `${subnum} ${quantity.quantity.split(' ')[1]}`,* u$ X b+ z2 P# v7 m( S5 M
contract: quantity.contract
* r4 r. c% l) x3 C3 |
},
5 \9 f* u8 g7 e/ V9 g" x S+ ~* |6 V
memo: 'sub account'9 H+ T: ~' R! T& ~& E# x
7 A6 `( _7 @4 K6 R2 \
},
[8 T# Z% W' j( \" e: _ g% ^4 E
{
// 需要提供合约账户的 active 权限/ |1 h! {) }; M0 F- t
4 d3 ~! }$ Z' i/ w+ S% {% h) P. P
actor: action.receiver,2 ]6 M* f* Q! [. I8 \
permission: 'active'
. @/ _0 R* N1 E* Q4 ?; l, i: l
}
1 ~. k+ N: W& a2 j' D
]);
0 s, k' V- o& t0 W
}
& k# D! ^/ W1 j
}6 P9 Z8 h- l. P# Z
合约代码开头我们需要做一些验证。* T, J3 H* f" l
1.收款方的账户为合约账户,否则因为下面代码执行给内容提供者转账时,因为转帐方也是合约账号会再次收到通知,造成无限递归,超出最大 send_inline 层数而报错。
2.我们用 memo 参数来放内容提供者的账户,所以我们需要对此参数校验一下该账户是否存在防止打错。
; [* X; ?! V% l+ F" ^6 _" ?
合约代码中我们使用 send_inline 调用 eosio.token 合约来执行转帐操作。转帐操作需要对应账户的 active 权限才能执行。为了解决权限滥用问题,FIBOS 定义了一个特殊权限 eosio.code。我们需要在平台合约账户中配置权限,在 active 权限下添加该合约账户的 eosio.code 授权。具体的配置操作会在下面说明。/ ?3 O* j2 {3 P2 D2 C
5 A" P" l+ L% D% ~
在 FIBOS TestNet 上注册账号
3 B0 R1 i- k. \
为方便测试,我们在测试网 http://testnet.fibos.fo 上注册三个账户。
用户账号 helloworld11* I/ v0 S5 y# T& G) ?! z6 D4 n
! p1 N7 K) G- A" [2 l: z
内容提供者账号 helloworld22+ G4 i: M+ y5 k" ~7 Y! I/ N
$ [% J. w9 A. c; r& ?* B) b1 v
平台合约账号 helloworld33
我们需要记录这三个账号的账户名以及公私钥。以便下面的开发使用。创建一个统一的配置文件来记录这些数据:
const config = {
- O) E o2 |0 K& i* z! c& P: k6 u
// 平台合约账户的客户端配置
* K: J0 ^) C+ `$ C9 {
client: {
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a', m( z8 q0 D& p6 g/ S( H, |
' b* b5 P; W% |% a4 J
httpEndpoint: 'http://testnet.fibos.fo',# ]( U4 g) ^+ N8 ?
keyProvider: 'PRIVATE_KEY_OF_helloworld33': [( Y/ U0 e' ^/ [$ \
},2 I' w1 K) X. n' [/ g+ v3 R2 |) L
: F& ~' [" S$ W/ [& {
// 用户账户的客户端配置
callClient:{$ {/ `) R4 y4 U+ b4 ~1 {0 }
7 d. c$ t, R& y" |9 O+ s
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',
' E! Y, T! u7 l I! F
httpEndpoint: 'http://testnet.fibos.fo',
keyProvider: 'PRIVATE_KEY_OF_helloworld11'
: r4 P& K" D3 u5 D% h) b, k
},
// 平台合约账户信息( J6 n/ \4 T. n ^+ z# N1 k
contractAccount: {
# h, U. n& x" n+ N$ B& a/ _- q
name: 'helloworld33',0 F, [( h3 D- G( N
publicKey: 'PUBLIC_KEY_OF_helloworld33',) g6 D3 w8 F0 K$ }& E3 p1 o. E
privateKey: 'PRIVATE_KEY_OF_helloworld33'
},2 o1 r S& u' {) z; v+ p# b' s5 V
3 W W3 s7 [- L+ Z. |
// 用户账户信息
( B, H5 d' N, ~& f, g
account1: {8 Q' n4 k' `0 ]9 H$ m
name: 'helloworld11',
, P7 G2 V! N' p
publicKey: 'PUBLIC_KEY_OF_helloworld11',
privateKey: 'PRIVATE_KEY_OF_helloworld11'+ k1 K/ U/ r9 K, I( j O# V6 u
},
& t3 _3 z: j, G* R6 j
// 内容提供者账户信息, N2 o- y; G% R0 {( ?. v- W; g
account2: {& z, C2 |7 _8 h- g* w0 b
name: 'helloworld22',
/ R7 _! j- A9 v( N! u5 [% s
publicKey: 'PUBLIC_KEY_OF_helloworld22',. Q9 _, d- N5 l& s9 Y
& @" {1 X2 N, N# U# a
privateKey: 'PRIVATE_KEY_OF_helloworld22'& i( U$ V: e, u: C" {, r3 ?: C# A, G
" u9 o6 p3 j+ ` F$ z
}5 l% E* J' |$ w! |: X# X$ N) \ l* |, f
}
0 ~/ e% G7 m; |* R. Q
module.exports = config9 b$ F, X( `" ?% [7 R
配置权限7 v1 }: e. ]& x- I8 F1 y0 A
/ Y. L0 \$ Y+ I! W9 y
在合约代码中,我们调用了 trans.send_inline 函数调用合约 eosio.token 来实现转帐操作,但是转帐操作是需要账户的 active 权限。所以我们需要更新一下合约账户的权限,需要添加调用者的 eosio.code 授权到它的 active 权限。这个调用者自然也是这个合约账户。" h2 p7 Y: Q3 p3 Z. ^4 }6 b# O
7 l; \/ p2 W; ~5 m* O; N' l0 r4 k/ Q
const FIBOS = require('fibos.js');
const config = require('./config');
3 l' \7 O( @( k. h4 p
const fibosClient = FIBOS(config.client);
let ctx = fibosClient.contractSync('eosio');9 z, U9 ~! j" n% W6 m0 q
/ J/ ]+ S* Y+ y) |: ]4 Z
var r = ctx.updateauthSync({
account: config.contractAccount.name, \+ {. i7 Y/ o' k) r; N
; n+ h/ v V5 `( @
permission: 'active',
" ]: @' Y3 ~# n7 Z. e- X7 g
parent: 'owner',3 p" G9 ~; X; {
9 R% z& U4 ~2 n7 L `7 y5 ]) ~
auth: {, U9 K2 {2 Z/ ?5 _& N/ d0 `
- R- f1 u. h1 V, q3 k/ {( \' q. u* ?9 @
threshold: 1,: y# m/ L' K6 H
0 ?% F& t! O8 d4 p
keys: [{& Y$ H. c( y, F: }2 H) V* r+ P* U
key: config.contractAccount.publicKey,- j) F# c) U% S. ?% o$ f c/ g6 Y
$ W& U- |4 @6 y3 H( N7 h5 l
weight: 1
}],
accounts: [{& c& H1 W- \* v* _4 k# A$ g- n
permission: {
// 将调用者账号的 eosio.code 授权添加到它的 active 权限下。
& K3 f1 Q- X* E+ }8 T
actor: config.contractAccount.name,, G) H% _, t6 L/ M
permission: 'eosio.code'
p- j$ S3 K+ i% H' z5 Z3 t- L
},
weight: 1
}]
+ z4 s$ |6 h( ~7 f: ~# t
}
},{! N/ Y: L# ?/ A/ L
" T. x; V* U2 [3 U
authorization: `${config.contractAccount.name}@owner` //更改账户权限需要使用 owner 权限# S7 F- z6 }5 D6 b/ x
});- T) f" K: h/ @2 H0 N. Y3 Y+ [; i
4 `3 ~8 I7 v2 |2 S# x* G" I) l- x
console.log(r);
部署合约2 s: w" D+ q& n$ P: h% [; Y0 c
2 n X' I% i' A' D( g; u
const FIBOS = require('fibos.js');
const config = require('./config');4 ?* o `# H" b$ V
# y; Q0 D7 o8 V
const fibosClient = FIBOS(config.client);& O/ Z$ e+ J" ~( ?/ r$ d
) E5 W! l: p: T# w8 ]/ q4 W
const fs = require('fs');
6 V E1 r U: h( H
// setcode7 h; K8 X6 O, N9 k0 y
const jsCode = fs.readTextFile(`${__dirname}/contracts/subaccount.js`);
" l( D2 Y3 A+ U J- D' z* {
fibosClient.setcodeSync(config.contractAccount.name, 0, 0, fibosClient.compileCode(jsCode));
// getcode/ M2 y' e8 n% g! G+ d
const code = fibosClient.getCodeSync(config.contractAccount.name, true);4 N( o$ ]8 S6 p7 t a/ \
console.log('code:', code);
5 r! b! K$ i5 a
// setabi
const abi = JSON.parse(fs.readTextFile(`${__dirname}/contracts/subaccount.abi`));6 Y7 Z: Z: l; R
fibosClient.setabiSync(config.contractAccount.name, abi);
转账测试, p2 U. }! q( Y* \" n
( S3 p* O1 @5 d, f) h* o4 ]/ [' B. O1 ]
我们先来写一个脚本 account.js 来查看三个账户的余额。
const FIBOS = require('fibos.js');' p# U; t+ y! V6 W; n) ~" r1 _
const config = require('./config');# N# _& ` ^6 v% c1 Y
# e. W0 H2 ]& S+ n3 f' u& W
const fibosClient = FIBOS(config.callClient);
const account1 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account1.name, 'accounts');! U- w a) t5 Q$ ^" p- T% V+ l( Q1 `
0 G8 \8 |$ F7 {8 T( ~3 T% r( C
console.log(config.account1.name);% q3 c+ r5 W" q7 ~
: N7 F5 W( L7 x$ B
console.log(account1);
, b' b* O* ^: [/ R
const account2 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account2.name, 'accounts');
console.log(config.account2.name);
console.log(account2);5 p- @4 X0 {4 Q5 p" [& n
const contractAccount = fibosClient.getTableRowsSync(true, 'eosio.token', config.contractAccount.name, 'accounts');8 R' o3 m/ P; o
$ c0 l5 H2 w9 N9 C F! ?8 @2 o, V
console.log(config.contractAccount.name);
console.log(contractAccount);7 R: h% k% V3 U4 }
$ e- h Q( q6 G! d$ ~
执行 fibos account.js 来查看三个账户信息。 目前我们的账户还没有 FO,所以大致情况是这样的:
# q7 }2 L, U+ X! G2 f8 e4 g8 \( ~. J/ K
用户账户:helloworld11 金额:0.0000 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO
3 s/ M! d! a' s: v7 e( S( Y
测试网会自动给每个账户发放10 EOS 的通证用以测试使用。账户中还并没有 FO 通证。所以我们再来写一个兑换脚本,用1 EOS 换一点 FO 通证。, U; K; v4 y6 f; F6 c" _: C
1 X3 G. |- x6 T( x2 T+ E
const FIBOS = require('fibos.js');0 \7 \7 C! ^; T. G
; M% s6 C/ g% {* l2 s
const config = require('./config');6 G' g/ _8 b4 Z9 I0 |$ e: y& |
) J0 I! U+ p8 f; x7 s4 ~
const fibosClient = FIBOS(config.callClient);
let ctx = fibosClient.contractSync('eosio.token');" K7 j K* W5 O
const r = ctx.exchangeSync(
+ |4 v! K; K9 z9 k0 _
config.account1.name,
'1.0000 EOS@eosio'," V2 [0 Y& c& S3 o) ^% M
'0.0000 FO@eosio',
# {7 U0 d9 L: G9 P' G# W; ?9 @
'exchange FO to EOS',
{
/ `& l! E; c' N( l0 E
authorization: config.account1.name
& P1 \1 E& e. J% ]. O" A
}5 L' m, V, w* W) I. Y- ]
3 ^1 p/ h9 B( d7 T/ `: R# T! ?
);& Q, r6 B1 }' } J: R
console.log(r)
再次执行 fibos account.js 来查看账户信息。目前我们的账户金额大致是这样的:% x" `# S+ S+ V1 p- r9 X
) ]/ u, ?* E4 y# {% d# }
用户账户:helloworld11 金额:146.4245 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO5 n! ]% z) b! G5 g
下面写个脚本 transfer.js 来执行转帐操作。
0 V/ d2 m, W3 ^1 q4 E
const FIBOS = require('fibos.js');2 R, i0 b+ N0 X0 Q- }
const config = require('./config');
const fibosClient = FIBOS(config.callClient);: `8 U3 A( L; Y! r) y' O
! Z" I: m* [+ e/ Y; d
let ctx = fibosClient.contractSync('eosio.token'); ^+ s7 O5 o& i4 m
; j; ?# F" ]7 V% j
const r = ctx.extransferSync(' P$ K. q6 F* a0 t: g
config.account1.name, // 用户账户( L; L+ b+ a z {( H
config.contractAccount.name, // 平台合约账户
'10.0000 FO@eosio', // 转帐金额- P- `+ Q1 g! T) G9 ~- k0 w
config.account2.name, // 附言填写内容提供者的账户名,平台合约会给它分账
' M8 g* O4 T' r' U4 O3 Q
{+ P) K& |# C( P* Z
8 ~4 m1 p# [% B, Q& F) K
authorization: config.account1.name //提供用户账户的授权# [ i- K2 w0 X
}( c4 l5 x4 W! O$ z7 Q
)% m3 a3 R7 T+ c+ n1 z
! O3 H' W4 Y8 a1 w8 }* I
console.log(r)
( M5 H7 ~1 W( t" e8 L. z3 ?
我们要从用户账户 account1 给平台合约账户 account3 转帐 10 FO。memo 参数为要分成的内容提供者账户 account2。根据合约中定的2/8分成,平台合约账户 account3 将会分得2 FO,而内容提供者账户 account2 将会获得8 FO。
使用命令 fibos transfer.js 执行该脚本完成转帐操作。
- a2 t- J, a" F1 I
下面我们再来看一下目前三个账户情况。执行命令 fibos account.js。三个账户金额大致如下。
用户账户:helloworld11 金额:136.4245 FO内容提供者账户:helloworld22 金额:8.0000 FO平台合约账户:helloworld33 金额:2.0000 FO. o' @: O+ U" q: X8 u
结果显示,分账账户和平台合约账户如预期那样获得8 FO 和2 FO。
! l3 ?" @# f2 m+ a
综上,我们成功使用了智能合约实现了自动分账。平台方还可以继续根据自己业务需要定制自己的合约。
' {* i$ w8 w, v
文中的代码请参考:https://github.com/fengluo/fibos-subaccount
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人