Profit合约

circle-info

这个文档中的Profit合约以feature/change-voting-option-profits分支为准,因为dev上的版本有bug(在延迟释放分红那块)……

名词解释

英文
解释
详情

Profit Scheme

分红方案

分红方案定义一个分红池的分配方式,通过添加分红受益者和相应的权重。分红受益者(广义的)也可以是另一个分红方案。Profit合约的逻辑围绕分红方案展开。

Beneficiary

分红受益者(侠义的)

能够领取分红池中分红的aelf账户(需要手动领取)。

Sub Scheme

子分红方案

当某一个分红方案添加为另一个分红方案的受益者的时候,可以称为子分红方案(释放分红的时候,分红会自动打入子分红方案的虚拟地址中)。

Scheme Virtual Address

分红方案的虚拟地址

分红方案的资金都保管在一个虚拟地址中,Profit合约的逻辑保证了只有释放该分红的时候,这个虚拟地址中的资金才有可能被打出去。

Scheme Period Virtual Address

分红方案的分期虚拟地址

分红都是按期(Period)释放的,Beneficiary的分红也是按期领取的(在每一期的权重或权重占比可能都不一样),因此在释放分红的时候,实际上的处理方式是把属于某一期的分红打进这一期的虚拟地址,这样这一期的Beneficiary只能从该分期虚拟地址中领取,同时也实现了另外一个feature:他人可以仅就某一期进行贡献(Contribute),而非只能把贡献的代币打入总账(就是非分期的虚拟地址)。

Contribute

贡献分红

为某一个分红方案捐赠一些代币作为未来的分红

Distribute

释放分红

-

先看一个分红方案(Scheme)的结构:

message Scheme {
    // The virtual address of the scheme.
    aelf.Address virtual_address = 1;
    // The total weight of the scheme.
    int64 total_shares = 2;
    // The manager of the scheme.
    aelf.Address manager = 3;
    // The current period.
    int64 current_period = 4;
    // Sub schemes information.
    repeated SchemeBeneficiaryShare sub_schemes = 5;
    // Whether you can directly remove the beneficiary.
    bool can_remove_beneficiary_directly = 6;
    // Period of profit distribution.
    int64 profit_receiving_due_period_count = 7;
    // Whether all the schemes balance will be distributed during distribution each period.
    bool is_release_all_balance_every_time_by_default = 8;
    // The is of the scheme.
    aelf.Hash scheme_id = 9;
    // Delay distribute period.
    int32 delay_distribute_period_count = 10;
    // Record the scheme's current total share for deferred distribution of benefits, period -> total shares.
    map<int64, int64> cached_delay_total_shares = 11;
    // The received token symbols.
    repeated string received_token_symbols = 12;
}
字段
备注

virtual_address

属于一个分红方案的虚拟地址,姑且称之为分红方案的总账地址,因为每一次释放分红,都是从该地址将代币打出去,分红池的收入也可以打到该地址。

total_shares

分红方案的总权重。

manager

分红方案的管理者。

current_period

分红方案当前是第几期,从1开始,每释放一次自增1。

sub_schemes

分红方案的子分红方案列表,每个子分红方案都有自己的权重,在释放分红的时候,会根据子分红方案的权重直接打到相应子分红方案的虚拟地址(也就是总账)中。

can_remove_beneficiary_directly

标识该分红方案是否可以直接删除受益者。

profit_receiving_due_period_count

如果Beneficiary超过几期没有领取分红,就不让领了。默认为10。

is_release_all_balance_every_time_by_default

如果为true,未来释放分红的时候,如果不指定释放多少钱,就把总账里面的余额完全释放掉。

scheme_id

这个分红方案的唯一标识

delay_distribute_period_count

延期释放的意思是,释放的分红来自当前的总账,但是依据的是曾经某一期的权重。如果设定为1,那么第一期分红会直接销毁,第二期依据第一期的总权重释放分红,以此类推。

cached_delay_total_shares

如果delay_distribute_period_count不为0,这个字段就用于缓存往期权重。

received_token_symbols

由于分红方案存在Contribute接口,捐赠的分红可能是任意类型的Token,所以加了这个字段保存一下这个分红方案共计收到过哪些类型Token的捐赠(也就是分红池里有哪些类型的Token)。

创建分红方案 - CreateScheme

根据input构造Scheme实例,然后以SchemeId为key,存入State.SchemeInfos

  • ProfitReceivingDuePeriodCount如果不填则默认为10,上限为1024。

  • CurrentPeriod初始化为1。

完成Scheme的存储后,为了便于Manager找到自己创建的Scheme,加了一个State.ManagingSchemeIds,存储一个CreatedSchemeIds结构:

这样,以后Manager可以使用GetManagingSchemeIds接口查询自己创建过的所有的分红方案。

