使用智能合约实现自动分账
哈哈笑417
发表于 2023-1-4 14:36:20
157
0
0
) v) M* \0 Z1 c* ^" o
文中代码已在 GitHub 上开源。https://github.com/fengluo/fibos-subaccount0 p9 p/ P- o; o6 K$ a5 ~& e7 |& x
* m% {4 J6 W: `" ^
设计思路
在 FIBOS 转账是通过 token 合约的extransfer方法来实现的。extransfer方法在执行的时候会给转账方账户和入账方账户发送通知。所以用户给平台方账户转账的时候,平台账户就会收到通知。所以整体业务逻辑如下:7 U' A8 h' U: |) ?' n! o) i6 ?+ l
1 E; y0 D) u4 O
quantity: 10 FO2 n5 n+ @) P/ t2 o
; N" I- K- {; O) e. i
memo: 内容提供者账户 quantity: 8 FO
9 R4 w. D7 i( c9 W
用户账户 -------------------> 平台账户 ----------------> 内容提供者账户
$ V4 S. ]1 ?- K0 t+ Y
extransfer 2/8 分成 extransfer
1.用户给平台方账户转账,memo 中填写内容提供者的账户名。
. D5 M: \. y" a
2.平台方的账户合约监听 extransfer 方法的通知,然后做出分账计算,给对应内容提供者的账户转账对应金额。3 j4 j* ?. M9 q Z* n4 B X
整体逻辑很简单,整个合约代码逻辑差不多用20行就可以写完。
编写合约
6 w7 u! P/ q% n9 J6 b9 x3 ]6 _& z) V
FIBOS 的智能合约分为 ABI 文件和 JS 合约两部分。ABI 相当于合约接口,JS 合约则是功能实现。本案例目前没有接口设计需求,不过 ABI 文件还是合约不可缺少的部分。所以我们简单创建一下就好。
我们先创建一个 contracts 文件夹,合约文件都会放在这里。然后在此文件夹下,创建 subaccount.abi 文件,内容为:7 R' h3 j7 M% }
) L7 N- K2 O* D B( V) m2 l* A
{
$ I# E5 p1 O5 r" {. K4 |2 Q
"version": "eosio::abi/1.0"$ H+ E% `$ J- |) A+ @9 ^; Z' B
4 [+ Q8 U! N8 L4 ~, Q
}
JS 合约部分也没有太复杂。在 contracts 文件夹下创建 subaccount.js 文件,代码为:
exports.on_extransfer = (from, to, quantity, memo) => {. }" B: |" `: l- s) F* w! D
5 t, T, b* i* x! r2 t [! m6 [' ?
// 需要在开头做一些判断
if (to === action.receiver && action.is_account(memo)) {
const num = parseInt(quantity.quantity.split(' ')[0])! u3 y# B2 p6 i% {/ b
# Z$ |3 b6 W& p$ k3 c K8 z: m
// 假设我们约定平台方跟内容提供者是2/8分成。
% T Q) w, w0 e5 v& s1 {4 R8 s
const subnum = (num * 0.8).toFixed(4);6 [# g% w" O5 e* _3 Y, c
% M3 g. }0 o: E
trans.send_inline('eosio.token', 'extransfer', {7 b" y! a/ s7 | r9 n; F
from: to, ^% [/ G8 r5 n5 H0 L @! o: \
to: memo,/ u5 K4 q3 j4 G/ l! v- c7 Z$ S9 @; E
V* b/ [$ g( y8 v% o/ S
quantity: {
3 r0 ~- \! M* P3 I2 V1 i
quantity: `${subnum} ${quantity.quantity.split(' ')[1]}`,
6 Z3 V, Q/ j8 B. K! \$ w
contract: quantity.contract% Z+ o' G' I$ [, M' V a9 F7 u9 U
d( N& t& r1 g/ y8 ?
},
memo: 'sub account'
+ |0 ~# q! U0 a3 Q# {
},
[% \1 ?3 x* h3 @' [& e
! M- A( w" V) T: z: T
{
// 需要提供合约账户的 active 权限
actor: action.receiver,
- |- h/ r& l" W3 P; e
permission: 'active'; Y. _" _" }) O9 _
. z4 L/ R3 d2 K& w/ I( T- M$ P f
}$ y; P+ h* V- p5 H! ]' O" k$ d5 P2 i
9 M' ? U3 X( }+ [ ?- H
]); I1 u# H" g) O/ o# Q2 d. v
}8 ?. Z5 e" r& q0 y$ K+ ]! L2 }: }
}3 S$ Z. w: b# s: n+ G+ r
合约代码开头我们需要做一些验证。. P8 }( j& A" {$ R. z; t
1.收款方的账户为合约账户,否则因为下面代码执行给内容提供者转账时,因为转帐方也是合约账号会再次收到通知,造成无限递归,超出最大 send_inline 层数而报错。- }$ p# a% b( L! Y2 |8 K
2.我们用 memo 参数来放内容提供者的账户,所以我们需要对此参数校验一下该账户是否存在防止打错。, w9 q8 X: {! ^2 K( ^( G6 u
& ]6 }' C. r2 Z2 K: k
合约代码中我们使用 send_inline 调用 eosio.token 合约来执行转帐操作。转帐操作需要对应账户的 active 权限才能执行。为了解决权限滥用问题,FIBOS 定义了一个特殊权限 eosio.code。我们需要在平台合约账户中配置权限,在 active 权限下添加该合约账户的 eosio.code 授权。具体的配置操作会在下面说明。
在 FIBOS TestNet 上注册账号
- T/ S0 j0 N7 V: x5 }, R
为方便测试,我们在测试网 http://testnet.fibos.fo 上注册三个账户。8 T3 x4 O; Z+ u, n/ K
! z( t! R) B: ?$ s8 b! E
用户账号 helloworld11+ x; k6 H$ W4 G. D$ ]
内容提供者账号 helloworld22& D1 Z5 ^% f* @7 L) a1 g; i/ C5 F+ K% R
1 ?; _1 j+ U o7 Y3 u
平台合约账号 helloworld33
我们需要记录这三个账号的账户名以及公私钥。以便下面的开发使用。创建一个统一的配置文件来记录这些数据:% J/ Y/ r7 M" p% _: U7 ~
const config = {& X7 I& @2 \: N+ W
5 r. @ R2 y+ n* K* T/ i" u
// 平台合约账户的客户端配置
. Q, H. G7 U1 }) L: i3 S( s
client: {
9 K' W- N7 K- ?( E& G
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',
) {- s3 J. g. l6 O0 d) h v
httpEndpoint: 'http://testnet.fibos.fo',
keyProvider: 'PRIVATE_KEY_OF_helloworld33'+ I% H% f' b q4 V4 K$ f' o
! W) X; C" y/ o8 S) g1 Z; V
},
6 J2 M1 O6 ^1 P i l1 i
// 用户账户的客户端配置3 s# b9 N& ~$ T
callClient:{1 n7 q5 h. @, F3 E! F G" r
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',
7 X* s4 l1 c2 t2 U3 `1 e# V* o, }
httpEndpoint: 'http://testnet.fibos.fo',$ M- E' u7 c2 Z7 H( A) c/ R# V" O
% u$ P' I: J8 X5 N
keyProvider: 'PRIVATE_KEY_OF_helloworld11'
2 L; H! F6 q0 h0 d% _
},2 L. G2 u% E7 c) Z) l- E
& U/ ^7 r- q! }
// 平台合约账户信息. h# S7 @9 _' s/ n% H( E6 k; w5 K
contractAccount: {. f# @" @. z) ~5 J, ?
' m, u: `; u; f: M* ?
name: 'helloworld33',
publicKey: 'PUBLIC_KEY_OF_helloworld33',2 S( r: t V2 P7 `, q) [
privateKey: 'PRIVATE_KEY_OF_helloworld33'
. L* E6 w# Q$ b3 y* \* }
},
% Q* n! j! P1 j" D; A6 z
// 用户账户信息2 J, D& n4 }9 C
* \ Y# _. z- w$ J5 \8 u
account1: {6 }8 A: n+ J' Z2 T$ f
name: 'helloworld11',) t: o1 y4 g/ z! @1 [$ M
5 ~3 [1 Y% W1 z; X: h! s( z
publicKey: 'PUBLIC_KEY_OF_helloworld11',
1 r& G% C- G/ ?( j( _4 d
privateKey: 'PRIVATE_KEY_OF_helloworld11'5 y; l, v! \" _8 t3 R6 ^; J
* ^3 v! ]" N/ Z; D* Y
},
2 e; ^# T0 r1 X( l
// 内容提供者账户信息, E( U- _3 ~8 |. Z
' v [* J+ t+ g: t
account2: {: h2 M7 j" j" y" ?& B' `
name: 'helloworld22',4 [& W2 \) Z. O( C! ^5 b, D' z( Y
publicKey: 'PUBLIC_KEY_OF_helloworld22',& B) _3 Z2 _3 z" P$ l" x$ p
privateKey: 'PRIVATE_KEY_OF_helloworld22'2 m, T0 w0 g9 ] y7 d! h5 |
}
}
module.exports = config: m+ r7 w8 |. H5 Y T6 ]# E
配置权限
在合约代码中,我们调用了 trans.send_inline 函数调用合约 eosio.token 来实现转帐操作,但是转帐操作是需要账户的 active 权限。所以我们需要更新一下合约账户的权限,需要添加调用者的 eosio.code 授权到它的 active 权限。这个调用者自然也是这个合约账户。
9 I- r1 [2 U' Y# }: D* a6 [
const FIBOS = require('fibos.js');! _- v4 {. M4 g: E/ \
const config = require('./config');
7 d7 H; N( t6 O0 P" o5 e0 A
const fibosClient = FIBOS(config.client);% H9 o. c! q5 S0 U, ^6 o* k
let ctx = fibosClient.contractSync('eosio');$ A7 H6 U2 t8 H
+ b( E9 D/ C- Z
var r = ctx.updateauthSync({
2 x4 s& d' W6 N4 U+ e
account: config.contractAccount.name,
5 r4 S! g: F0 L0 J
permission: 'active',; b( V, M( n; m9 G
, }4 U- ]4 ^6 L2 Z M
parent: 'owner',% B8 d) E6 j0 o6 S" y
auth: {
4 M+ a. d+ ^* R2 K( u. F; x% f ?
threshold: 1,/ Q* I" x' K' J6 U5 B
keys: [{- y1 K; n# r2 U( e8 s7 d% Z9 y
$ g& F$ `# j" ^8 O( C9 L, i
key: config.contractAccount.publicKey,
9 t" u7 [# \: f3 H$ t9 P5 M8 G
weight: 1( d0 y; s, N9 U( c( T* {
1 X) t2 z, Q& y! V5 E
}],
" _8 g9 ^* @! O
accounts: [{4 f. a! `; _" [: `3 g
permission: {
// 将调用者账号的 eosio.code 授权添加到它的 active 权限下。7 G" y' ^; Q. u; p- j2 r, M' I
4 w6 J6 G( M8 d& T
actor: config.contractAccount.name, C: b7 Q0 h6 A
permission: 'eosio.code'
3 e! g7 M/ k& y, S) J: f
},
weight: 1! j3 M5 k+ Z2 r) C- S' V
}]3 _9 A4 m5 Y# m1 | J
}1 Y, g8 C' w4 e1 {0 I
},{
7 B2 M" E5 ~% R' S( J
authorization: `${config.contractAccount.name}@owner` //更改账户权限需要使用 owner 权限( ]& h# _: g! d( p0 v6 ~) x& O
});
console.log(r);9 ?+ U$ l. ?4 }, V3 r- I5 X
部署合约
const FIBOS = require('fibos.js');7 H. l& {( O s' L6 H" C
) G% v) s6 ^( ?7 Z
const config = require('./config');% |5 R' E2 Y# y! Q9 U8 O' o/ V- x
+ f: H( f' O* s6 Y, V$ ^
const fibosClient = FIBOS(config.client);$ ?8 S Z3 q2 N( w
7 R' \! m" q9 I8 k) ?! F; B1 Y
const fs = require('fs');
8 A2 u0 j4 g8 j6 g: y
// setcode
, T/ T* t% s8 _- \- o9 u5 w4 D0 Y
const jsCode = fs.readTextFile(`${__dirname}/contracts/subaccount.js`);0 P$ I- R7 |2 }; @5 S Q7 j
6 u( L. K# Y% F' ?
fibosClient.setcodeSync(config.contractAccount.name, 0, 0, fibosClient.compileCode(jsCode));
// getcode, N/ O0 {+ I; S0 |3 I+ N
const code = fibosClient.getCodeSync(config.contractAccount.name, true);
$ r( g0 `+ Y8 p7 s9 H P8 o1 q' S! H Q
console.log('code:', code);
; i2 c$ o0 Z4 a3 J
// setabi
const abi = JSON.parse(fs.readTextFile(`${__dirname}/contracts/subaccount.abi`));. ^( h; u. ~+ q
) W; k' W/ i/ K( v/ Q$ k2 C
fibosClient.setabiSync(config.contractAccount.name, abi);
0 h& |( R: s8 @( S0 x6 h
转账测试
' B6 T/ A5 S5 n; \( S9 r' D# e
我们先来写一个脚本 account.js 来查看三个账户的余额。9 m5 \3 T0 |9 o! v4 T! d
const FIBOS = require('fibos.js');
const config = require('./config');; k8 q6 K3 A9 O0 P
~; ]- a' f9 S2 V$ q1 P' Y' M
const fibosClient = FIBOS(config.callClient);; J7 o1 n# ?- b4 y
const account1 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account1.name, 'accounts');
0 X8 q& F7 ^, F9 J. I8 Z- {
console.log(config.account1.name);
6 P2 _" C7 J. i+ D6 `
console.log(account1);' i+ g/ p# W/ c! _6 d+ C
0 P2 \8 e' a) F2 Y$ f; n7 y" ~$ D
const account2 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account2.name, 'accounts');1 I. E4 A3 [/ p8 p6 ?
7 @% C% g% d% G
console.log(config.account2.name);
! c; \* ~8 J: m7 K6 ~
console.log(account2);
?* S0 J, w9 _% F B/ j
const contractAccount = fibosClient.getTableRowsSync(true, 'eosio.token', config.contractAccount.name, 'accounts');( G+ V* s' X; [0 j
console.log(config.contractAccount.name);" Z9 S* ]0 a2 }6 M& V
console.log(contractAccount);
d4 N4 s, Z% }; h% |: ^
执行 fibos account.js 来查看三个账户信息。 目前我们的账户还没有 FO,所以大致情况是这样的:: m" B9 V% O5 H2 V2 n( U: O; I' n9 g
0 J0 s4 H! v% p* T8 V2 @0 y) M
用户账户:helloworld11 金额:0.0000 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO
* v& a7 |0 f N) j3 {6 [
$ A5 h& x4 [5 H& l& ^1 L# m' d
测试网会自动给每个账户发放10 EOS 的通证用以测试使用。账户中还并没有 FO 通证。所以我们再来写一个兑换脚本,用1 EOS 换一点 FO 通证。- f t5 `8 }( Q% K: C
, p% f" ~, e) J" b3 a9 m
const FIBOS = require('fibos.js');) e- ~( r5 m3 v8 k
0 \! u. g+ {+ z, ?
const config = require('./config');5 y2 [! p: X( W6 ?1 a" g
6 m; G/ | m8 W, Q
const fibosClient = FIBOS(config.callClient);
; n: b! i2 d ^; A( m4 O
let ctx = fibosClient.contractSync('eosio.token');( {4 x) m: o% E, \
5 u5 T8 C3 w/ g- d9 f2 u
const r = ctx.exchangeSync(
config.account1.name,
'1.0000 EOS@eosio',
+ U. o; a% k H# C/ k# H
'0.0000 FO@eosio',
4 Z% _( p; @6 x; O
'exchange FO to EOS',9 X0 I' d# C& `5 {
{$ e7 h; w7 S6 z# K
authorization: config.account1.name
}# G, \ x j2 ]' n7 O
* {) P% b1 q+ @ H
);* `, \. Q7 F7 R6 ]& n1 g
console.log(r)
1 l* r( b* z+ E/ E/ x
再次执行 fibos account.js 来查看账户信息。目前我们的账户金额大致是这样的:
& t0 |3 i9 o3 t( G& ~
用户账户:helloworld11 金额:146.4245 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO1 b- ` G/ T0 [+ R. }$ T
0 m# l6 D7 t, |, l& D
下面写个脚本 transfer.js 来执行转帐操作。
const FIBOS = require('fibos.js');6 T2 M m5 U' X F/ L0 u' l
4 Z& S) W/ j+ u) {6 _
const config = require('./config');+ N$ }# \* k7 Z/ U
0 L H) C* V$ W
const fibosClient = FIBOS(config.callClient);
( d7 \, _9 s+ ^8 a# }: v3 S
let ctx = fibosClient.contractSync('eosio.token');
: W# r: z+ q) s/ Y' i0 V0 H
const r = ctx.extransferSync(
config.account1.name, // 用户账户, @! I J$ \: n1 G& y4 s+ P; g4 }
config.contractAccount.name, // 平台合约账户
'10.0000 FO@eosio', // 转帐金额! Q9 _' ~" @. e" o( I
0 s! l( o( |, ?* C6 a
config.account2.name, // 附言填写内容提供者的账户名,平台合约会给它分账
{
$ w7 o1 A) q9 H3 a& A! z
authorization: config.account1.name //提供用户账户的授权+ r: O6 F/ ^: W0 h1 Z
}
)+ _2 x3 y$ f/ s) z6 d. D& `
console.log(r)
我们要从用户账户 account1 给平台合约账户 account3 转帐 10 FO。memo 参数为要分成的内容提供者账户 account2。根据合约中定的2/8分成,平台合约账户 account3 将会分得2 FO,而内容提供者账户 account2 将会获得8 FO。/ @3 O/ \) V# D" ]4 }8 H6 ?
使用命令 fibos transfer.js 执行该脚本完成转帐操作。$ } ^, @* U/ @% y5 H5 D M: T8 O
- L( c8 `) X: L
下面我们再来看一下目前三个账户情况。执行命令 fibos account.js。三个账户金额大致如下。' ~4 A/ W* r. ?/ [" }* i& v. O" s
用户账户:helloworld11 金额:136.4245 FO内容提供者账户:helloworld22 金额:8.0000 FO平台合约账户:helloworld33 金额:2.0000 FO- p# _4 K' m; ~: W/ ]; o6 l
结果显示,分账账户和平台合约账户如预期那样获得8 FO 和2 FO。" o! ^* r8 x$ ^+ ?, ^
综上,我们成功使用了智能合约实现了自动分账。平台方还可以继续根据自己业务需要定制自己的合约。/ [6 r, t! W( a. u l8 `/ H
* {+ ~: z1 t. {6 m
文中的代码请参考:https://github.com/fengluo/fibos-subaccount
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人