ACS8 - Transaction Resource Fee Standard

ACS8与ACS1有一些类似,都是用来收取交易费的标准。

不同点在于,ACS1向用户收取交易费,ACS8向被调用的合约收取交易费,且ACS8收取的交易费为指定的四种代币:WRITE,READ,NET,TRAFFIC。

也就是说,如果一个合约声明自己继承自ACS8,每个目标合约为该合约的交易都会根据交易实际执行的数据,对该合约收取数额不定的四种资源币。

接口

acs8.proto文件中仅定义了一个方法:

  • BuyResourceToken,参数为该Proto文件中定义的BuyResourceTokenInput类型。
message BuyResourceTokenInput {
    string symbol = 1;
    int64 amount = 2;
    int64 pay_limit = 3; // No buy if paying more than this, 0 if no limit
}

这个方法可以用来购买四种资源币中的一种,消耗的是该合约账户中的ELF余额(可以自行充值,也可以收取用户的ELF代币作为盈利,做到自给自足)。

当然,开发者自行购买资源币,然后直接通过Token合约的Transfer方法转到这个合约的地址也是可以的,因此这个方法并不是必须实现。

应用

继承自ACS1的合约在向用户收取手续费时,利用的是针对被收费交易产生的方法名为ChargeTransactionFees的pre-plugin交易。

而继承自ACS8的合约在向合约收资源币时,由于具体收费的数额是由实际执行该交易所产生的消耗决定的,因此利用的是针对被收费交易产生的post-plugin交易(即这个新产生的交易在原始交易之后执行),方法名为ChargeResourceToken。

ChargeResourceToken的实现也类似ChargeTransactionFees:

public override Empty ChargeResourceToken(ChargeResourceTokenInput input)
{
    Context.LogDebug(() => string.Format("Start executing ChargeResourceToken.{0}", input));
    if (input.Equals(new ChargeResourceTokenInput()))
    {
        return new Empty();
    }
    var bill = new TransactionFeeBill();
    foreach (var pair in input.CostDic)
    {
        Context.LogDebug(() => string.Format("Charging {0} {1} tokens.", pair.Value, pair.Key));
        var existingBalance = GetBalance(Context.Sender, pair.Key);
        Assert(existingBalance >= pair.Value,
            string.Format("Insufficient resource of {0}. Need balance: {1}; Current balance: {2}.", pair.Key, pair.Value, existingBalance));
        bill.FeesMap.Add(pair.Key, pair.Value);
    }
    foreach (var pair in bill.FeesMap)
    {
        Context.Fire(new ResourceTokenCharged
        {
            Symbol = pair.Key,
            Amount = pair.Value,
            ContractAddress = Context.Sender
        });
        if (pair.Value == 0)
        {
            Context.LogDebug(() => string.Format("Maybe incorrect charged resource fee of {0}: it's 0.", pair.Key));
        }
    }
    return new Empty();
}

具体每种资源币该收多少,都在AElf.Kernel.FeeCalculation这个项目中被计算好了,具体来说,就是在token_contract.proto中定义了一个名为CalculateFeeCoefficients的数据结构,其作用是保存一个多项式的所有系数,每三个数字为一组,如a、b、c,表示(b/c)*x^a。然后将这些系数作为CalculateFunction类型的属性之一,结合四种资源币分别针对的交易执行所实际消耗的资源,利用CalculateFeeCoefficients配置好的系数计算多项式,最终把计算结果作为ChargeResourceToken的参数,构造出这个post-plugin交易。

除此之外,已欠费的合约的方法在合约重新充值之前不能执行,因此加了一个pre-plugin交易,类似ACS5的pre-plugin交易,对合约的资源币余额进行检查,交易名为CheckResourceToken:

public override Empty CheckResourceToken(Empty input)
{
    foreach (var symbol in Context.Variables.GetStringArray(TokenContractConstants.PayTxFeeSymbolListName))
    {
        var balance = GetBalance(Context.Sender, symbol);
        var owningBalance = State.OwningResourceToken[Context.Sender][symbol];
        Assert(balance > owningBalance,
            string.Format("Contract balance of {0} token is not enough. Owning {1}.", symbol, owningBalance));
    }
    return new Empty();
}