使用智能合约实现自动分账
哈哈笑417
发表于 2023-1-4 14:36:20
307
0
0
& F3 h% ?) V- v
文中代码已在 GitHub 上开源。https://github.com/fengluo/fibos-subaccount
; b. X( I2 f1 P3 ~4 ~9 S# N
设计思路
在 FIBOS 转账是通过 token 合约的extransfer方法来实现的。extransfer方法在执行的时候会给转账方账户和入账方账户发送通知。所以用户给平台方账户转账的时候,平台账户就会收到通知。所以整体业务逻辑如下:
$ h, i( l) S6 q! q% r
quantity: 10 FO1 L: o+ d+ e. H" s1 T: \7 ~: R) v
- N; B- j) k3 Y4 f1 M6 G
memo: 内容提供者账户 quantity: 8 FO+ T2 L1 ~. T/ c5 A+ |
用户账户 -------------------> 平台账户 ----------------> 内容提供者账户
extransfer 2/8 分成 extransfer
1.用户给平台方账户转账,memo 中填写内容提供者的账户名。
2.平台方的账户合约监听 extransfer 方法的通知,然后做出分账计算,给对应内容提供者的账户转账对应金额。5 x4 ?3 y4 ?) t' M8 C
整体逻辑很简单,整个合约代码逻辑差不多用20行就可以写完。
! [8 ?: w3 U% n; R: Q% u4 `( v
编写合约; D5 f0 ~( ^8 d6 W$ [) B F9 @7 l$ D
: ?2 Z( |+ {1 K
FIBOS 的智能合约分为 ABI 文件和 JS 合约两部分。ABI 相当于合约接口,JS 合约则是功能实现。本案例目前没有接口设计需求,不过 ABI 文件还是合约不可缺少的部分。所以我们简单创建一下就好。4 r+ e* `6 |# b
我们先创建一个 contracts 文件夹,合约文件都会放在这里。然后在此文件夹下,创建 subaccount.abi 文件,内容为:6 b! y$ F2 K1 a- v: J9 ^9 A
0 w2 B* H, K7 U# h6 h
{
"version": "eosio::abi/1.0") ~. J$ J& D! p' I4 C! T& h; k
$ D# F2 Z9 t5 E6 i c7 y/ T
}8 l) G' F3 o" y9 B
1 T7 ~# E2 Y2 {; r, a5 k- x# i3 f
JS 合约部分也没有太复杂。在 contracts 文件夹下创建 subaccount.js 文件,代码为:! I* l1 W* |% k8 L( l& W4 R
exports.on_extransfer = (from, to, quantity, memo) => {8 N& \0 R# P8 E) N% |; v
5 X: { t3 K: U4 _+ W& X
// 需要在开头做一些判断
7 z* Y/ x* S: o; W+ z- V
if (to === action.receiver && action.is_account(memo)) {
const num = parseInt(quantity.quantity.split(' ')[0]). }! s; z* A9 h t2 s( E
0 u8 p; [( \- v" U* j4 y; z
// 假设我们约定平台方跟内容提供者是2/8分成。7 ^/ R- J; g' o1 q
W/ t& l W2 E- c' U
const subnum = (num * 0.8).toFixed(4);
trans.send_inline('eosio.token', 'extransfer', {' n1 Y7 i& s: X! Q8 f
2 a/ O- s8 p* z. }8 D3 w
from: to,- X8 V7 ], ?, }) P2 r# ?
1 G+ t5 ~1 o+ P7 O
to: memo,! Y4 w$ r/ _. n% l& L8 S
5 V# n' h2 l4 e+ E0 F
quantity: {
quantity: `${subnum} ${quantity.quantity.split(' ')[1]}`,
contract: quantity.contract
},# {9 P/ g8 j5 ?. Z' ]# n% s+ X
memo: 'sub account'
},
9 J$ t* }0 |! Q0 V* x0 K7 E8 D. m
[0 I+ ?0 {- l7 U& S
{
; h* j8 F) A. D j8 x) w
// 需要提供合约账户的 active 权限: A7 e, U2 ^8 |; M3 W$ q
actor: action.receiver,! s) y8 M2 W% ?" }' W, H
' g) g/ b3 [" A. z0 a, Q" C
permission: 'active') H; v" o' F2 o$ X4 G6 ?- V
1 s1 ~( r0 @( \. y/ P0 P8 a8 k
}4 G' \8 o2 L& T5 {0 I8 `
]);
}
3 ?! p- v& \2 ~9 \5 I2 T# s4 |
}! G6 R& K4 y. Z9 D+ ], k8 `
合约代码开头我们需要做一些验证。. T, W; G" Y! k: r! f
: L- a1 Y. f; n. |+ F; R
1.收款方的账户为合约账户,否则因为下面代码执行给内容提供者转账时,因为转帐方也是合约账号会再次收到通知,造成无限递归,超出最大 send_inline 层数而报错。
2.我们用 memo 参数来放内容提供者的账户,所以我们需要对此参数校验一下该账户是否存在防止打错。
合约代码中我们使用 send_inline 调用 eosio.token 合约来执行转帐操作。转帐操作需要对应账户的 active 权限才能执行。为了解决权限滥用问题,FIBOS 定义了一个特殊权限 eosio.code。我们需要在平台合约账户中配置权限,在 active 权限下添加该合约账户的 eosio.code 授权。具体的配置操作会在下面说明。
在 FIBOS TestNet 上注册账号4 r8 ?& }* x- t) t
为方便测试,我们在测试网 http://testnet.fibos.fo 上注册三个账户。
! u+ Y' |0 s* B0 \6 H9 v0 F/ u
用户账号 helloworld11
5 S/ J* M) o0 u' G
内容提供者账号 helloworld22
平台合约账号 helloworld33) |, d* [8 j5 m3 F
我们需要记录这三个账号的账户名以及公私钥。以便下面的开发使用。创建一个统一的配置文件来记录这些数据:5 _- R7 G1 v0 M# |/ C% d
. j- S/ g$ C8 z+ _5 F1 c
const config = {+ A# K6 b0 R) C' L: O) f! r2 ]
// 平台合约账户的客户端配置
client: {" d2 o4 P0 ~+ |: ?% H% H
* n" T4 J1 w# C G% B
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',
httpEndpoint: 'http://testnet.fibos.fo',# D- q- x9 q, H3 U+ H7 X
5 Y$ j& [) \+ @! |( N/ @, l2 b- y
keyProvider: 'PRIVATE_KEY_OF_helloworld33'% n" U( m& J; s
},
// 用户账户的客户端配置
- ` C9 J: P7 p: v1 q
callClient:{4 O4 c6 y; {+ z& X
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',
httpEndpoint: 'http://testnet.fibos.fo',
: n1 S8 c( M- {* l/ L
keyProvider: 'PRIVATE_KEY_OF_helloworld11'
},
9 ?3 ^& q' P D8 c% D( E
// 平台合约账户信息
contractAccount: {
name: 'helloworld33',. i" q* {( z* M9 g# b; F8 t
1 A0 r# q+ o* k& B2 l
publicKey: 'PUBLIC_KEY_OF_helloworld33',7 C: u( o5 ` N9 y, V* m
privateKey: 'PRIVATE_KEY_OF_helloworld33'
: Q% @: ~. B0 h/ T
},
// 用户账户信息
account1: {" k5 E: V) x# r' T! }( c% z
name: 'helloworld11',
8 L& ]7 B. B) ?3 S9 s+ e3 v
publicKey: 'PUBLIC_KEY_OF_helloworld11',
privateKey: 'PRIVATE_KEY_OF_helloworld11'
},
: t* I \8 A# u+ |
// 内容提供者账户信息' K7 w. @0 l, F' h/ R* H
account2: {) C' q( V/ f0 ~ R+ x! P& Q
name: 'helloworld22',
publicKey: 'PUBLIC_KEY_OF_helloworld22',
privateKey: 'PRIVATE_KEY_OF_helloworld22'& T( a! J) n9 P0 n/ t. p( f
; H6 b$ E# D! m$ F2 O- ?
}
$ r7 l9 f9 r2 O
}- B+ G: ^/ y' d' W. F
module.exports = config
配置权限( k! R- a1 ]3 k+ e
在合约代码中,我们调用了 trans.send_inline 函数调用合约 eosio.token 来实现转帐操作,但是转帐操作是需要账户的 active 权限。所以我们需要更新一下合约账户的权限,需要添加调用者的 eosio.code 授权到它的 active 权限。这个调用者自然也是这个合约账户。; O* k0 r' s" l, F# } ^7 \, t
const FIBOS = require('fibos.js');- U& s2 X$ t/ ^8 s
" m8 H5 i$ n% r/ e! B% a
const config = require('./config');
const fibosClient = FIBOS(config.client);
7 U! R7 b2 o7 P
let ctx = fibosClient.contractSync('eosio');
var r = ctx.updateauthSync({% l5 d( D }* G! J% J: V1 m s0 Y
0 F5 Y( o, _8 ^2 j: E9 y: S
account: config.contractAccount.name,/ {! Q. u9 l7 |" m
permission: 'active',
. m. D3 D: Y% A* q. l5 G! n0 H9 A
parent: 'owner',
: `+ i/ U9 G( o7 b, t6 t- L
auth: {
8 Y; { Z3 S7 F1 a6 c5 y* x U
threshold: 1,1 ?! @4 d+ ]: ]
keys: [{) G7 U/ ]" O* Z$ c* E8 [
7 T# l. K! c6 c+ \, n7 g4 J8 ]/ |: Y4 I. Z
key: config.contractAccount.publicKey,$ T9 j+ A0 R& [$ z; s" [+ ?: a$ g# g
. ] k$ ?, W, O% E; K
weight: 1
' m7 i A& Z6 w. U$ ^" j
}],0 W6 M1 s6 [" v* e, ~
accounts: [{
( C/ @/ h1 @8 P$ ~
permission: {
: w9 Q; V+ h! Y+ U/ B
// 将调用者账号的 eosio.code 授权添加到它的 active 权限下。. S. }" @" K6 e5 [4 @
" W1 v1 M" U# o. W
actor: config.contractAccount.name,1 x. e! Y. V. [ J3 ]5 d
+ Y/ ~" N2 F" Z& B
permission: 'eosio.code'
},. z4 C6 a) o- N) D
weight: 1
# f* ]. E: x6 w) k1 p! ?
}]
" P7 ]9 @, o6 Z8 C3 d
}5 W3 b* |. m6 R, _1 J% {" D# _
},{' Q- {, C$ S: ?! g# c9 ~
* S/ d2 H0 q w# [' q& ~
authorization: `${config.contractAccount.name}@owner` //更改账户权限需要使用 owner 权限
});/ t6 {1 [! f, \& _: q
console.log(r);( h5 E, N3 ~. [
部署合约: G) ~4 G) V/ M( G6 M/ R
const FIBOS = require('fibos.js');/ i T6 P( r0 y' x) U: l: W
. Q3 {6 T: q6 p2 r
const config = require('./config');
const fibosClient = FIBOS(config.client);
const fs = require('fs');9 @1 I' ]/ P1 K1 A( g+ y! z4 S5 X
3 }% u1 V$ H4 ~5 q3 I
// setcode
, ^5 ~* v! h5 `8 T& n% g
const jsCode = fs.readTextFile(`${__dirname}/contracts/subaccount.js`);
fibosClient.setcodeSync(config.contractAccount.name, 0, 0, fibosClient.compileCode(jsCode));
// getcode
const code = fibosClient.getCodeSync(config.contractAccount.name, true);0 |4 U0 ^3 O* x5 P3 D% j* O
console.log('code:', code);
// setabi" g& @2 R# P# i. J8 Q3 J
( H9 g2 Y$ f. p/ l K ~
const abi = JSON.parse(fs.readTextFile(`${__dirname}/contracts/subaccount.abi`));
fibosClient.setabiSync(config.contractAccount.name, abi);& _, t4 K# X. E7 {; {7 Z5 }
转账测试% g" d1 i+ c+ b; M# a/ F; _! W; ]
我们先来写一个脚本 account.js 来查看三个账户的余额。& H% Q% ~* f, f# G( M
" _1 b5 l) p" D; G! p3 k
const FIBOS = require('fibos.js');
: |7 y7 `) L* @$ t- V
const config = require('./config');
' u. v8 j8 n1 m. z! R
const fibosClient = FIBOS(config.callClient);" T: ^9 m+ r$ a* x7 O
; d: K) ^, d, B. Z! P4 U; R" G
const account1 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account1.name, 'accounts');& X c: n7 a' Z% K) N
console.log(config.account1.name);9 S! ]. O( r3 V ]; m
5 b" k! Q I5 D1 n1 N/ i' l
console.log(account1);6 T/ J$ I+ F. S) s. E5 C+ G
0 l5 j" v% F: Q
const account2 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account2.name, 'accounts');- w8 F* p% c+ J* C
& x( n5 M; [1 V; k `3 t& g" G
console.log(config.account2.name);7 w, z9 W" R; a5 t$ ?* r ]7 l
console.log(account2);
. I/ H5 ]1 T) S( \; z1 r
const contractAccount = fibosClient.getTableRowsSync(true, 'eosio.token', config.contractAccount.name, 'accounts');2 v' Z8 e3 V: q. p3 F1 x$ y* N
console.log(config.contractAccount.name);
# x$ @& y$ f% R2 S; Z7 R) e
console.log(contractAccount);0 f# E& `; _8 y4 Z, `/ `
) l* [7 X4 j; u$ I
执行 fibos account.js 来查看三个账户信息。 目前我们的账户还没有 FO,所以大致情况是这样的:
/ t t; C/ p. V- `% _
用户账户:helloworld11 金额:0.0000 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO
0 `8 I% W- _& y7 |7 d
" b+ {1 Z# ^; ^8 c, R. K+ q. L4 o
测试网会自动给每个账户发放10 EOS 的通证用以测试使用。账户中还并没有 FO 通证。所以我们再来写一个兑换脚本,用1 EOS 换一点 FO 通证。
const FIBOS = require('fibos.js');8 i p. l- h! \
' w `1 \( k% H# u2 a! n* m9 E
const config = require('./config');
7 a% P, m0 s9 }. g8 J# l" _+ r
const fibosClient = FIBOS(config.callClient);
" K0 c4 u2 H- y c
let ctx = fibosClient.contractSync('eosio.token');# x3 _$ @- ?3 |$ I2 g( {1 }' ?5 e
+ j8 x2 L5 [- X& x7 W5 o) c
const r = ctx.exchangeSync(: P2 r, {' A7 j; r+ E, V; K: S
config.account1.name,
'1.0000 EOS@eosio',% ^' t6 O; ~; X4 W- L1 M. n% M: P
'0.0000 FO@eosio',
'exchange FO to EOS',4 s: W& Z' O8 K. k
{
authorization: config.account1.name# G4 }* h! d4 p( o( p- w1 W2 X6 P
}# |5 o9 J* B y+ X9 d/ r- d- f) }
);7 r- c$ _0 G. i* T
console.log(r)
再次执行 fibos account.js 来查看账户信息。目前我们的账户金额大致是这样的:
f( j7 n, e# X0 q7 M
用户账户:helloworld11 金额:146.4245 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO! y# `: V+ f: c( u* U
) G9 J* S4 t" c& r
下面写个脚本 transfer.js 来执行转帐操作。
const FIBOS = require('fibos.js');
, H* g, }+ D+ s& r& j
const config = require('./config');
" G) h+ a% o4 g% i9 |2 R
const fibosClient = FIBOS(config.callClient);, t+ H( Y% Y2 y9 o7 Y
let ctx = fibosClient.contractSync('eosio.token');3 ^3 l2 Q) O' r5 i6 S" C
! K# ~% Y0 Y. y! K
const r = ctx.extransferSync(
' v9 n+ x0 _* C8 D; b& ~9 |' ]
config.account1.name, // 用户账户
config.contractAccount.name, // 平台合约账户
- [. {; Y+ V) k) t
'10.0000 FO@eosio', // 转帐金额. L3 E: w' O7 P8 ]& z S3 ~
2 L7 w' ~4 `1 O6 R
config.account2.name, // 附言填写内容提供者的账户名,平台合约会给它分账
7 @# K8 ?6 d; R) [
{" B y7 X' _+ w4 y
authorization: config.account1.name //提供用户账户的授权- N$ ^: l& P+ z' E; }2 q
# ~: h# s$ S9 p) H4 ]6 m% @
}; }+ H3 U* \6 Z* M7 q. h+ c6 P7 \
; t4 e: f: a+ h: V
)0 B0 X) _7 X/ S
console.log(r)
我们要从用户账户 account1 给平台合约账户 account3 转帐 10 FO。memo 参数为要分成的内容提供者账户 account2。根据合约中定的2/8分成,平台合约账户 account3 将会分得2 FO,而内容提供者账户 account2 将会获得8 FO。5 Z* _! a- d- {" w. ^$ v( Z& H
使用命令 fibos transfer.js 执行该脚本完成转帐操作。5 E+ N, s, L+ v) W. W
1 H t9 a) W- [- S, {1 T
下面我们再来看一下目前三个账户情况。执行命令 fibos account.js。三个账户金额大致如下。
# y" u+ t3 `; p% x7 y0 y1 i
用户账户:helloworld11 金额:136.4245 FO内容提供者账户:helloworld22 金额:8.0000 FO平台合约账户:helloworld33 金额:2.0000 FO0 X" U" z& ?, o' R
) K, f) y3 U7 n- }7 | R
结果显示,分账账户和平台合约账户如预期那样获得8 FO 和2 FO。/ a. p$ {: M( U5 x6 c1 v3 V) M9 {
9 t. V& {# I/ w1 v1 [8 L
综上,我们成功使用了智能合约实现了自动分账。平台方还可以继续根据自己业务需要定制自己的合约。
文中的代码请参考:https://github.com/fengluo/fibos-subaccount
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人