Technical documentation - smart contract
Glossary
"Sallar" - name of the token
"Dust" - name of the smallest unit of the token. 1 Sallar is equal to 100,000,000 Dusts
"BP" - Block Part, a name for the point awarded to the user for participating in solving a block. BP is converted to the final block reward expressed in Dusts
"BI" - Block Index
"Boost" - boost takes the form of a reward multiplier obtained by including virtual requests which are contained in the equation of base request. Therefore, Boost takes the form of a reward multiplier. Boost system is a contract client feature
"Request" - demand sent by the user through the contract's client (Sallar app), in the form of a user's record in a data package containing information related to the user's identification on the blockchain to finalize a reward payout
Libraries
anchor-lang version “0.27.0”
anchor-spl version “0.27.0”
Introduction
With the implementation of the Sallar project stages, phases related to the evolution of tokenomics over time are distinguished.
The contract life cycle is divided into two phases:
Token minting
Redistribution of minted tokens
The second phase will begin after the completion of the first phase.
Phase 1 - Token Minting
In this phase, new tokens are minted and become the subject of distribution to users participating in this phase. The tokens are assigned to blocks, the total number of which is 2,600,000 and the blocks are arranged in a single chain in such a way that access to the next block must be preceded by the distribution of the content of the previous block. Each block stores 20,000 tokens.
Tokens for a given block are always minted when access to that block is unlocked, which occurs at the level of the smart contract logic, and the distribution of 20,000 tokens to users occurs when the block is solved - i.e., when the content of the block is covered by authorized requests generated by users through an application that is a client of the contract and oversees this process.
Users participating in submitting requests for the reward contained in a given block (solving the block) receive the tokens. In the above process, a total of 52,000,000,000 tokens are distributed. Additionally, a one-time minting of 2,600,000,000 tokens is performed, which are intended for purposes related to the early project policy. Token minting for the organization can only be performed once according to the contract assumptions.
The maximum number of Sallar tokens that can be minted is 54,600,000,000.
Block solving mechanism
The block chain consists of 2,600,000 blocks. Each block is assigned a number - these are consecutive integers: 1, 2, 3, etc., up to 2,600,000. The block chain can be solved from both sides simultaneously - this is called top-bottom solving (mining) and bottom-top solving (staking).
Blocks are solved through requests submitted by users - each request has a certain number of BP (Block Part), which means it has a certain weight according to which the corresponding reward is determined, covering a portion of the block entitled to receive the minted tokens.
Each block has a certain number of BP assigned to it (this value is different for each block)
Each user generates a request that covers a portion of the BP of the block. When users' requests cover the total number of BP for a given block, the block is solved
Solving a block is equivalent to minting 20,000 tokens, which are distributed to users whose requests were registered on the solved block. Due to the limitation in the smart contract logic, blocks within groups (top or bottom) cannot be solved more frequently than every 3 minutes. It is possible to solve a top and a bottom block in parallel, but at least 3 minutes must pass between the solving of two top blocks or two bottom blocks.
Block solving from the top - mining
Top blocks start from block number 1. When block 1 is solved, the system switches to block number 2, then block number 3, and so on until all blocks are exhausted. Top blocks can be solved by all users who have performed a request supported by the completion of a task assigned by the contract client (PoA). The task system and user qualification for solving top blocks is the responsibility of the contract client - the contract only receives users who are eligible to participate in block solving. The reward for participating in top block solving (user request) can take one of two values:
A standard value: 1 req => 1 req • 1 BP/req • 1000 = 1000 BP
An increased value: 1 req => (1 req + boost) • 1000, where the boost value is calculated based on the block number
The request has an increased value when it is boosted. Any user with sufficient resources in their account (GAS) can purchase the ability to activate a boost for their next request using GAS. The mechanism for activating boosts and charging fees in GAS is not a part of the contract and is implemented by the contract client's application.
Block solving from the bottom - staking
The bottom blocks start from block number 2,600,000. When block 2,600,000 is solved, the switch is made to block number 2,599,999, then block number 2,599,998, and so on until all blocks are exhausted. Bottom blocks can only be solved by users who have at least 20,000 Sallar tokens. The verification of users' account balances is performed on the client contract side, which rejects users who do not meet the above conditions. Additionally, the contract verifies the wallet balance provided in the request, and for users who do not meet the minimum required balance, the reward value is calculated as 0 Dust.
The user's request for solving blocks from the bottom can have one of two values:
standard BP value equal to the number of tokens owned by the user,
increased value: the number of tokens owned by the user multiplied by the number equal to (1 BP + boost value on the given block).
A request has an increased value when it is a boost request.
Block solving phase process
At the initialization of the contract, 20,000 tokens are minted to the top and bottom distribution accounts, both owned by the contract. These accounts store tokens for current blocks. After solving a block, the appropriate portion of the minted tokens is transferred to the wallets of users participating in solving the block.
The next step is to launch the initial token distribution function, which mints 2,600,000,000 tokens and sends them to the account of the organization managing Sallar.
The final stage of the phase is to begin parallel block-solving from the top and bottom. Each solved block results in the minting of 20,000 tokens to the appropriate distribution accounts - top or bottom.
Blocks are solved until a collision occurs between them, i.e., when the top and bottom blocks differ by one number, and switching any of them to the next one would result in both sides having the same number. When a block collision occurs and both the last top block and the last bottom block are solved, this phase ends.
User rest
User requests may be worth more than the remaining tokens on the block. In such cases the user is given the reward in two separate transfers: the first transfer consists of all tokens remaining on the current block and the second transfer with missing tokens is sent when the next block is solved.
The missing part of the reward that was not available on the current block and needs to be taken from the next block is named user rest.
Due to the nature of the processing of the blocks, only one user rest can exist at any given time for the top block and only one for the bottom block.
It results from the fact that only one user may not receive a full reward for a given block and the user gets the transfer for the next solved block as the first one - before any other users (that participated in the next block’s solving) are given their rewards. As the top and the bottom blocks are processed separately - the user rests for the top blocks and for the bottom blocks are also independent of each other.
The reward is based on BP so the reward may be slightly different compared to the situation when the user gets the whole reward on the current block.
Example: if the user’s requests are worth 5000 BP but there are 3000 BP available on the current block, then the user receives the first transfer for 3000 BP on the current block and 2000 BP on the next block.
It gives the same amount of tokens as in the situation when the user participates in the current block solving and the requests are worth exactly 3000 BP and then the user participates in the solving of the next block and the requests for the next block are worth 2000 BP.
So the user may receive a slightly different amount of tokens compared to the situation when the current block has all 5000 BP available. However, the difference will be very minimal and it’s actually irrelevant.
User rest could be so large that it could solve several blocks. In such a situation the scenario looks like this:
The user is given the remaining tokens from the current block.
The user waits 3 minutes before solving the next block.
The next block solving process is started. As the user's rest is worth more BP than all BP on the block, the user receives all tokens (20 000 Sallars) from the next block. The user’s rest is updated - its value is decreased by all the block’s BP.
Steps 2 and 3 are repeated until the user’s rest is worth less than all BP on the block.
The user is given a reward for the left user’s rest.
Processing is continued as usual - the next incoming requests are processed as usual.
One more edge case here is that at the end of Phase 1 user may lose the tokens for the rest - if the last block is being processed and the user’s requests are worth more than the available BP on the block - then the user is given the remaining tokens on the block and nothing more as there are no more blocks which means that there are no more tokens to mint. This situation may happen only twice in the whole lifecycle of the contract - once for top blocks and once for bottom blocks.
Execution of the top block solving function
The contract client prepares and sends batches of orders to the contract. Each batch contains several orders which are processed individually and sequentially in the contract.
Verification of the possibility of running the function:
Check the data of the account calling the function - whether it is a signer of the transaction and the owner of the contract (i.e., the same account that initialized the contract),
Check whether the solving of the top blocks has not ended (collision with the bottom block), i.e., whether there are any BP on the current top block,
Check if the required time (3 min.) has elapsed since the previous top block was solved.
Check if user info contains any data - it must not be empty
Retrieval of the current state of the top block.
Processing of user rest from the previous block if any rest exists:
Verification of the current block - it must be a new one, i.e. no requests should be processed on the block beforehand and all tokens should still be available on the block.
Verification of the first user info in the user info list - the first entry must be defined for the user who should receive the rest. Whenever there is an unprocessed user’s rest, the first user info is reserved for the information about the address that should receive the rest - it is an additional validation between the client and the contract and the client confirms awareness of the user rest this way. Information about the number of requests in such user info is ignored as the reward for the user rest is calculated based on the stored user rest information so only address matters here.
Verification of the remaining accounts list - it must contain the account that should receive the rest.
Calculation of the transfer amount for the user rest.
Transfer of the tokens for the user rest.
Update of the current state of the block.
Update of the user rest to clear it if it is exhausted or to decrease the already used BP from the user rest.
Update of the user info list to to exclude the first user info entry which was used for the user rest processing.
Calculation of BP value for a request with boost for the current top block.
For each user sent in the input data to the function (each entry of user info):
Verification if there are still any available BP in the current block.
Verification of the correctness of the data sent to the function in order to associate the account from the remaining accounts (information about the token account address) with the data necessary to calculate the number of tokens to be sent to that token account (current user info entry).
Update of user’s rest - it is set to zero if the user request’s BP does not exceed the available block BP, otherwise, it is set to the difference between the user request’s BP and the available blocks BP.
Verification if the user is the last one in the block, i.e., if the value of their request's BP is equal to or greater than the number of BP remaining in the current block. If so, they receive all remaining tokens in the block, which protects against leaving token remnants resulting from rounding in calculations.
Transfer of the due funds to the user.
Update of the current state of the block.
Update of the account’s address that participated in block solving most recently.
Switch to the next block if the current one has been solved and there is no block collision.
Update of information about block collision stored in the contract - this information can only change when the current block is solved, otherwise, it will not change.
Execution of the bottom block solving function
Verification of the possibility of running the function:
Check the data of the account calling the function - whether it is a signer of the transaction and the owner of the contract (i.e., the same account that initialized the contract),
Check whether the solving of the bottom blocks has not ended (collision with the bottom block), i.e., whether there are any BP on the current top block,
Check if the required time (3 min.) has elapsed since the previous bottom block was solved.
Check if user info contains any data - it must not be empty
Retrieval of the current state of the bottom block.
Processing of user rest from the previous block if any rest exists:
Verification of the current block - it must be a new one, i.e. no requests should be processed on the block beforehand and all tokens should still be available on the block.
Verification of the first user info in the user info list - the first entry must be defined for the user who should receive the rest. Whenever there is an unprocessed user’s rest, the first user info is reserved for the information about the address that should receive the rest - it is an additional validation between the client and the contract and the client confirms awareness of the user rest this way. Information about the number of requests in such user info is ignored as the reward for the user rest is calculated based on the stored user rest information so only address matters here.
Verification of the remaining accounts list - it must contain the account that should receive the rest.
Calculation of the transfer amount for the user rest.
Transfer of the tokens for the user rest.
Update of the current state of the block.
Update of the user rest to clear it if it is exhausted or to decrease the already used BP from the user rest.
Update of the user info list to to exclude the first user info entry which was used for the user rest processing.
Calculation of BP value for a request with boost for the current bottom block.
For each user sent in the input data to the function (each entry of user info):
Verification if there are still any available BP in the current block.
Verification of the correctness of the data sent to the function in order to associate the account from the remaining accounts (information about the token account address) with the data necessary to calculate the number of tokens to be sent to that token account (current user info entry).
Update of user’s rest - it is set to zero if the user request’s BP does not exceed the available block BP, otherwise, it is set to the difference between the user request’s BP and the available blocks BP.
Verification if the user is the last one in the block, i.e., if the value of their request's BP is equal to or greater than the number of BP remaining in the current block. If so, they receive all remaining tokens in the block, which protects against leaving token remnants resulting from rounding in calculations.
Transfer of the due funds to the user.
Update of the current state of the block.
Update of the account’s address that participated in block solving most recently.
Switch to the next block if the current one has been solved and there is no block collision.
Update of information about block collision stored in the contract - this information can only change when the current block is solved, otherwise, it will not change.
Calculating reward
Each user can send requests with boost (user_request_with_boost) or without boost (user_request_without_boost). The reward is calculated based on the number of BP earned by the user in the current block.
Reward reduction mechanism
Each block has a different, increasing number of BP. The increase in the number of BP per block leads to a reduction in the value of the reward for a single BP, as there are always 20,000 Sallars available per block. The number of BP on each block is calculated using the formula:
where:
BI - Block Index (block_index)
FBP =20,000 represents BP number available on first block
RR = 0.99999430521433, controls reward reduction rate max_BP takes integer values.
The rounding used is the HALF_UP rule commonly used in finance (implemented as the round_hu() function).
(based on European Comission: The Introduction of the Euro and the rounding of currency amounts, Brussels, DGII/C-4-SP(99), II/28/99-EN)
The reward value (in Dust) for a single BP is calculated by dividing the number of Dusts per block by the number of BP available on that block.
where:
BI - Block Index
dusts_per_block = 2,000,000,000,000 (20,000 Sallar)
Reward for block solving
Depending on the dynamics of the process of solving successive blocks, a user can register their request on a single block multiple times. In addition, some registered requests may include a boost, while others may not. The final reward (total_amount) that a user receives for participating in solving a particular block is the sum of all rewards for individual requests, with rewards for requests without boost (amount_without_boost) and with boost (amount_with_boost) calculated separately.
Individual rewards (in Dust) are calculated by multiplying the number of BP due for a given request type by the dust_per_BP calculated for the current block. Each individual reward associated with a request is rounded to full Dusts using the HALF_UP rounding rule (function round_hu()). The components of the total amount in Dusts (total_amount) due for all user requests within the block are calculated using the following formulas:
where:
req_without_boost - user requests registered on the current block without boost
req_with_boost - user requests registered on the current block with boost enabled
BP_without_boost - number of BP calculated according to the rules described in the "Calculating BP for request" section)
BP_with_boost - number of BP taking into account the base value of 1 BP and additional BP resulting from the applied boost (calculation rules in the "Calculating BP for request with boost" section)
The total number of BP owned by the user (total_bp) is deducted from the BP available on the current block.
Calculating BP for requests
The final BP numbers assigned to the user for participating in the block are calculated using separate algorithms for the top and bottom blocks.
BP reward for top Block
For requests without an active "boost" option, the base BP amount (top_BP_without_boost) is constant and equals 1 BP
For requests with an active "boost" option, the final BP amount (top_BP_with_boost) is calculated by adding the base value of 1 BP to a certain number of BP, depending on the block index (rounded_boost)
The boost system for the top block assumes the following assumptions:
Boost is available from block 250 and has a value of 1 (0.5 rounded using the HALF_UP rounding rule).
The maximum boost value is 60,000 and is achieved on the last block.
The boost value increases exponentially with the block index.
Boost is an integer number.
Taking into account the above assumptions of the mechanism, the boost for the top block is described by the following function:
where:
BI - Block Index
boost_increment = 1.000004498927 - is a value selected to meet the above assumptions.
The calculated number of boosts is rounded so that the final number of boosts (rounded_boost) is an integer number. The following rounding rules for boosts have been adopted:
If base_boost < 100, the final value is rounded to the nearest integer according to the HALF_UP rounding rule
If 100 <= base_boost < 1000, the final value is rounded to a multiple of 10
If 1000 <= base_boost, the final value is rounded down to a multiple of 100
Reward for the bottom block
The determination of BP for the top block is based on the balance of the user's wallet set in the application (specifically, their token account holding Sallar). Only full Sallars are taken into account in the calculations according to the following rules:
For requests without the "boost" option active, the due (base) number of BP (bottom_BP_without_boost) is equal to the number of full Sallars in the user's registered wallet.
For requests with the "boost" option active, the final number of BP (bottom_BP_with_boost) is calculated as a multiple of the number of full Sallars in the user's wallet set in the application. Bottom_BP_with_boost includes the base value of bottom_BP_without_boost and the part resulting from the applied boost. The following assumptions were made in the calculations:
Boost is available from block 206267 and then assumes the value of 1.
The maximum value of boost is 60 and it is available on the last block.
The value of boost grows exponentially with the block index.
The final boost (rounded_boost) is an integer.
Taking into account the above assumptions of the mechanism, the boost for the top block is described by the following function:
where:
BI - Block Index
boost_reduction = 0.999997999992 - is a value chosen to meet the above assumptions.
The final number of boosts (rounded_boost) is calculated by rounding base_boost to the nearest integer according to the HALF_UP rounding rule.
The BP (Block Part) value taking into account the base reward and boost is calculated using the following formula:
Phase 2 - Token redistribution
After the end of phase 1 (i.e., solving the last bottom block and the last top block and no possibility of switching to the next block), phase 2 begins.
In this phase, blocks cannot be solved anymore, but two functions are available that were not in phase 1:
final mining,
final staking.
Each of the above functions redistributes tokens from wallets dedicated to those functions. Tokens can be placed in these wallets by anyone, such as the organization managing Sallar, but also by Sallar users.
In this phase, there is no possibility of mining new tokens - only previously mined tokens can be transferred between wallets.
Final mining
Each user can participate in final mining. The number of tokens received in this process depends on the state of the final mining account at the time of the user's request submission to final mining. The contract client accepts such a request, records it along with the state of the final mining account at the time of the request, and sends information to the contract about the state of the final mining account for each request. This information is needed to calculate the tokens that the user will receive as part of final mining, as the state of the final mining account determines the number of tokens to be transferred.
Final mining mechanism
Verification of the possibility of executing the function:
checking the data of the calling account - whether it is a signer of the transaction and the owner of the contract (the same account that initialized the contract),
checking if phase 1 has ended (whether the upper and lower blocks are solved) - whether there was a collision of blocks, whether the remaining BP for the current upper block is equal to zero, and whether the remaining BP for the current lower block is equal to zero.
For each user account passed in the input data to the function:
Verification of the correctness of the data sent to the function to associate information about the token account address (obtained from the user account) with the data needed to calculate the number of tokens to be sent to that account.
Initialization of the total transfer amount for the user to zero.
For each request sent in the input data to the function associated with the currently processed user's token account address:
Calculation of the number of tokens due to the user for this request based on the final mining account balance, which is part of the user info (information about the request).
Update of the total transfer amount for the user by adding the calculated number of tokens for the request.
Transfer of the due funds to the user.
Calculation of the transfer amount within "final mining"
The transfer amount depends on the final mining account balance. The thresholds determining the final mining account balance (on the left) and the transfer amount due (on the right) are shown below:
≤124,999,999 Sallar = 25 Sallar
125,000,000 - 249,999,999 Sallar = 50 Sallar
250,000,000 - 499,999,999 Sallar = 100 Sallar
500,000,000 - 999,999,999 Sallar = 250 Sallar
≥1,000,000,000 Sallar = 500 Sallar
Final Staking
Each user can participate in the final staking process. The number of tokens received during this process is different for each user and depends on the state of the final staking account at the beginning of the current round and the portion of the total pool allocated for transfers to the user.
Any number of users can participate in a single final staking process, but the sum of each user's share of the total reward pool must be equal to 1. There must be a minimum of 20 hours between the end of one final staking process and the start of the next.
Mechanism of operation of final staking
Verification of the ability to run the function:
Verification of the calling account's data - whether it is the signer of the transaction and the owner of the contract (i.e., the same account that initialized the contract).
Verification if phase 1 has ended (the upper and lower blocks are split) - whether there is a collision of blocks, whether the remaining BP for the current upper block is zero, and whether the remaining BP for the current lower block is zero.
Verification if the required time has passed since the last final staking - minimum 20 hours.
Check if the previous final staking has ended. If yes - a new final staking is started, i.e., the reward pool for the current final staking is calculated and saved (the total number of tokens to be transferred for the entire current final staking), the number of remaining tokens to be transferred for the current final staking is initialized as the value of the entire pool, and the remaining part of the total pool is initialized as the value 1. Additionally, the end date of the final staking is saved.
Verification if the sum of values for each user account that determines the user's share of the total reward pool is equal to 1.
Verification if the entire reward pool for the current final staking has not been used up.
For each user account sent in the input data to the function (containing a token account):
Verification if there is at least one linkage between the user account and user info, i.e., information about the reward portion due to the user in the final staking.
For each user info (information about the reward portion due to the current token account):
Verification if the appropriate portion of the pool (equal to or greater than the reward portion due to the token account) is still available for the current final staking.
Calculation of the transfer amount for the user.
Verification of the final staking account balance to ensure that there are enough tokens to transfer to the user.
Transfer of the due funds to the user.
Update of the current final staking account balance and the remaining reward pool available for the current final staking.
Calculation of the transfer amount in the "final staking"
The transfer amount depends on the final staking account balance. The reward pool for the final staking is calculated as 0.001 multiplied by the current balance of the final staking account. The transfer amount for an individual user is calculated as their reward portion multiplied by the value of the total pool.
Last updated