初衷
简单说,我们需要一个智能合约来管理所有的分红项目(profit item)。
分红项目是一个代币分配中心:每个分红项目的创建者(creator)可以为该分红项目注册(register)分红的接收地址(receiver address)或其他可以接收分红的分红项目,并为每个接收方设定权重(weight)。之后,每次分红项目的创建者释放(release)分红时,就会为其下注册的接收方地址(可以是一个单独的账户地址,也可以是一个分红项目的虚拟地址(virtual address))按指定的权重分配代币——把待分配的代币一律转化为ELF,或者直接采取Transfer的方式打到相应的地址上,或者等待接收地址的所有人自行获取分红(profit)。每次释放分红后,该分红项目的账期(account period)加一。
我们从中提取出几个概念:
- 分红项目(profit item)。通过分红合约创建出来的区块链代币分配中心。
- 分红项目创建者(creator)。有权限为创造出来的分红项目注册分红接收地址。
- 分红的接收地址(receiver address)。一个平平无奇的AElf区块链上的用来接收分红代币的账户地址。要注意的是,该部分分红需要接收人自行获取,不会在释放分红的时候自动打到这个账户上(见“获取分红(profit)”)。
- 分红项目的虚拟地址(virtual address)。每个分红项目,都会通过其唯一标识(profit id)映射出来一个只有该分红项目的创建人可以操作的虚拟地址,这个地址仅用来释放分红,没有对应的公私钥对(碰撞出来这个地址的概率可以忽略不计)。
- 子分红项目(sub profit item)。这是一个相对的概念,每个分红项目都可能成为子分红项目。子分红项目可以被其他分红项目分配权重,这样其他分红项目在释放分红时,会为子分红项目的虚拟地址打上一笔代币。
- 获取分红(profit)。作为一个能够接收某个分红的普通用户,需要自行发送交易来获取自己应得的分红,这是为了避免被注册的接收地址过多,释放分红的交易执行超时。
- 权重(weight)。我们选择使用权重来管理每一个分红接收地址能够获取的分红的比例,即(该接收地址被分配的权重/总权重),这样会更加灵活。在必要的时候,我们会限制某一类分红项目的总权重,并把一部分权重分配给固定的(子)分红项目,以达到强行分红的目的——只要这一类分红项目释放了分红,固定的子分红项目就可以至少接收到一定比例的分红。如DApp开发者部署的合约可以选择将一定比例的分红贡献给国库(Treasury)。
- 释放(release)分红。将该分红项目虚拟地址上的余额全部通过Bancor合约转化为ELF,并Transfer给分红接收地址的过程。
- 账期(account period)。账期的时长由每个分红项目自行控制,释放分红后账期自增1。
- 国库(Treasury)。这可能是AElf区块链中最大的分红项目,它可以作为区块生产奖励、合约交易费分红、合约利润分红的子项目,也作为一般的分红项目将它虚拟地址中的余额分配给上一届产生了区块的CDC、参加了AElf竞选的验证节点VDC、参与了AElf竞选的选民等。
接口
创建分红项目:
rpc CreateProfitItem (CreateProfitItemInput) returns (aelf.Hash) {
}
...
message CreateProfitItemInput {
sint64 profit_receiving_due_period_count = 1;
bool is_release_all_balance_everytime_by_default = 2;
}
message ProfitItem {
aelf.Address virtual_address = 1;
sint64 total_weight = 2;
map<string, sint64> total_amounts = 3;// token_symbol -> total_amount
sint64 current_period = 4;
repeated SubProfitItem sub_profit_items = 7;
aelf.Address creator = 8;
sint64 profit_receiving_due_period_count = 9;
bool is_release_all_balance_everytime_by_default = 10;
bool is_treasury_profit_item = 11;
}
message SubProfitItem {
aelf.Hash profit_id = 1;
sint64 weight = 2;
}
在创建某分红项目时,可以指定receiver address获取分红的超时时间,过期会删除相应的分红信息以减少State DB的存储开销。
除此之外,在分红项目创建者释放分红的时候,可以指定本次账期释放多少分红,如果该创建者本意是每次释放分红时就将分红项目虚拟地址账面上的所有余额进行释放,可以将is_release_all_balance_everytime_by_default设置为true,释放分红时将释放额度传为0(或者不传)即可。
注册子分红项目:
rpc RegisterSubProfitItem (RegisterSubProfitItemInput) returns (google.protobuf.Empty) {
}
...
message RegisterSubProfitItemInput {
aelf.Hash profit_id = 1;
aelf.Hash sub_profit_id = 2;
sint64 sub_item_weight = 3;
}
用来添加子分红项目并分配相应的权重。
权重管理:
rpc AddWeight (AddWeightInput) returns (google.protobuf.Empty) {
}
rpc SubWeight (SubWeightInput) returns (google.protobuf.Empty) {
}
rpc AddWeights (AddWeightsInput) returns (google.protobuf.Empty) {
}
rpc SubWeights (SubWeightsInput) returns (google.protobuf.Empty) {
}
...
message AddWeightInput {
aelf.Address receiver = 1;
aelf.Hash profit_id = 2;
sint64 weight = 3;
sint64 end_period = 4;
}
message SubWeightInput {
aelf.Address receiver = 1;
aelf.Hash profit_id = 2;
}
message AddWeightsInput {
aelf.Hash profit_id = 1;
repeated WeightMap weights = 2;
sint64 end_period = 4;
}
message WeightMap {
aelf.Address receiver = 1;
sint64 weight = 2;
}
message SubWeightsInput {
repeated aelf.Address receivers = 1;
aelf.Hash profit_id = 2;
}
为了避免过多交易,应该提供批量管理权重的接口。
添加分红:
rpc AddProfits (AddProfitsInput) returns (google.protobuf.Empty) {
}
...
message AddProfitsInput {
aelf.Hash profit_id = 1;
sint64 amount = 2;
sint64 period = 3;
string token_symbol = 4;
}
用于给指定分红项目增加一定数量可用来分红的代币,什么币种都可以。当period为0(为空)时,这一笔代币会添加到分红项目的虚拟地址上(可以称之为总账)。当指定了大于0的period时,这一笔代币会添加到指定账期的账期虚拟地址上。
释放分红:
rpc ReleaseProfit (ReleaseProfitInput) returns (google.protobuf.Empty) {
}
...
message ReleaseProfitInput {
aelf.Hash profit_id = 1;
sint64 period = 2;
sint64 amount = 3;
sint64 total_weight = 4;
}
period即本次释放分红的账期,不可以跳着释放,但是有必要自己传入该释放的账期供合约检查,以防止分红项目创建人手滑发送两个同样的交易,导致分红释放两次。当amount设置为0且该分红项目创建时指定is_release_all_balance_everytime_by_default为true,则本次会释放分红项目虚拟地址上的所有余额。这里的total_weight是为分红需要延期释放的分红项目准备的,因为延期释放的分红项目的可用总权重无法使用释放时的该分红项目的总权重,只能自己将总权重设置为过去某个时间点的总权重。比如今天是6号,要给5号时注册在分红接收列表中的地址释放分红,就应该把5号时的总权重传入参数ReleaseProfitInput中。
获取分红:
rpc Profit (ProfitInput) returns (google.protobuf.Empty) {
}
...
message ProfitInput {
aelf.Hash profit_id = 1;
}
提供分红项目的唯一标识,收取自己所有能获取的分红。