使用智能合约实现自动分账
哈哈笑417
发表于 2023-1-4 14:36:20
119
0
0
文中代码已在 GitHub 上开源。https://github.com/fengluo/fibos-subaccount
+ y# A5 u& {3 [! g
设计思路% K$ l8 U+ q; f& l. Y j
在 FIBOS 转账是通过 token 合约的extransfer方法来实现的。extransfer方法在执行的时候会给转账方账户和入账方账户发送通知。所以用户给平台方账户转账的时候,平台账户就会收到通知。所以整体业务逻辑如下:
quantity: 10 FO
memo: 内容提供者账户 quantity: 8 FO
9 l) |5 n, L/ n0 M3 z$ T
用户账户 -------------------> 平台账户 ----------------> 内容提供者账户
4 R& m; ?+ x/ `0 D6 H1 a
extransfer 2/8 分成 extransfer; H4 x( F0 B9 F
1.用户给平台方账户转账,memo 中填写内容提供者的账户名。
4 X* O+ u* g% w
2.平台方的账户合约监听 extransfer 方法的通知,然后做出分账计算,给对应内容提供者的账户转账对应金额。2 c7 C T0 i t, h. T
1 S) m, K e) E/ @, I% o, |
整体逻辑很简单,整个合约代码逻辑差不多用20行就可以写完。
; \8 ]5 L3 o( {( d6 {( u
编写合约
FIBOS 的智能合约分为 ABI 文件和 JS 合约两部分。ABI 相当于合约接口,JS 合约则是功能实现。本案例目前没有接口设计需求,不过 ABI 文件还是合约不可缺少的部分。所以我们简单创建一下就好。# H) z1 y8 {3 ^
我们先创建一个 contracts 文件夹,合约文件都会放在这里。然后在此文件夹下,创建 subaccount.abi 文件,内容为:) t9 k. |% [' Q! n4 X
3 H; S$ X% w f7 D0 K
{
"version": "eosio::abi/1.0"
" \! N# p" J" _
}
' i9 U" @/ H, [% z- m
JS 合约部分也没有太复杂。在 contracts 文件夹下创建 subaccount.js 文件,代码为:$ y8 n8 v/ D# L3 j
, ^8 D1 `5 y' {6 B( x; D/ Z1 I
exports.on_extransfer = (from, to, quantity, memo) => {0 ?- @* Q+ e$ F8 ]! I! l2 F( `" A
// 需要在开头做一些判断: Z* i& l) I, B. G$ e4 p
) P9 U+ o' ~5 G2 N/ O+ l
if (to === action.receiver && action.is_account(memo)) {# {" O$ G2 n" ]
const num = parseInt(quantity.quantity.split(' ')[0])
. a3 I+ m, |. ?- L |
// 假设我们约定平台方跟内容提供者是2/8分成。
const subnum = (num * 0.8).toFixed(4);
trans.send_inline('eosio.token', 'extransfer', { K& s+ P P# m# v
/ R3 U& T9 B! G3 s9 n
from: to,
9 l1 Q4 s* ~6 B
to: memo,
quantity: {
5 U5 ~0 k1 O& Q% H/ K
quantity: `${subnum} ${quantity.quantity.split(' ')[1]}`,
2 g! n* S3 I h$ A/ p+ ~
contract: quantity.contract0 `( Z8 [7 \+ m
9 K7 y' e" V; O5 ?
},
memo: 'sub account'6 {9 ~& k4 }3 d
},
' ~4 [8 F J1 z) W" l
[
{
// 需要提供合约账户的 active 权限
actor: action.receiver,
* G6 _$ C; ^. U9 ?
permission: 'active'
}; e+ `# }5 W' p3 q3 P1 P
+ h: F( R! @2 R% @. d" l' s% i! t
]);
7 o3 y& E) Q$ r# ^/ o
}
& c7 T! S$ F- \% {) ~
}. n9 P7 I9 F, x
合约代码开头我们需要做一些验证。
1.收款方的账户为合约账户,否则因为下面代码执行给内容提供者转账时,因为转帐方也是合约账号会再次收到通知,造成无限递归,超出最大 send_inline 层数而报错。6 [# W5 }' o) b
2.我们用 memo 参数来放内容提供者的账户,所以我们需要对此参数校验一下该账户是否存在防止打错。
合约代码中我们使用 send_inline 调用 eosio.token 合约来执行转帐操作。转帐操作需要对应账户的 active 权限才能执行。为了解决权限滥用问题,FIBOS 定义了一个特殊权限 eosio.code。我们需要在平台合约账户中配置权限,在 active 权限下添加该合约账户的 eosio.code 授权。具体的配置操作会在下面说明。
8 Y5 H* Q J3 p3 a/ G. M3 T
在 FIBOS TestNet 上注册账号
为方便测试,我们在测试网 http://testnet.fibos.fo 上注册三个账户。8 Q) \8 h2 r. z& K0 e1 c/ L1 a
用户账号 helloworld11- e# k( L; S( H0 T0 ~
& Z K8 [3 V7 B' i( i6 K
内容提供者账号 helloworld22
% I4 t( `0 j. r8 |2 {3 Z
平台合约账号 helloworld33
我们需要记录这三个账号的账户名以及公私钥。以便下面的开发使用。创建一个统一的配置文件来记录这些数据:
9 |- t M: t q0 U: ]
const config = {
+ d, E- h5 H# S6 s
// 平台合约账户的客户端配置8 v5 H: k& _7 [5 f; F
6 y4 x/ l/ f* o3 J! } X
client: {
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',: \3 f! _+ s5 W. c/ @: n- M4 ^* ?
' t8 ^( H1 n6 z
httpEndpoint: 'http://testnet.fibos.fo',0 L, x- L! C+ L7 M6 V7 Z
- q) e0 |& H9 R2 m& w1 z( r
keyProvider: 'PRIVATE_KEY_OF_helloworld33'
},) [* h9 m [* E' B
" y1 H+ H9 W4 m& q2 O' Q0 j6 U0 y
// 用户账户的客户端配置
callClient:{# D4 W( Q+ ?) Q9 {
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',
7 q5 l6 E" U E
httpEndpoint: 'http://testnet.fibos.fo'," R, R5 p5 x+ g0 T
keyProvider: 'PRIVATE_KEY_OF_helloworld11'
* y( x* e' h0 \) x' E; N" Y
},
9 G; c7 T' h; D- Q' n
// 平台合约账户信息
9 {) y- }& e! I5 Q- ~/ I
contractAccount: {+ \9 `5 o c$ r, h
name: 'helloworld33',7 }+ _1 `; S5 e9 B' l; V# z% m
publicKey: 'PUBLIC_KEY_OF_helloworld33',
privateKey: 'PRIVATE_KEY_OF_helloworld33'; f6 |7 j+ K7 j [# B1 X5 m
},
// 用户账户信息% l( |% q/ R, \3 Z p6 i
2 L) C! \9 F' b* {
account1: {; x6 F; Z9 Z' O! W8 U! m, e% n
6 ~ y4 b' Q" p: ~
name: 'helloworld11',
publicKey: 'PUBLIC_KEY_OF_helloworld11',
p3 D/ L' M. A/ y6 I$ l4 u
privateKey: 'PRIVATE_KEY_OF_helloworld11'
' i7 U. o; x1 z( u$ j7 O/ m
},+ ]" g3 C3 Q1 z/ m7 D) I
8 I; O$ z4 b3 f" T) j% m, C* J: C
// 内容提供者账户信息
account2: {
name: 'helloworld22',0 n5 a9 A: \8 X
# Q8 D; i, p0 t# d- h* h
publicKey: 'PUBLIC_KEY_OF_helloworld22',# U% s6 M- A$ I# a% O
privateKey: 'PRIVATE_KEY_OF_helloworld22'7 b3 W" @0 T! d7 m! o- z. C' }
}
! Y8 ~, v, x3 K" s0 |0 L
}$ d: V- _- f8 _3 \" C2 j# N2 i
4 {! I) Z3 T+ Y/ r0 ^! Z$ j- y
module.exports = config
. U% I. m2 c4 \$ ~. e
配置权限
在合约代码中,我们调用了 trans.send_inline 函数调用合约 eosio.token 来实现转帐操作,但是转帐操作是需要账户的 active 权限。所以我们需要更新一下合约账户的权限,需要添加调用者的 eosio.code 授权到它的 active 权限。这个调用者自然也是这个合约账户。/ f; [7 F1 {9 G. @
const FIBOS = require('fibos.js'); @1 n, r6 b) M: a& ?; O* b; i
/ {% ~8 q8 m: m; G9 G
const config = require('./config');
const fibosClient = FIBOS(config.client);
let ctx = fibosClient.contractSync('eosio');
var r = ctx.updateauthSync({, m% T; o. ?# l5 N! ?& j7 G
account: config.contractAccount.name,
0 h2 L- [6 k5 Q7 R% E, b" g8 r
permission: 'active',
parent: 'owner',
( b w# `, J+ ^2 \7 l
auth: {
) Z2 g: g; L7 Y4 \
threshold: 1,# P9 k2 H- D2 S, d5 e
keys: [{1 {. H/ z0 E8 T" _& E
' r/ V7 t( x6 h- d3 g3 l
key: config.contractAccount.publicKey,) Z3 O" X6 O: o( p( ?; x8 y' A2 P* @
weight: 1
) f; O+ \7 s" D0 }$ m
}],6 [; g- A- j. K( h& l
accounts: [{( ~0 E/ B8 D4 P
* y' E2 X* l$ i$ @! A
permission: {2 b/ u2 ~9 [" }9 b! z* O3 x
// 将调用者账号的 eosio.code 授权添加到它的 active 权限下。( L" l: n4 H6 ~/ B
& k& B; [- A- U" G/ c5 Q9 @* V
actor: config.contractAccount.name,2 e& v6 V, J, H9 J, v
* M; z0 l H, T9 f
permission: 'eosio.code'
3 K, K( u, J1 v& A7 z. {
},
* t) E n0 P" _/ B
weight: 1
( O) @+ i* Z% P
}]- u- p& D9 w% Q
/ J" G) V- ?. F9 }
}
) v7 i% ]. d: t6 l1 S& G0 j% Z
},{
authorization: `${config.contractAccount.name}@owner` //更改账户权限需要使用 owner 权限* s) ]' {: k' u0 x
});
console.log(r);. I+ Z$ r; x( M9 z, V3 |" f
部署合约
G$ I2 r7 _1 R' @1 w2 M& }) o* B9 O# V
const FIBOS = require('fibos.js');- a' P g1 y5 l$ m- ?* ]
const config = require('./config');8 a6 W# K- J( j& G0 T6 D
const fibosClient = FIBOS(config.client);
const fs = require('fs');
3 B6 ]6 p' q) ^
// setcode( D$ s" j& g- Z5 h5 Y: E9 h& b
8 n+ W O% m$ k* L8 r' J& [1 Q
const jsCode = fs.readTextFile(`${__dirname}/contracts/subaccount.js`);* I6 L/ |2 d, S' A
fibosClient.setcodeSync(config.contractAccount.name, 0, 0, fibosClient.compileCode(jsCode));
// getcode
const code = fibosClient.getCodeSync(config.contractAccount.name, true);$ | W! Y0 k& x% _9 c9 u9 H( R3 P' Q
console.log('code:', code);
/ f& r- i5 R+ w; b$ |0 k4 [
// setabi7 R. W1 k; s' R( T: D7 @: _
6 e" z# G' R3 q, b
const abi = JSON.parse(fs.readTextFile(`${__dirname}/contracts/subaccount.abi`));
fibosClient.setabiSync(config.contractAccount.name, abi);2 q4 Q% J8 ~0 V- e
转账测试
我们先来写一个脚本 account.js 来查看三个账户的余额。
7 A" c c4 A# z" {
const FIBOS = require('fibos.js');
0 ?) o) D( w& S( e9 f. h- \, U
const config = require('./config');
const fibosClient = FIBOS(config.callClient);
const account1 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account1.name, 'accounts');. W2 `; B2 @& O& k' V
1 k1 S2 f9 p/ ~( e0 l
console.log(config.account1.name);' G- m6 q7 i' X9 o
console.log(account1);
9 ^5 C% c9 R2 E9 J( Z
const account2 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account2.name, 'accounts');2 y9 _' \! h2 Z8 X3 U8 U
" l7 y3 D6 \! K* a% I" O
console.log(config.account2.name);
3 G6 q+ m8 t% o0 d9 ^
console.log(account2);
const contractAccount = fibosClient.getTableRowsSync(true, 'eosio.token', config.contractAccount.name, 'accounts');4 O$ F% ?" a1 M4 h' T3 b4 s
console.log(config.contractAccount.name);. T P( j3 O+ o8 |& }( X$ L' ~
console.log(contractAccount);
f5 z: K q( d7 k; w
执行 fibos account.js 来查看三个账户信息。 目前我们的账户还没有 FO,所以大致情况是这样的:
用户账户:helloworld11 金额:0.0000 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO
测试网会自动给每个账户发放10 EOS 的通证用以测试使用。账户中还并没有 FO 通证。所以我们再来写一个兑换脚本,用1 EOS 换一点 FO 通证。
* R6 V% _( K* Y% E
const FIBOS = require('fibos.js');
const config = require('./config');( k: F d# S e: Q
const fibosClient = FIBOS(config.callClient);
6 P9 Y% K1 {; w0 b7 I
let ctx = fibosClient.contractSync('eosio.token');
const r = ctx.exchangeSync(
config.account1.name,
& i" f4 | j. J& L. z
'1.0000 EOS@eosio',9 U4 ^" Y6 a v" ]5 d
- s3 i* i# S8 v
'0.0000 FO@eosio',
1 \6 u) s7 Z: h0 G6 H9 }( K
'exchange FO to EOS',4 L8 R/ C- B+ O1 r; e
{
authorization: config.account1.name
}) _" Y9 [& v% v. j# B% w) a
# ]6 m" T- k3 s" }& Z. a/ j3 a
);# u4 m" U6 r( n# j' Q, H% M6 w
: _! T4 P }8 c4 ]5 |' x- v7 A
console.log(r)1 q7 {: Y4 y* D+ R
再次执行 fibos account.js 来查看账户信息。目前我们的账户金额大致是这样的:
用户账户:helloworld11 金额:146.4245 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO5 o5 m: P8 P5 _5 X
下面写个脚本 transfer.js 来执行转帐操作。
1 v# t; ^# Z2 E1 s, z; m/ X
const FIBOS = require('fibos.js');
const config = require('./config');1 W# ~) E, W3 a8 b6 S* l
const fibosClient = FIBOS(config.callClient);) ~! Z; ^/ ~6 U# U# C
let ctx = fibosClient.contractSync('eosio.token');9 u3 O0 t0 W$ C R5 W u
const r = ctx.extransferSync(
config.account1.name, // 用户账户
! L7 c! B: f, o! N. F2 Y' ]
config.contractAccount.name, // 平台合约账户 E3 Z% o* }4 q( N, p
4 ]3 V' U7 `7 K
'10.0000 FO@eosio', // 转帐金额& B" r% ]. h2 ]3 K; w
3 X1 S, u( N. t$ w- z* W
config.account2.name, // 附言填写内容提供者的账户名,平台合约会给它分账5 j6 q" Y+ i9 o9 b
1 J% a) ^, _# @* B2 Z6 A2 W0 u
{9 D& K' U/ Y# I4 K6 z
authorization: config.account1.name //提供用户账户的授权
}
)6 ^+ k' r2 O- m( B& w( y
console.log(r)
+ y$ P1 O. l4 U. H# m" E8 `7 M
我们要从用户账户 account1 给平台合约账户 account3 转帐 10 FO。memo 参数为要分成的内容提供者账户 account2。根据合约中定的2/8分成,平台合约账户 account3 将会分得2 FO,而内容提供者账户 account2 将会获得8 FO。
- I5 r- b( Y: i
使用命令 fibos transfer.js 执行该脚本完成转帐操作。' J4 S) c5 `6 j9 M/ }- u3 f
下面我们再来看一下目前三个账户情况。执行命令 fibos account.js。三个账户金额大致如下。$ C4 F& ^1 T& U& c# m w3 R% f$ c
用户账户:helloworld11 金额:136.4245 FO内容提供者账户:helloworld22 金额:8.0000 FO平台合约账户:helloworld33 金额:2.0000 FO
0 B* z2 p' r4 u( m
结果显示,分账账户和平台合约账户如预期那样获得8 FO 和2 FO。
- P4 y+ B! N6 ^! l$ ~+ U8 u9 D0 d
' J! n7 S+ K. K
综上,我们成功使用了智能合约实现了自动分账。平台方还可以继续根据自己业务需要定制自己的合约。$ p, Y1 Q O1 E7 g6 u
0 u" ^- [/ u+ `7 C4 v6 J1 R5 W8 U
文中的代码请参考:https://github.com/fengluo/fibos-subaccount
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人