使用智能合约实现自动分账
哈哈笑417
发表于 2023-1-4 14:36:20
113
0
0
文中代码已在 GitHub 上开源。https://github.com/fengluo/fibos-subaccount7 {- \# s* Q# @( N! A& G6 r
; [; B4 P+ T# Q* W4 x
设计思路
在 FIBOS 转账是通过 token 合约的extransfer方法来实现的。extransfer方法在执行的时候会给转账方账户和入账方账户发送通知。所以用户给平台方账户转账的时候,平台账户就会收到通知。所以整体业务逻辑如下:0 z! u% L) a6 M2 P) t% m8 c
$ ? D2 ]4 K% q
quantity: 10 FO
' H9 o5 L& c4 ^; D
memo: 内容提供者账户 quantity: 8 FO0 t$ A5 L5 L E% Q. o% E
3 s2 b% x! K5 a N2 V( c! D
用户账户 -------------------> 平台账户 ----------------> 内容提供者账户
extransfer 2/8 分成 extransfer7 P! O( v6 i& U5 b9 b( N8 X
: [' e9 i( |, v% }
1.用户给平台方账户转账,memo 中填写内容提供者的账户名。4 ^( x6 ]* i1 Q, X
2.平台方的账户合约监听 extransfer 方法的通知,然后做出分账计算,给对应内容提供者的账户转账对应金额。& i0 N6 l3 H$ X6 g: i
整体逻辑很简单,整个合约代码逻辑差不多用20行就可以写完。
) O4 o. J5 w% {) y0 E* W9 t+ N
编写合约1 ]% B; y3 e0 I
FIBOS 的智能合约分为 ABI 文件和 JS 合约两部分。ABI 相当于合约接口,JS 合约则是功能实现。本案例目前没有接口设计需求,不过 ABI 文件还是合约不可缺少的部分。所以我们简单创建一下就好。
我们先创建一个 contracts 文件夹,合约文件都会放在这里。然后在此文件夹下,创建 subaccount.abi 文件,内容为:
{9 x4 w4 ?+ b0 E7 q# W
" x% }( o/ m; d* d7 c) [: O
"version": "eosio::abi/1.0"
}
4 E9 C6 C' h" K1 i, b
JS 合约部分也没有太复杂。在 contracts 文件夹下创建 subaccount.js 文件,代码为:
8 f/ \& U( A) b: m- \& ~% F/ ^
exports.on_extransfer = (from, to, quantity, memo) => {3 h4 @* p) @/ s$ s' \' d
' R$ F5 [5 s) f2 w: {. B
// 需要在开头做一些判断) D/ X2 f( [$ E+ W8 Q- c6 Y
if (to === action.receiver && action.is_account(memo)) {9 A* _! W( i0 `
const num = parseInt(quantity.quantity.split(' ')[0]). O. @8 {1 B; M& D' c
2 o. e" C$ ^& W7 A q. W1 {$ p) ]% q- j
// 假设我们约定平台方跟内容提供者是2/8分成。; S X! M8 d& A; }; U
( Q6 i9 @0 G9 ]8 I
const subnum = (num * 0.8).toFixed(4);) l( w" y* o: x; H; c
, a- H$ P! \9 O3 Q
trans.send_inline('eosio.token', 'extransfer', {
0 k! V6 x5 u1 Q" {% N, K9 e
from: to,
to: memo,
# \3 T# U" e9 _1 u
quantity: {- E/ y8 x! |7 m& N
9 ]* L% |0 U! N; H6 E2 K9 A
quantity: `${subnum} ${quantity.quantity.split(' ')[1]}`,
, {5 o) q" V# O# |1 H( q9 K6 ?+ k% ]
contract: quantity.contract
},
memo: 'sub account'" ?0 R. W# ^, J/ t6 h. m* w
5 Y: M `- V& U6 N/ y W
},
n1 G. M5 w# k3 ]
[
{, k( v6 @. `; r% `2 q' x1 G5 a2 h
% Y: Z+ a0 l4 x; o1 f1 l, Q( p3 i
// 需要提供合约账户的 active 权限
actor: action.receiver,5 D9 j8 M7 D# b) c& n0 P' H
permission: 'active'1 Y5 _# J4 S8 v0 Q' W, R8 c1 v
5 _: m8 b1 j) O! l/ `( o
}/ b8 n. y& U( ^7 d; R
]);
) M9 p4 v, r) p6 q& ^$ ?3 Q2 X
}- J9 I$ Q0 p+ X6 Y+ o* U! @
' ]; L/ `4 W2 @. q
}+ r# c2 O) j6 h4 M# L! W
合约代码开头我们需要做一些验证。: X6 z6 r& S/ g; ]5 L
1.收款方的账户为合约账户,否则因为下面代码执行给内容提供者转账时,因为转帐方也是合约账号会再次收到通知,造成无限递归,超出最大 send_inline 层数而报错。. B1 E$ U; e! R
2.我们用 memo 参数来放内容提供者的账户,所以我们需要对此参数校验一下该账户是否存在防止打错。
合约代码中我们使用 send_inline 调用 eosio.token 合约来执行转帐操作。转帐操作需要对应账户的 active 权限才能执行。为了解决权限滥用问题,FIBOS 定义了一个特殊权限 eosio.code。我们需要在平台合约账户中配置权限,在 active 权限下添加该合约账户的 eosio.code 授权。具体的配置操作会在下面说明。8 y+ h$ b8 n# [; G3 L6 @7 O! c
% R5 a9 R0 t8 p: _1 K6 D
在 FIBOS TestNet 上注册账号8 t) G5 M) U N; s9 e
为方便测试,我们在测试网 http://testnet.fibos.fo 上注册三个账户。9 e7 l* W' k8 w3 k: p+ D
" q" }1 B2 l, k" s+ L& W
用户账号 helloworld110 f4 [% Z+ m& a+ p) Z9 C `: y/ ]
4 P, Z) \' g, b
内容提供者账号 helloworld227 [8 c! \6 ]+ e9 w2 G
: y2 m+ I6 I9 d9 w' x# Y' `
平台合约账号 helloworld33# R. y" Z- H1 j; r. R5 G! J
2 u" v- X7 i7 M a: `
我们需要记录这三个账号的账户名以及公私钥。以便下面的开发使用。创建一个统一的配置文件来记录这些数据:$ H+ Y4 c& W0 c* Q! l
* W X1 ~. ~* k1 h
const config = {7 R1 L6 e D* L, ]
' j4 B7 }9 u2 ]; }1 S- ?
// 平台合约账户的客户端配置
client: {0 b+ T0 U8 B# B- s, W+ ?
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',
, [ V8 q4 D3 {- x5 D1 ~
httpEndpoint: 'http://testnet.fibos.fo',; N2 ]2 D- ]$ R% w
keyProvider: 'PRIVATE_KEY_OF_helloworld33'; }& P2 C( K- Z/ b' ]) Z
$ m) u: [+ M: k
},
// 用户账户的客户端配置
, q" S6 \. B( N2 W: k- L% S% a; P
callClient:{, o( D0 a1 ~1 ~& u
. f3 ^. s" i ^' q
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',
httpEndpoint: 'http://testnet.fibos.fo',
+ K1 ^8 b. {9 J9 l/ p
keyProvider: 'PRIVATE_KEY_OF_helloworld11'
4 b; P1 _/ I' u- C
},
// 平台合约账户信息. k5 H. Z& D4 A$ U/ P/ k& X6 _
contractAccount: {# k6 y8 R+ }! x
" h% [, C4 I7 C/ U( {6 y4 E. g, s
name: 'helloworld33',
publicKey: 'PUBLIC_KEY_OF_helloworld33',
privateKey: 'PRIVATE_KEY_OF_helloworld33'* y% N! `- F- A' ^7 f2 V
},- }) s! J' Z' w
o- L# Q7 c2 t: H" I+ j2 Z
// 用户账户信息4 Y6 E. b: o N
account1: {" O% @: Y, e9 ~0 f
, q; d/ {- [. ~3 K- N' P- P! n
name: 'helloworld11',
publicKey: 'PUBLIC_KEY_OF_helloworld11',: [2 {; z0 i3 c; n# K5 k; k d
( Z1 F# X4 a) h! {/ }5 j3 L6 w
privateKey: 'PRIVATE_KEY_OF_helloworld11'
},
// 内容提供者账户信息
account2: {3 V( A" g! i' Y: M: \% h
name: 'helloworld22',
- ~$ l4 I( W6 D& _7 h& _' }
publicKey: 'PUBLIC_KEY_OF_helloworld22',
5 [5 D/ O) u; k
privateKey: 'PRIVATE_KEY_OF_helloworld22'
) m2 ?+ ^# Z5 ^# W
}# m# Y9 B2 i9 L
}
# ]8 T v+ H2 J) k
module.exports = config( C4 Y5 q( {, ? x+ g
配置权限
4 m. [* Z( g7 Z. D* l
在合约代码中,我们调用了 trans.send_inline 函数调用合约 eosio.token 来实现转帐操作,但是转帐操作是需要账户的 active 权限。所以我们需要更新一下合约账户的权限,需要添加调用者的 eosio.code 授权到它的 active 权限。这个调用者自然也是这个合约账户。$ p. p- n, e' C" C. i, Z
const FIBOS = require('fibos.js');
const config = require('./config');( m+ U+ b1 n( ?+ I- r! L& Y
: M7 ?+ D# n3 y: e
const fibosClient = FIBOS(config.client);
4 L* Q% B9 `! m% u
let ctx = fibosClient.contractSync('eosio');9 ~! A* s1 j/ W
var r = ctx.updateauthSync({
account: config.contractAccount.name,7 y0 p0 D* ?% q/ I. \
permission: 'active',
2 W0 ?6 F+ f9 R- t& @7 W
parent: 'owner',
auth: {
7 {5 m! u$ O6 {9 ~
threshold: 1,' m% R7 Z9 [8 r& Q4 L) V4 z7 S
keys: [{9 U H7 Q1 X6 P( C# Q6 r9 }
key: config.contractAccount.publicKey,7 I6 n! M1 k `9 P0 y' }8 V
weight: 1# Y8 i) u& Z2 x6 ]6 K4 U) t1 E% _: y
% E8 ~+ P0 q! Y; Q m1 [: C1 k! `
}],
2 u5 X% m- C2 |$ Q0 g! E/ n
accounts: [{2 @9 f/ I, d. J( c/ o3 [
h. s+ i4 T; ?2 N) ^
permission: {
// 将调用者账号的 eosio.code 授权添加到它的 active 权限下。0 a0 j" n0 h5 o
actor: config.contractAccount.name,1 a" \ B8 E" u) u4 s$ b% \
permission: 'eosio.code'. @: }; a, V2 V- m# k* ]
},
" Y4 l' p' C& b
weight: 13 q: m! W* a" l( Y, i0 M3 F) ^5 g
}]
}
},{* ~* z; g# I. ]! W
0 A/ m5 p9 k) i. N
authorization: `${config.contractAccount.name}@owner` //更改账户权限需要使用 owner 权限2 L2 }5 i5 W) p$ W8 X2 |, G
* ^: I) w, \, v: B: e j1 B# N
});
console.log(r);! L: J ^2 e/ W- {3 ~: W& w
部署合约! b; j" O s+ l! y$ }; g
const FIBOS = require('fibos.js');% _. |* g5 h$ ~8 m* f O5 ?' A
+ Z# L9 q& |# w: I/ i7 F. X, D
const config = require('./config');* ? z" B0 a! y1 n
const fibosClient = FIBOS(config.client);- @) C# C" J( M' j& u, d
6 t8 v2 m/ Q: o
const fs = require('fs');
# v0 X8 d! F" Z+ P, g- a9 B( U
// setcode9 ]% M3 b% m; k- B
7 I9 ` {: H: W+ M7 w$ R( P
const jsCode = fs.readTextFile(`${__dirname}/contracts/subaccount.js`);
: J8 F7 X0 A! z; d2 O
fibosClient.setcodeSync(config.contractAccount.name, 0, 0, fibosClient.compileCode(jsCode));
7 {2 |1 u5 C4 M" b" l; [0 x
// getcode
const code = fibosClient.getCodeSync(config.contractAccount.name, true);* O& ?' q3 s9 }- l4 F5 H
console.log('code:', code);
// setabi% H+ Q+ X3 d& K+ E5 M" e3 O; a
3 {7 R6 t2 k7 m/ X7 h! _
const abi = JSON.parse(fs.readTextFile(`${__dirname}/contracts/subaccount.abi`));
fibosClient.setabiSync(config.contractAccount.name, abi);
转账测试
我们先来写一个脚本 account.js 来查看三个账户的余额。
const FIBOS = require('fibos.js');
8 v1 J1 N- H7 i$ J: V# t
const config = require('./config');
# p) _0 E5 i. P$ M4 K) H7 H9 ~& }
const fibosClient = FIBOS(config.callClient);
const account1 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account1.name, 'accounts');
% U+ a7 ^! T. P' O: z& G; N
console.log(config.account1.name);
& b- [& O/ U2 I" I- O+ ` B
console.log(account1);/ @( I! X6 N7 l- Y! j
const account2 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account2.name, 'accounts');
# Q0 |. X, Q, e2 `+ \( [3 p
console.log(config.account2.name);
: I! Q4 M- H; z+ m% z
console.log(account2);0 i" k/ R$ S8 n/ H& s
const contractAccount = fibosClient.getTableRowsSync(true, 'eosio.token', config.contractAccount.name, 'accounts');
6 e o) N. \+ r" f
console.log(config.contractAccount.name);
console.log(contractAccount);' i) ?1 m% O: P
执行 fibos account.js 来查看三个账户信息。 目前我们的账户还没有 FO,所以大致情况是这样的:+ Z- D4 p& Z/ J: C; O
用户账户:helloworld11 金额:0.0000 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO) A% U9 l/ [1 m/ U
测试网会自动给每个账户发放10 EOS 的通证用以测试使用。账户中还并没有 FO 通证。所以我们再来写一个兑换脚本,用1 EOS 换一点 FO 通证。1 O0 _+ G- j9 f' c5 v! Q
7 D. M8 ~2 K( O/ Z0 E
const FIBOS = require('fibos.js');
const config = require('./config');
const fibosClient = FIBOS(config.callClient);
let ctx = fibosClient.contractSync('eosio.token');! |% x! S6 D9 u3 t3 p% ?. U0 ^
const r = ctx.exchangeSync(/ e7 v, Q- ~* N: A0 k5 T. O
- s9 c/ d9 j! V* \7 b
config.account1.name,
'1.0000 EOS@eosio',5 A f! A) }2 e2 o6 Z$ @+ P
' V2 G- e5 K( v% c$ i
'0.0000 FO@eosio',3 N% U- I/ a4 |& a
'exchange FO to EOS',
{
6 d% ^1 d. j. i* @4 {/ Y
authorization: config.account1.name
}
' a+ Y' |2 W2 a7 R/ ~3 g
);' H' L$ e6 f+ Q J( r1 c
console.log(r)3 a& ^/ S9 s8 b' C \
5 L" w% }! y" L. j# Q) f, S' X
再次执行 fibos account.js 来查看账户信息。目前我们的账户金额大致是这样的:
用户账户:helloworld11 金额:146.4245 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO: e9 G7 v" H4 Z- ?0 c
2 {% w) }" [; x! A# L9 A3 O' ?. q
下面写个脚本 transfer.js 来执行转帐操作。5 N3 ~; k4 ]7 r( a
* f/ X0 C, E; B5 L8 B+ I
const FIBOS = require('fibos.js');: q1 Y5 Q4 U! x7 P
" |- ?- K9 L1 W7 O
const config = require('./config');) H& u8 L8 ?/ {, r7 r# R4 z8 S
1 Y' H1 a5 m! y: ]7 {3 A* c4 L( m% `
const fibosClient = FIBOS(config.callClient);) o y+ g* M$ o2 x( E; M) J
let ctx = fibosClient.contractSync('eosio.token');
" Y% m- A0 E& v( U/ K
const r = ctx.extransferSync(
4 k: y6 h( {1 A7 \) \
config.account1.name, // 用户账户) _9 a) m" m2 p* [# y* g3 R) J6 J
config.contractAccount.name, // 平台合约账户$ r; g4 a t& i+ \
'10.0000 FO@eosio', // 转帐金额
config.account2.name, // 附言填写内容提供者的账户名,平台合约会给它分账
- {6 t+ ^0 ?# ~0 H
{. N. k7 R0 o- r" v# l/ y" X8 i3 E
: S. k9 P% a4 w7 ~, b
authorization: config.account1.name //提供用户账户的授权$ B* T% P# }% j4 G/ H
! y7 n6 F3 D/ Q, k+ l
}
)
console.log(r)* c) S. C! T5 h0 s. h8 C
我们要从用户账户 account1 给平台合约账户 account3 转帐 10 FO。memo 参数为要分成的内容提供者账户 account2。根据合约中定的2/8分成,平台合约账户 account3 将会分得2 FO,而内容提供者账户 account2 将会获得8 FO。! N' k; `) w8 V
使用命令 fibos transfer.js 执行该脚本完成转帐操作。5 L; H+ a4 h0 M% W/ q, D. R( N% \
' d s9 v, s6 ]% I3 c/ c' Q/ i
下面我们再来看一下目前三个账户情况。执行命令 fibos account.js。三个账户金额大致如下。
$ J+ W; ~# L* ?, Z
用户账户:helloworld11 金额:136.4245 FO内容提供者账户:helloworld22 金额:8.0000 FO平台合约账户:helloworld33 金额:2.0000 FO
结果显示,分账账户和平台合约账户如预期那样获得8 FO 和2 FO。
; M# x7 B2 V k' g7 ?
综上,我们成功使用了智能合约实现了自动分账。平台方还可以继续根据自己业务需要定制自己的合约。
文中的代码请参考:https://github.com/fengluo/fibos-subaccount
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人