管理分红受益人权重

重点关注ProfitDetail的结构,我们姑且称之为分红详情:

管理分红受益者,实质上就是管理与某一个Scheme关联的ProfitDetail。

  • start_period:可以开始领取分红的期数(Scheme的期数)。

  • end_period:终止领取分红的期数,如果在添加ProfitDetail的时候input.EndPeriod为0,则设置为long.MaxValue

  • shares:占用该分红方案的权重。实际领取到的分红amount = total_amount * shares / total_shares。

  • is_weight_removed:一个标记,标记这一个分红详情是否已经被取消了。

  • id:这个分红详情的唯一标识。

添加和删除受益人/子分红方案,本质上都是在操作这个数据结构。领取分红也以这个结构中的数据为准。

管理分红受益人 - AddBeneficiary / RemoveBeneficiary

分红受益人的权重的维护使用的结构:

beneficiary就是受益人的地址,shares是这个受益人对应的权重。这个方法大致做了几件事:

  1. 维护一下对应scheme的TotalShares,增加input.BeneficiaryShare.Shares;

  2. 根据input构造一个ProfitDetail实例,插入State.ProfitDetailsMap里,后者存的是某个分红受益人针对一个scheme的ProfitDetail列表,因为可能不止有一个分红详情,而且每一个分红详情都有不同的起始和终止的期数。

RemoveBeneficiary是上述的镜像操作,但是需要额外注意两件事。

第一,如果scheme的CanRemoveBeneficiaryDirectly设置为false,那么只能移除分红已经领取完的分红详情(代码看detailsCanBeRemoved的赋值);如果CanRemoveBeneficiaryDirectly设置为true,只要此前没有被标记为权重已移除,就可以在这里移除,不管还有没有没领取完的分红。

第二,如果scheme的DelayDistributePeriodCount不为0,就需要注意在移除受益者的权重的时候,移除一下CachedDelayTotalShares中相关期数的缓存的权重。

管理子分红方案 - AddSubScheme / RemoveSubScheme

子分红方案的权重的维护使用的结构:

AddSubScheme中直接调用了AddBeneficiary方法,beneficiary字段设置的是这个子分红方案的虚拟地址。

除此之外,会在相应的scheme的SubSchemes字段中插入一个SchemeBeneficiaryShare实例,作为未来父分红方案在释放分红的时候,自动将属于子分红方案的分红打进虚拟地址的参照。

RemoveSubScheme就是把指定的子分红方案从scheme的SubSchemes中移除,TotalShares减去相应分红。

贡献分红 - ContributeProfits

如果input.Period为0,就把Token打到分红方案的虚拟地址里;否则,看这一期是否已经释放过了,没有的话,就打到分红方案的分期虚拟地址里。

如果此前这个分红方案没有收到这个类型的Token,会往scheme的ReceivedTokenSymbols里插入一下token symbol。

释放分红 - DistributeProfits

一次可以释放多种类型的Token,只需要在input.AmountsMap的keys中体现这些Token的symbol即可。

在实际释放的过程中,先根据scheme.SubSchemes把分红逐个打给子分红方案的虚拟地址(DistributeProfitsForSubSchemes方法),维护子分红方案对应的ProfitDetail(操作State.ProfitDetailsMap),最后需要顺手维护一下子分红方案的ReceivedTokenSymbols字段。子分红方案的分红安排完毕后,剩余的分红都打进分红方案的分期虚拟地址中,等待分红受益人手动领取。

领取分红 - ClaimProfits

领取分红的依据就是受益人在某个分红方案下的ProfitDetails(查State.ProfitDetailsMap)。

首先查到受益人跟该分红方案相关的所有分红详情profitDetails,据此做两次筛选:

第一次是筛选有效的分红详情availableDetails,如果这个分红详情之前有用到过(用户领取过对应分红,LastProfitPeriod不为0),说明肯定已经过了StartPeriod了,就找EndPeriod >= LastProfitPeriod的分红详情(如果不符合,说明已经领完了);如果这个分红详情之前从来没领取过相应分红,就看分红详情本身是否合理(EndPeriod >= StartPeriod)。

第二次是进一步筛选还可以领的分红详情profitableDetails,只要LastProfitPeriod小于scheme.CurrentPeriod就可以了。筛选完了以后,为了防止用户领取分红的交易执行不完(超过CodeOps步数而失败),又对每一次能领取的分红详情的数量做一个上限,目前是10(ProfitContractConstants.ProfitReceivingLimitForEachTime)。

针对每一个ProfitDetail,就是根据分红详情里记录的权重和scheme当时的总权重(记录在DistributedProfitsInfo结构里)和释放的总数量,计算出分红数额,打给分红的受益者。最后更新一下这个ProfitDetail的LastProfitPeriod字段就可以了。

Last updated