اجرای استیکینگ در سالیدیتی

مقدمه
یک فرد زمانی در یک سرمایهگذاری سهیم است که در ازای انجام سطح خاصی از کنترل، نفوذ یا مشارکت در فعالیتها، دارایی را وارد کند. در دنیای ارزهای دیجیتال، استیکینگ به معنای دادن نوعی حق یا پاداش به کاربران تا زمانی است که توکنهای در اختیارشان را منتقل نکنند.
در واقع مکانیسم استیکینگ مشوق نگهداری توکن نسبت به خرج کردن آن است و انتظار می رود که این مکانیسم ارزش توکن را افزایش دهد. ما معتقدیم که دانش برای به اشتراک گذاشتن بوجود آمده، و قصد داریم در این مقاله نحوه پیادهسازی مکانیزم استیکینگ در سالیدیتی را نشان دهیم.
جهت ساخت مکانیسم استیکینگ به موارد زیر نیاز داریم :
- یک توکن استیک
- ساختارهای داده برای پیگیری سهام، سهامداران و پاداش ها.
- روش های ایجاد و حذف سهام.
- یک سیستم پاداش
برای کسب اطلاعات بیشتر می توانید به مقاله آموزش ساخت سایت استیک در سالیدیتی مراجعه کنید.
توکن استیک
یک توکن استیک می تواند به عنوان یک توکن ERC20 ایجاد شود. بعداً به SafeMath و Ownable نیاز خواهیم داشت، بنابراین اجازه دهید آنها را وارد کرده و استفاده کنیم.
pragma solidity ^0.5.0;import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";/**
* @title Staking Token (STK)
* @author Alberto Cuesta Canada
* @notice Implements a basic ERC20 staking token with incentive distribution.
*/
contract StakingToken is ERC20, Ownable {
using SafeMath for uint256; /**
* @notice The constructor for the Staking Token.
* @param _owner The address to receive all tokens on construction.
* @param _supply The amount of tokens to mint on construction.
*/
constructor(address _owner, uint256 _supply)
public
{
_mint(_owner, _supply);
}
همین، چیز دیگری نیاز نیست.
ذینفعان
در این پیاده سازی، ما می خواهیم ذینفعان را پیگیری و دنبال کنیم که بعداً بتوانیم وجود انگیزه های قوی را توجیه کنیم. در تئوری، نمیتوان آنها را مانند توکن معمولی ERC20 ردیابی کرد، اما در عمل، اطمینان از اینکه ذینفعان در صورت عدم ردیابی آنها، سیستم توزیع را بازی نمی دهند، دشوار است.
برای پیاده سازی ما فقط از یک آرایه پویا از آدرس ذینفعان استفاده می کنیم.
/**
* @notice We usually require to know who are all the stakeholders.
*/
address[] internal stakeholders;
به کمک روشهای زیر می توانید یک ذینفع را اضافه و یا حذف کنید و بررسی کنید که آیا یک آدرس به یک ذینفع تعلق دارد یا خیر. مطمئناً دیگر روش های کارآمد پیاده سازی امکان پذیر است، اما از نظر من این روش خوانایی مناسبی دارد.
/**
* @notice A method to check if an address is a stakeholder.
* @param _address The address to verify.
* @return bool, uint256 Whether the address is a stakeholder,
* and if so its position in the stakeholders array.
*/
function isStakeholder(address _address)
public
view
returns(bool, uint256)
{
for (uint256 s = 0; s < stakeholders.length; s += 1){
if (_address == stakeholders[s]) return (true, s);
}
return (false, 0);
} /**
* @notice A method to add a stakeholder.
* @param _stakeholder The stakeholder to add.
*/
function addStakeholder(address _stakeholder)
public
{
(bool _isStakeholder, ) = isStakeholder(_stakeholder);
if(!_isStakeholder) stakeholders.push(_stakeholder);
} /**
* @notice A method to remove a stakeholder.
* @param _stakeholder The stakeholder to remove.
*/
function removeStakeholder(address _stakeholder)
public
{
(bool _isStakeholder, uint256 s) = isStakeholder(_stakeholder);
if(_isStakeholder){
stakeholders[s] = stakeholders[stakeholders.length - 1];
stakeholders.pop();
}
}
سهام توکن استیک
یک سهام در ساده ترین شکل خود باید اندازه سهام و دارنده سهام را ثبت کند. یک پیاده سازی کاملا ساده از این قالب می تواند فقط ردیابی آدرس ذینفع به اندازه سهام باشد.
/**
* @notice The stakes for each stakeholder.
*/
mapping(address => uint256) internal stakes;
من میخواهم نام توابع ERC20 را دنبال و معادلهایی ایجاد کنم که بتوانم دادهها را از نگاشت سهام بدست بیاورم.
/**
* @notice A method to retrieve the stake for a stakeholder.
* @param _stakeholder The stakeholder to retrieve the stake for.
* @return uint256 The amount of wei staked.
*/
function stakeOf(address _stakeholder)
public
view
returns(uint256)
{
return stakes[_stakeholder];
} /**
* @notice A method to the aggregated stakes from all stakeholders.
* @return uint256 The aggregated stakes from all stakeholders.
*/
function totalStakes()
public
view
returns(uint256)
{
uint256 _totalStakes = 0;
for (uint256 s = 0; s < stakeholders.length; s += 1){
_totalStakes = _totalStakes.add(stakes[stakeholders[s]]);
}
return _totalStakes;
}
اکنون باید به دارندگان استیک قابلیت ایجاد و حذف سهام را بدهیم. ما توکنها را بعد از استیک شدن، میسوزانیم، تا مانع از انتقال آن توسط کاربران قبل از حذف استیک شویم.
لطفاً توجه داشته باشید، اگر در هنگام ایجاد استیک کاربر سعی کند توکنهای بیشتر از دارایی خود به اشتراک بگذارد، burn_ برگردانده میشود، و در صورت حذف استیک، اگر تلاشی جهت حذف توکنهای بیشتر ازآنچه استیک شده، انجام شود، بهروزرسانی نگاشت سهام اعاده می شود.
در نهایت، ما از addStakeholder و removeStakeholder برای ثبت دارندگان استیک استفاده می کنیم چرا که این اطلاعات بعداً در سیستم پاداش مورد استفاده قرار می گیرد.
/**
* @notice A method for a stakeholder to create a stake.
* @param _stake The size of the stake to be created.
*/
function createStake(uint256 _stake)
public
{
_burn(msg.sender, _stake);
if(stakes[msg.sender] == 0) addStakeholder(msg.sender);
stakes[msg.sender] = stakes[msg.sender].add(_stake);
} /**
* @notice A method for a stakeholder to remove a stake.
* @param _stake The size of the stake to be removed.
*/
function removeStake(uint256 _stake)
public
{
stakes[msg.sender] = stakes[msg.sender].sub(_stake);
if(stakes[msg.sender] == 0) removeStakeholder(msg.sender);
_mint(msg.sender, _stake);
}
پاداش توکن استیک
مکانیسمهای پاداش را می توان به اشکال مختلف پیادهسازی کرد و اجرای آن می تواند بسیار سنگین باشد. برای این قرارداد، ما یک نسخه بسیار ساده را پیاده سازی خواهیم کرد که در آن ذینفعان به طور دوره ای پاداشی معادل 1٪ از سهام خود بصورت توکن استیک دریافت می کنند.
در قراردادهای پیچیده تر، زمانی که شرایط خاصی برآورده می شود توزیع پاداش به طور خودکار آغاز می شود، اما در اینجا به مالک اجازه می دهیم آن را به صورت دستی فعال کند. با پیروی از یک روش مناسب، پاداش ها را پیگیری و رویکردی را برای برداشتن آنها اجرا می کنیم.
مانند قبل، برای خوانا کردن کد، از قراردادهای نامگذاری ERC20.sol، ابتدا ساختار داده و روش های مدیریت داده پیروی کرده ایم:
/**
* @notice The accumulated rewards for each stakeholder.
*/
mapping(address => uint256) internal rewards;
/**
* @notice A method to allow a stakeholder to check his rewards.
* @param _stakeholder The stakeholder to check rewards for.
*/
function rewardOf(address _stakeholder)
public
view
returns(uint256)
{
return rewards[_stakeholder];
} /**
* @notice A method to the aggregated rewards from all stakeholders.
* @return uint256 The aggregated rewards from all stakeholders.
*/
function totalRewards()
public
view
returns(uint256)
{
uint256 _totalRewards = 0;
for (uint256 s = 0; s < stakeholders.length; s += 1){
_totalRewards = _totalRewards.add(rewards[stakeholders[s]]);
}
return _totalRewards;
}
مرحله بعد روش های محاسبه، توزیع و برداشت پاداش توکن استیک
/**
* @notice A simple method that calculates the rewards for each stakeholder.
* @param _stakeholder The stakeholder to calculate rewards for.
*/
function calculateReward(address _stakeholder)
public
view
returns(uint256)
{
return stakes[_stakeholder] / 100;
} /**
* @notice A method to distribute rewards to all stakeholders.
*/
function distributeRewards()
public
onlyOwner
{
for (uint256 s = 0; s < stakeholders.length; s += 1){
address stakeholder = stakeholders[s];
uint256 reward = calculateReward(stakeholder);
rewards[stakeholder] = rewards[stakeholder].add(reward);
}
} /**
* @notice A method to allow a stakeholder to withdraw his rewards.
*/
function withdrawReward()
public
{
uint256 reward = rewards[msg.sender];
rewards[msg.sender] = 0;
_mint(msg.sender, reward);
}
تست کردن توکن استیک
هیچ قراردادی بدون آزمایشات جامع تکمیل نمی شود. من قصد دارم برای هر تابع یک باگ ایجاد کنم، و اغلب رویکردها آنطور که ما فکر می کنم عمل نمی کنند.
تست کردن علاوه بر اینکه به شما امکان میدهد کدی تولید کنید که فعال است، در توسعه فرآیند تنظیم و استفاده از قراردادها نیز بسیار مفید است. من همیشه مستندات شروع کار را طبق کدی می نویسم که محیط آزمایش را بقرار می کند.
نحوه راه اندازی و استفاده از محیط تست را دنبال می کند. ما 1000 توکن استیک را ضرب می کنیم و در اختیار کاربران قرار می دهیم تا با سیستم بازی کنند. ما از ترافل، که حساب هایی جهت تست در اختیار ما قرار می دهد، استفاده می کنیم.
contract('StakingToken', (accounts) => {
let stakingToken;
const manyTokens = BigNumber(10).pow(18).multipliedBy(1000);
const owner = accounts[0];
const user = accounts[1]; before(async () => {
stakingToken = await StakingToken.deployed();
}); describe('Staking', () => {
beforeEach(async () => {
stakingToken = await StakingToken.new(
owner,
manyTokens.toString(10)
);
});
هنگام ایجاد تست، من همیشه تستهایی را مینویسم که باعث رجوع کد میشوند، اما دیدن آنها چندان جالب نیست. تست ایجاد استیک نشان می دهد که برای ایجاد یک سهام چه کاری باید انجام شود و چه چیزی باید پس از آن تغییر کند.
توجه به، وجود دو ساختار داده موازی، یکی برای بالانس استیک و دیگری برای استیک در این قرارداد استیکینگ، بسیار مهم است و اینکه چگونه مجموع آنها در طی ایجاد و حذف استیک ثابت می ماند. در این مثال ما سه STK wei به کاربر می دهیم. مجموع موجودی به علاوه استیک برای آن کاربر، همیشه 3 خواهد بود.
دیدگاه ها