使用智能合约实现自动分账
哈哈笑417
发表于 2023-1-4 14:36:20
118
0
0
/ |* J6 K/ T! }/ R& i% J. k
文中代码已在 GitHub 上开源。https://github.com/fengluo/fibos-subaccount: M6 o5 ?5 ^/ D3 D y/ z/ l
设计思路$ @9 J& T$ m% w; }& }+ F
( ^9 K5 L B; p$ S
在 FIBOS 转账是通过 token 合约的extransfer方法来实现的。extransfer方法在执行的时候会给转账方账户和入账方账户发送通知。所以用户给平台方账户转账的时候,平台账户就会收到通知。所以整体业务逻辑如下:
# u) l: }1 c8 `! `! v
quantity: 10 FO* G3 |, p1 `! a$ u; x4 l
memo: 内容提供者账户 quantity: 8 FO$ {, b3 K- q, o+ k" q5 y
用户账户 -------------------> 平台账户 ----------------> 内容提供者账户( }5 O* B; g+ [$ V
extransfer 2/8 分成 extransfer4 b; w+ \# w; K4 h; a
& l6 ?" P; T$ h1 m
1.用户给平台方账户转账,memo 中填写内容提供者的账户名。
2.平台方的账户合约监听 extransfer 方法的通知,然后做出分账计算,给对应内容提供者的账户转账对应金额。
+ A5 ?/ _$ I3 O5 y" x
整体逻辑很简单,整个合约代码逻辑差不多用20行就可以写完。
! r, p6 u7 s0 N: |
编写合约: l4 L+ N4 Q5 S% Z, S
FIBOS 的智能合约分为 ABI 文件和 JS 合约两部分。ABI 相当于合约接口,JS 合约则是功能实现。本案例目前没有接口设计需求,不过 ABI 文件还是合约不可缺少的部分。所以我们简单创建一下就好。
6 g) w# x. w1 X2 U1 M' ^0 a3 k% E
我们先创建一个 contracts 文件夹,合约文件都会放在这里。然后在此文件夹下,创建 subaccount.abi 文件,内容为:; g) _4 _; n* V0 U3 [* \+ Q3 ?" X1 z1 |
{% G! H* h& D; D. U* x
"version": "eosio::abi/1.0"
* p0 V ^# k9 I' Y0 y0 v: b. c
}, @- C5 S: {- {: b# }8 r
JS 合约部分也没有太复杂。在 contracts 文件夹下创建 subaccount.js 文件,代码为:7 ~/ ?( M+ R4 y2 K
8 r2 o5 g, W" D- d& O% W2 l# f
exports.on_extransfer = (from, to, quantity, memo) => {# B, H* ?5 ]# f" ^4 C( G
// 需要在开头做一些判断 j! {& @4 w. `! ?2 f# I
2 ^1 P/ N' s. X" ^+ \, S% {
if (to === action.receiver && action.is_account(memo)) { O7 M$ J/ _$ t8 b/ B, l' d" f
3 Y' C9 k- ?& H: P! J9 ^3 Y+ q) s3 H
const num = parseInt(quantity.quantity.split(' ')[0])' o/ U* k# Z# o8 }
// 假设我们约定平台方跟内容提供者是2/8分成。
const subnum = (num * 0.8).toFixed(4);4 R+ ?* G8 d8 r5 S
# i1 H) M3 x: M% G! C" a: Y# k8 s
trans.send_inline('eosio.token', 'extransfer', {
from: to,
) n8 G: k1 g* I) Y' U8 G
to: memo,
8 |+ ?) Y# W. v1 T$ T- R' b
quantity: {
quantity: `${subnum} ${quantity.quantity.split(' ')[1]}`,& [; B- \: u$ w5 H, G
Y! Q6 ?1 N2 F3 ~# \3 D
contract: quantity.contract9 @- @, ^" p/ Y! f: {* h
! M( h7 d! o' n0 M/ ?/ j
},& g* r/ R$ X9 S4 L7 [" l( R
memo: 'sub account'
},
1 f4 {8 d3 d# A1 s9 T
[
{+ `( g5 `& K" x- t2 ]" q
; T% a6 B1 |6 a, s c' [8 [1 \
// 需要提供合约账户的 active 权限
actor: action.receiver,
permission: 'active'3 C! t. K3 o- G; S2 Q) ? {
5 r2 _* X% i! u# N6 h2 J+ w
} k4 b- U* k5 `2 r- A4 G2 o" Q
]);4 d t# ]1 ^! _9 R N, K
}3 Q' Q& ^8 c$ n$ b5 |! F8 a* y6 U
$ R ^& Y0 J& M( Y, \$ s
}' p2 d4 K9 T- T: t4 Y2 C
合约代码开头我们需要做一些验证。1 L! {# h/ t6 _& Q
1.收款方的账户为合约账户,否则因为下面代码执行给内容提供者转账时,因为转帐方也是合约账号会再次收到通知,造成无限递归,超出最大 send_inline 层数而报错。; x! z* W) a8 N" X: `
/ G6 ~% a6 d6 f
2.我们用 memo 参数来放内容提供者的账户,所以我们需要对此参数校验一下该账户是否存在防止打错。
+ U& u' m, g5 b( e6 t
合约代码中我们使用 send_inline 调用 eosio.token 合约来执行转帐操作。转帐操作需要对应账户的 active 权限才能执行。为了解决权限滥用问题,FIBOS 定义了一个特殊权限 eosio.code。我们需要在平台合约账户中配置权限,在 active 权限下添加该合约账户的 eosio.code 授权。具体的配置操作会在下面说明。4 ?5 N8 b3 }4 L! N4 H
" T7 F7 `% Y) O
在 FIBOS TestNet 上注册账号
为方便测试,我们在测试网 http://testnet.fibos.fo 上注册三个账户。" \% e5 l: \- C7 b) _4 {
2 r2 a& ~# y4 Q* I
用户账号 helloworld111 h9 [4 m2 K: D" z" r
内容提供者账号 helloworld22* I6 n7 l0 u" |: |5 y
平台合约账号 helloworld331 B9 h! ]& }$ B9 s2 g/ q
: U1 m. j8 r1 O d& _0 N
我们需要记录这三个账号的账户名以及公私钥。以便下面的开发使用。创建一个统一的配置文件来记录这些数据:) M( r. n& d' L4 D2 D2 G, ?0 z
" w5 i/ F |% l+ x
const config = {8 f l* y5 G- q! S, }- |$ O
// 平台合约账户的客户端配置
( ]& o9 W& |$ `3 t
client: {9 T9 Z( |2 ?: J/ w( z0 N* B
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',* e8 R1 i' D, {; K6 ~. R
' U& B6 P$ J/ X$ ]9 x$ u% J
httpEndpoint: 'http://testnet.fibos.fo',, Y: v$ |* l! D+ `
keyProvider: 'PRIVATE_KEY_OF_helloworld33'$ S. B3 m6 y' h' p
) z v* C" F% T2 G+ s+ b) \
},
2 o! g( ?4 @0 P7 u; M
// 用户账户的客户端配置" L1 ?" B* Q1 v# h# q& Y
callClient:{
% k, F4 b- V* `
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',# ], N8 _; h6 r, |
httpEndpoint: 'http://testnet.fibos.fo',# l8 f! `4 {8 N- z6 z5 @4 p5 z. H% O# |
keyProvider: 'PRIVATE_KEY_OF_helloworld11'* u0 `: L# J, Z$ a" A
}," o/ J6 Y1 i, @2 y$ a' b
// 平台合约账户信息
contractAccount: {* d2 d3 X9 Z* m. f0 t' A: M0 r5 y
name: 'helloworld33',6 M M6 |8 o R/ P, k9 g7 z3 }
publicKey: 'PUBLIC_KEY_OF_helloworld33',# v: H" O. s, } e, O6 g
privateKey: 'PRIVATE_KEY_OF_helloworld33'
& f/ ?2 J9 V+ d( p, S) f: h: }
},
5 a* { Y q" p) G
// 用户账户信息
4 n- [* [' j% V$ j, T
account1: {
name: 'helloworld11',: V/ t$ j. R% i' y
. z! b n f+ M3 c. l
publicKey: 'PUBLIC_KEY_OF_helloworld11',# m9 x9 o* P7 @3 u2 P" j0 S% @6 ~5 |
: {9 T$ t* K2 c q# ~; z
privateKey: 'PRIVATE_KEY_OF_helloworld11'& g- r1 P$ d5 H$ G& ?
7 w5 J, B2 D/ ]9 }; N I# n8 x
},
( G: n% N. J1 C" t
// 内容提供者账户信息
) j. f# L/ o) c) }, |# d
account2: {7 k! n9 X8 f4 ]+ n* M+ L
name: 'helloworld22',& m/ b2 ]4 p8 a7 V
publicKey: 'PUBLIC_KEY_OF_helloworld22',
privateKey: 'PRIVATE_KEY_OF_helloworld22'
}
}+ J- ~% n4 R7 d& L$ T* S
7 L7 e1 d% E: [! D+ h( I
module.exports = config9 ~ S& p7 e9 O7 Z; N; \9 j4 o* d( s
配置权限/ T5 @, l% Z$ w4 I7 [+ F
在合约代码中,我们调用了 trans.send_inline 函数调用合约 eosio.token 来实现转帐操作,但是转帐操作是需要账户的 active 权限。所以我们需要更新一下合约账户的权限,需要添加调用者的 eosio.code 授权到它的 active 权限。这个调用者自然也是这个合约账户。
1 Y, \- R$ j/ N( i
const FIBOS = require('fibos.js');
const config = require('./config');/ x8 n! s2 B- [/ ?9 }$ R
const fibosClient = FIBOS(config.client);
. i. E- G" Q; ]/ C
let ctx = fibosClient.contractSync('eosio');
" r4 I: Q/ V A, J- I
var r = ctx.updateauthSync({8 S2 @# y% T$ t. A
account: config.contractAccount.name,7 F* N4 P* p9 Y6 G
2 s, T/ F6 }' K( s
permission: 'active',
( G( q% o7 } W$ T, p
parent: 'owner',
' O8 P7 I, x( w) q( L
auth: {
7 ?1 O& D, }( t. X& V* P$ `
threshold: 1,1 C# O" ]2 Z s d/ s+ c- _
keys: [{! E- W b! _2 G
key: config.contractAccount.publicKey,9 t8 ?+ o# @* G
weight: 1
6 Y0 @" O$ J3 a9 ?& J, v; V
}],: k& K0 F Q* ]& @ R( D: g+ l5 p
$ { P( ^5 f! ^# T, |; j
accounts: [{( X3 G. e2 S/ q0 S
. `1 Z- v% }" M6 ~5 L: C
permission: {: X; K/ u5 K4 V& t N& x
. a# {9 h% e/ d6 Q( a# i
// 将调用者账号的 eosio.code 授权添加到它的 active 权限下。8 s8 c, z- o& z( |' J& u
actor: config.contractAccount.name,0 T) H# B# c! r/ v
7 o: k' k; ] n3 A& n- R( g1 n; v9 \
permission: 'eosio.code'
},) T' _2 d7 ~! C/ b5 f k: j
weight: 1# G# v: y5 b& k
}]2 U8 s4 H# q9 c7 y# V% m
! J9 Q& O: i6 W; M& l5 E7 \1 X9 o3 [
}
},{
" J( ]$ `$ K6 |, j4 c/ G3 u
authorization: `${config.contractAccount.name}@owner` //更改账户权限需要使用 owner 权限7 ~$ c4 {' G' K8 g) {* R
});
j$ h$ V. Y* _' V7 S4 n* d8 E; L. \
console.log(r);
5 z9 r- e. y* u- B# r+ p( a
部署合约1 c3 t4 i) |* ^) f; a! i* P
% {% P$ J9 r; A4 l& \) g0 |
const FIBOS = require('fibos.js'); g' u4 s$ w* s1 M: [1 _. q
const config = require('./config');# x+ S8 \" V0 ? V1 |5 d2 ]) B
; Z! o2 k6 r- V- F6 k! x6 o
const fibosClient = FIBOS(config.client);7 ~, l! u% g+ @7 d' P n
const fs = require('fs');! i4 B2 {! E* [1 P3 B
, z8 P; N; Q- e J8 E
// setcode& t& W U5 P$ z
! A8 w# e0 G: c! O7 q
const jsCode = fs.readTextFile(`${__dirname}/contracts/subaccount.js`);$ D: x4 b4 N% T, |6 p
fibosClient.setcodeSync(config.contractAccount.name, 0, 0, fibosClient.compileCode(jsCode));
// getcode
+ L' Q& j$ F% u, N
const code = fibosClient.getCodeSync(config.contractAccount.name, true);6 }3 j! @( t* L) g4 C+ e% w! X; Z
console.log('code:', code);
: M6 d+ ~: q+ w8 M( u1 Z# Q
// setabi) x2 B6 w$ L2 k2 H9 d# U
* ^& N- J& C' q: f
const abi = JSON.parse(fs.readTextFile(`${__dirname}/contracts/subaccount.abi`));
3 l c' [7 L: m- P' L1 Q; z& s% C
fibosClient.setabiSync(config.contractAccount.name, abi);
转账测试
: k/ w9 l; V+ j) S% g% _, o0 D
我们先来写一个脚本 account.js 来查看三个账户的余额。9 P; ~- J$ D# w! ^# k2 O
const FIBOS = require('fibos.js');1 f0 t0 i3 ^, V
$ Y1 y8 M& @$ H3 ^
const config = require('./config');
/ Z! b" \( g# m ]4 l( \. l
const fibosClient = FIBOS(config.callClient);9 z! w5 [+ w9 v3 w. E4 r8 c1 j4 e
- t! |+ T8 F! Y: b
const account1 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account1.name, 'accounts');
console.log(config.account1.name);! `/ I0 l2 [+ N; ^; |1 f- ?8 [4 B
console.log(account1);
6 s3 \; g8 a( _% `) f
const account2 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account2.name, 'accounts');
console.log(config.account2.name);
console.log(account2);
const contractAccount = fibosClient.getTableRowsSync(true, 'eosio.token', config.contractAccount.name, 'accounts');
% S1 i* V$ W& v& r/ o8 f
console.log(config.contractAccount.name);& I) k+ z- I+ q& p
- j" }/ N6 k3 f: X
console.log(contractAccount);
执行 fibos account.js 来查看三个账户信息。 目前我们的账户还没有 FO,所以大致情况是这样的:: q+ u9 P2 V0 n2 R( K1 S5 ]
3 ~2 T& r. j+ u' u& c& o
用户账户:helloworld11 金额:0.0000 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO. o* b& n- F5 \5 I( O2 d/ \( P
; ~- R8 v0 [# F7 Y& Y- W
* p* l6 ? y; v4 r) F" U4 b: [
测试网会自动给每个账户发放10 EOS 的通证用以测试使用。账户中还并没有 FO 通证。所以我们再来写一个兑换脚本,用1 EOS 换一点 FO 通证。0 Q& g2 ~8 N, b4 I3 M+ P
3 W0 ?, V+ k& k: Z
const FIBOS = require('fibos.js');
: f* {1 C+ L& K) Y2 W
const config = require('./config');% W4 f0 F# \( {6 d8 ]9 _; c
const fibosClient = FIBOS(config.callClient);# W4 d3 Y" `5 N& Z8 o* |; N) o
/ c5 [$ X- V, j
let ctx = fibosClient.contractSync('eosio.token');4 x0 h2 e7 Q' L1 o( P2 O7 H; M
- @) r: E7 X5 g- f9 ]
const r = ctx.exchangeSync(; a$ P, l( G' ^$ f: R
config.account1.name,+ z7 g2 c5 N8 A4 X" D: y
'1.0000 EOS@eosio',
'0.0000 FO@eosio',! I5 J+ z$ L e9 V, I
; E1 ?5 o& u- s. _4 l" _
'exchange FO to EOS',
{
* |8 h' \" z, {5 U2 }+ e
authorization: config.account1.name
}" G1 ]( _9 k0 k& n, D6 ?
% @; B: ]/ H# q; y
);, ]1 D" J9 H! X- D6 ?. b
5 A- y2 N: A( S K6 _1 ^
console.log(r)
再次执行 fibos account.js 来查看账户信息。目前我们的账户金额大致是这样的:
' U' a7 t% r2 c
用户账户:helloworld11 金额:146.4245 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO& `+ t4 e: G. O' R- h7 e4 Y- p
下面写个脚本 transfer.js 来执行转帐操作。
const FIBOS = require('fibos.js');
const config = require('./config');& l Q+ C4 k3 e. x! O8 n9 R
const fibosClient = FIBOS(config.callClient);- G+ t0 J1 e, X. ]' p: k, S
( s+ K8 n, ?/ V
let ctx = fibosClient.contractSync('eosio.token');
const r = ctx.extransferSync(% B1 f7 M4 q" ]: ?. i& w+ H" V
config.account1.name, // 用户账户* R( l' s4 w5 Q7 n+ K f
* O- V( m! u, u/ q' `( ]
config.contractAccount.name, // 平台合约账户" h! m6 m$ ^! Y9 I3 O! c: I
'10.0000 FO@eosio', // 转帐金额; H( W. Q, @9 K- x# O! i; J9 C
config.account2.name, // 附言填写内容提供者的账户名,平台合约会给它分账( b$ r. Y, p S
{
authorization: config.account1.name //提供用户账户的授权
}2 N8 m+ B% G; G! F
* W, X, [6 q7 S) c: \8 p5 t \
)0 j5 n j: \5 ^8 M4 e
console.log(r)
我们要从用户账户 account1 给平台合约账户 account3 转帐 10 FO。memo 参数为要分成的内容提供者账户 account2。根据合约中定的2/8分成,平台合约账户 account3 将会分得2 FO,而内容提供者账户 account2 将会获得8 FO。
1 `' r7 H& e% i- z% [2 u* U2 h
使用命令 fibos transfer.js 执行该脚本完成转帐操作。
下面我们再来看一下目前三个账户情况。执行命令 fibos account.js。三个账户金额大致如下。
用户账户:helloworld11 金额:136.4245 FO内容提供者账户:helloworld22 金额:8.0000 FO平台合约账户:helloworld33 金额:2.0000 FO; I. X( k' i# _* b5 S7 A9 [
0 r; w7 H& [8 G! K6 v% ]2 j
结果显示,分账账户和平台合约账户如预期那样获得8 FO 和2 FO。. ?6 v- C. p9 _% f9 J7 T7 A
综上,我们成功使用了智能合约实现了自动分账。平台方还可以继续根据自己业务需要定制自己的合约。
文中的代码请参考:https://github.com/fengluo/fibos-subaccount
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人