Skip to main content

ACS10 - Dividend Pool Standard

ACS10 facilitates the creation and management of dividend pools within a contract.

Interface

To create a dividend pool, implement these optional interfaces:

Methods

Method NameRequest TypeResponse TypeDescription
Donateacs10.DonateInputgoogle.protobuf.EmptyTransfers tokens from the caller to the dividend pool. Converts non-native tokens to native tokens if required.
Releaseacs10.ReleaseInputgoogle.protobuf.EmptyReleases dividends based on the specified period number.
SetSymbolListacs10.SymbolListgoogle.protobuf.EmptySets the list of token symbols supported by the dividend pool.
GetSymbolListgoogle.protobuf.Emptyacs10.SymbolListRetrieves the list of token symbols supported by the dividend pool.
GetUndistributedDividendsgoogle.protobuf.Emptyacs10.DividendsQueries the balance of undistributed tokens according to the symbol list.
GetDividendsgoogle.protobuf.Int64Valueacs10.DividendsQueries dividend information based on the specified height.

Types

acs10.Dividends

FieldTypeDescriptionLabel
valueDividends.ValueEntryThe dividends, symbol -> amount.repeated

acs10.Dividends.ValueEntry

FieldTypeDescriptionLabel
keystring
valueint64

acs10.DonateInput

FieldTypeDescriptionLabel
symbolstringThe token symbol to donate.
amountint64The amount to donate.

acs10.DonationReceived

FieldTypeDescriptionLabel
fromaelf.AddressThe address of donors.
pool_contractaelf.AddressThe address of dividend pool.
symbolstringThe token symbol Donated.
amountint64The amount Donated.

acs10.ReleaseInput

FieldTypeDescriptionLabel
period_numberint64The period number to release.

acs10.SymbolList

FieldTypeDescriptionLabel
valuestringThe token symbol list.repeated

aelf.Address

FieldTypeDescriptionLabel
valuebytes

aelf.BinaryMerkleTree

FieldTypeDescriptionLabel
nodesHashThe leaf nodes.
rootHashThe root node hash.repeated
leaf_countint32The count of leaf node.

aelf.Hash

FieldTypeDescriptionLabel
valuebytes

aelf.LogEvent

FieldTypeDescriptionLabel
addressAddressThe contract address.
namestringThe name of the log event.
indexedbytesThe indexed data, used to calculate bloom.repeated
non_indexedbytesThe non indexed data.repeated

aelf.MerklePath

FieldTypeDescriptionLabel
merkle_path_nodesMerklePathNodeThe merkle path nodes.repeated

aelf.MerklePathNode

FieldTypeDescriptionLabel
hashHashThe node hash.
is_left_child_nodeboolWhether it is a left child node.

aelf.SInt32Value

FieldTypeDescriptionLabel
valuesint32

aelf.SInt64Value

FieldTypeDescriptionLabel
valuesint64

aelf.ScopedStatePath

FieldTypeDescriptionLabel
addressAddressThe scope address.
pathStatePathThe path of contract state.

aelf.SmartContractRegistration

FieldTypeDescriptionLabel
categorysint32The category of contract code (0: C#).
codebytesThe byte array of the contract code.
code_hashHashThe hash of the contract code.
is_system_contractboolWhether it is a system contract.
versionint32The version of the current contract.

aelf.StatePath

FieldTypeDescriptionLabel
partsstringThe partial path of the state path.repeated

aelf.Transaction

FieldTypeDescriptionLabel
fromAddressThe address of the sender of the transaction.
toAddressThe address of the contract when calling a contract.
ref_block_numberint64The height of the referenced block hash.
ref_block_prefixbytesThe first four bytes of the referenced block hash.
method_namestringThe name of a method in the smart contract at the To address.
paramsbytesThe parameters to pass to the smart contract method.
signaturebytesWhen signing a transaction, subset of fields: from/to, target method, parameter, reference block number, prefix.

aelf.TransactionExecutingStateSet

FieldTypeDescriptionLabel
writesTransactionExecutingStateSet.WritesEntryThe changed states.repeated
readsTransactionExecutingStateSet.ReadsEntryThe read states.repeated
deletesTransactionExecutingStateSet.DeletesEntryThe deleted states.repeated

aelf.TransactionExecutingStateSet.DeletesEntry

FieldTypeDescriptionLabel
keystring
valuebool

aelf.TransactionExecutingStateSet.ReadsEntry

FieldTypeDescriptionLabel
keystring
valuebool

aelf.TransactionExecutingStateSet.WritesEntry

FieldTypeDescriptionLabel
keystring
valuebytes

aelf.TransactionResult

FieldTypeDescriptionLabel
transaction_idHashThe transaction id.
statusTransactionResultStatusThe transaction result status.
logsLogEventThe log events.repeated
bloombytesBloom filter for transaction logs.repeated
return_valuebytesThe return value of the transaction execution.
block_numberint64The height of the block that packages the transaction.
block_hashHashThe hash of the block that packages the transaction.
errorstringFailed execution error message.

aelf.TransactionResultStatus

NameNumberDescription
NOT_EXISTED0The execution result of the transaction does not exist.
PENDING1The transaction is in the transaction pool waiting to be packaged.
FAILED2Transaction execution failed.
MINED3The transaction was successfully executed and packaged into a block.
CONFLICT4When executed in parallel, there are conflicts with other transactions.
PENDING_VALIDATION5The transaction is waiting for validation.
NODE_VALIDATION_FAILED6Transaction validation failed.

Usage

ACS10 provides a standardized interface for dividend pools, independent of aelf chain interactions.

Implementation

  • Using the Profit Contract
State.ProfitContract.Value =
Context.GetContractAddressByName(SmartContractConstants.ProfitContractSystemName);
var schemeToken = HashHelper.ComputeFrom(Context.Self);
State.ProfitContract.CreateScheme.Send(new CreateSchemeInput
{
Manager = Context.Self,
CanRemoveBeneficiaryDirectly = true,
IsReleaseAllBalanceEveryTimeByDefault = true,
Token = schemeToken
});
State.ProfitSchemeId.Value = Context.GenerateId(State.ProfitContract.Value, schemeToken);
  • Using the TokenHolder Contract
State.TokenHolderContract.Value =
Context.GetContractAddressByName(SmartContractConstants.TokenHolderContractSystemName);
State.TokenHolderContract.CreateScheme.Send(new CreateTokenHolderProfitSchemeInput
{
Symbol = Context.Variables.NativeSymbol,
MinimumLockMinutes = input.MinimumLockMinutes
});
return new Empty();
  • Donate can be implemented as:
public override Empty Donate(DonateInput input)
{
State.TokenContract.TransferFrom.Send(new TransferFromInput
{
From = Context.Sender,
Symbol = input.Symbol,
Amount = input.Amount,
To = Context.Self
});
State.TokenContract.Approve.Send(new ApproveInput
{
Symbol = input.Symbol,
Amount = input.Amount,
Spender = State.TokenHolderContract.Value
});
State.TokenHolderContract.ContributeProfits.Send(new ContributeProfitsInput
{
SchemeManager = Context.Self,
Symbol = input.Symbol,
Amount = input.Amount
});
Context.Fire(new DonationReceived
{
From = Context.Sender,
Symbol = input.Symbol,
Amount = input.Amount,
PoolContract = Context.Self
});
var currentReceivedDividends = State.ReceivedDividends[Context.CurrentHeight];
if (currentReceivedDividends != null && currentReceivedDividends.Value.ContainsKey(input.Symbol))
{
currentReceivedDividends.Value[input.Symbol] =
currentReceivedDividends.Value[input.Symbol].Add(input.Amount);
}
else
{
currentReceivedDividends = new Dividends
{
Value =
{
{
input.Symbol, input.Amount
}
}
};
}
State.ReceivedDividends[Context.CurrentHeight] = currentReceivedDividends;
Context.LogDebug(() => string.Format("Contributed {0} {1}s to side chain dividends pool.", input.Amount, input.Symbol));
return new Empty();
}
  • The method Release directly sends the TokenHolder’s method DistributeProfits transaction:
public override Empty Release(ReleaseInput input)
{
State.TokenHolderContract.DistributeProfits.Send(new DistributeProfitsInput
{
SchemeManager = Context.Self
});
return new Empty();
}
  • GetSymbolList returns the symbol list recorded in dividend scheme:
public override SymbolList GetSymbolList(Empty input)
{
return new SymbolList
{
Value =
{
GetDividendPoolScheme().ReceivedTokenSymbols
}
};
}
private Scheme GetDividendPoolScheme()
{
if (State.DividendPoolSchemeId.Value == null)
{
var tokenHolderScheme = State.TokenHolderContract.GetScheme.Call(Context.Self);
State.DividendPoolSchemeId.Value = tokenHolderScheme.SchemeId;
}
return Context.Call<Scheme>(
Context.GetContractAddressByName(SmartContractConstants.ProfitContractSystemName),
nameof(ProfitContractContainer.ProfitContractReferenceState.GetScheme),
State.DividendPoolSchemeId.Value);
}
  • Implementation of GetUndistributedDividends returns the balance (same as previous section):
public override Dividends GetUndistributedDividends(Empty input)
{
var scheme = GetDividendPoolScheme();
return new Dividends
{
Value =
{
scheme.ReceivedTokenSymbols.Select(s => State.TokenContract.GetBalance.Call(new GetBalanceInput
{
Owner = scheme.VirtualAddress,
Symbol = s
})).ToDictionary(b => b.Symbol, b => b.Balance)
}
};
}

Test

Testing includes sending Donate, Release transactions, and querying operations. Example:

  • Define the required Stubs:
const long amount = 10_00000000;
var keyPair = SampleECKeyPairs.KeyPairs[0];
var address = Address.FromPublicKey(keyPair.PublicKey);
var acs10DemoContractStub =
GetTester<ACS10DemoContractContainer.ACS10DemoContractStub>(DAppContractAddress, keyPair);
var tokenContractStub =
GetTester<TokenContractContainer.TokenContractStub>(TokenContractAddress, keyPair);
var tokenHolderContractStub =
GetTester<TokenHolderContractContainer.TokenHolderContractStub>(TokenHolderContractAddress,
keyPair);
  • Approve the TokenHolder contract and the dividend pool contract
await tokenContractStub.Approve.SendAsync(new ApproveInput
{
Spender = TokenHolderContractAddress,
Symbol = "ELF",
Amount = long.MaxValue
});
await tokenContractStub.Approve.SendAsync(new ApproveInput
{
Spender = DAppContractAddress,
Symbol = "ELF",
Amount = long.MaxValue
});
  • Lock the position to reducethe account balance by 10 ELF:
await tokenHolderContractStub.RegisterForProfits.SendAsync(new RegisterForProfitsInput
{
SchemeManager = DAppContractAddress,
Amount = amount
});
  • Implement Donate to reduce the account balance by another 10 ELF:
await acs10DemoContractStub.Donate.SendAsync(new DonateInput
{
Symbol = "ELF",
Amount = amount
});
  • Test the GetUndistributedDividends and GetDividends:
// Check undistributed dividends before releasing.
{
var undistributedDividends =
await acs10DemoContractStub.GetUndistributedDividends.CallAsync(new Empty());
undistributedDividends.Value["ELF"].ShouldBe(amount);
}
var blockchainService = Application.ServiceProvider.GetRequiredService<IBlockchainService>();
var currentBlockHeight = (await blockchainService.GetChainAsync()).BestChainHeight;
var dividends =
await acs10DemoContractStub.GetDividends.CallAsync(new Int64Value {Value = currentBlockHeight});
dividends.Value["ELF"].ShouldBe(amount);
  • Release bonus, and test GetUndistributedDividends again:
await acs10DemoContractStub.Release.SendAsync(new ReleaseInput
{
PeriodNumber = 1
});
// Check undistributed dividends after releasing.
{
var undistributedDividends =
await acs10DemoContractStub.GetUndistributedDividends.CallAsync(new Empty());
undistributedDividends.Value["ELF"].ShouldBe(0);
}
  • Account will receive the dividend and will change the balance:
var balanceBeforeClaimForProfits = await tokenContractStub.GetBalance.CallAsync(new GetBalanceInput
{
Owner = address,
Symbol = "ELF"
});
await tokenHolderContractStub.ClaimProfits.SendAsync(new ClaimProfitsInput
{
SchemeManager = DAppContractAddress,
Beneficiary = address
});
var balanceAfterClaimForProfits = await tokenContractStub.GetBalance.CallAsync(new GetBalanceInput
{
Owner = address,
Symbol = "ELF"
});
balanceAfterClaimForProfits.Balance.ShouldBe(balanceBeforeClaimForProfits.Balance + amount);

Example

Implementing ACS10 facilitates building dividend pools on main and side chains.