當某人投資某些資產以換取某種程度的控制,影響力或參與其活動時,就會有人持有該企業的股份。
在加密貨幣世界中,這被理解為只要他們不轉讓他們擁有的某些代幣,就會給予用戶某種權利或獎勵。staking機制通常鼓勵對代幣交易進行代幣持有,而代幣交易又有望推動代幣估值。
要建立這種staking機制,我們需要:
1. staking代幣
2. 跟蹤stakes、權益持有者和回報的數據結構
3. 創建和刪除stakes的方法
4. 獎勵機制
我們繼續吧。
staking代幣
為我們的staking代幣創建ERC20代幣。稍后我將需要SafeMash和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;
}
我們現在將給予STK持有者創建和移除抵押的能力。我們將銷毀這些令牌,因為它們被標記,以防止用戶在移除標記之前轉移它們。
請注意,在創建抵押時,如果用戶試圖放置比他擁有的更多的令牌時,_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%的STK代幣獎勵。
在更復雜的合同中,當滿足某些條件時,將自動觸發獎勵的分配,但在這種情況下,我們將讓所有者手動觸發它。按照最佳實踐,我們還將跟蹤獎勵并實施一種撤銷方法。
和以前一樣,為了使代碼可讀,我們遵循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);
}
測試
沒有一套全面的測試,任何合同都不能完成。我傾向于至少為每個函數生成一個bug,但通常情況下不會發生這樣的事情。
除了允許您生成有效的代碼之外,測試在開發設置和使用智能合約的過程中也非常有用。
下面介紹如何設置和使用測試環境。我們將制作1000個STK令牌并將其交給用戶使用該系統。我們使用truffle進行測試,這使我們可以使用帳戶。
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)
);
});
在創建測試時,我總是編寫使代碼恢復的測試,但這些測試并不是很有趣。 對createStake的測試顯示了創建賭注需要做些什么,以及之后應該改變什么。
重要的是要注意在這個抵押合約中我們有兩個平行的數據結構,一個用于STK余額,一個用于抵押以及它們的總和如何通過創建和刪除抵押數量保持不變。在這個例子中,我們給用戶3個STK wei,該用戶的余額加抵押總和將始終為3。
it(‘createStake creates a stake.’, async () =》 {
await stakingToken.transfer(user, 3, { from: owner });
await stakingToken.createStake(1, { from: user });
assert.equal(await stakingToken.balanceOf(user), 2);
assert.equal(await stakingToken.stakeOf(user), 1);
assert.equal(
await stakingToken.totalSupply(),
manyTokens.minus(1).toString(10),
);
assert.equal(await stakingToken.totalStakes(), 1);
});
對于獎勵,下面的測試顯示了所有者如何激發費用分配,用戶獲得了1%的份額獎勵。
it(‘rewards are distributed.’, async () =》 {
await stakingToken.transfer(user, 100, { from: owner });
await stakingToken.createStake(100, { from: user });
await stakingToken.distributeRewards({ from: owner });
assert.equal(await stakingToken.rewardOf(user), 1);
assert.equal(await stakingToken.totalRewards(), 1);
});
當獎勵分配時,STK的總供應量會增加,這個測試顯示了三個數據結構(余額、抵押和獎勵)是如何相互關聯的。現有和承諾的STK金額將始終是創建時的金額加上獎勵中分配的金額,這些金額可能會或可能不會被鑄造。。創建時生成的STK數量將等于余額和抵押的總和,直到完成分配。
it(‘rewards can be withdrawn.’, async () =》 {
await stakingToken.transfer(user, 100, { from: owner });
await stakingToken.createStake(100, { from: user });
await stakingToken.distributeRewards({ from: owner });
await stakingToken.withdrawReward({ from: user });
const initialSupply = manyTokens;
const existingStakes = 100;
const mintedAndWithdrawn = 1;
assert.equal(await stakingToken.balanceOf(user), 1);
assert.equal(await stakingToken.stakeOf(user), 100);
assert.equal(await stakingToken.rewardOf(user), 0);
assert.equal(
await stakingToken.totalSupply(),
initialSupply
.minus(existingStakes)
.plus(mintedAndWithdrawn)
.toString(10)
);
assert.equal(await stakingToken.totalStakes(), 100);
assert.equal(await stakingToken.totalRewards(), 0);
});
結論
抵押和獎勵機制是一種強大的激勵工具,復雜程度根據我們自身設計相關。 erc20標準和safemath中提供的方法允許我們用大約200行代碼對其進行編碼。
文章來源:區塊鏈研究實驗室?
評論
查看更多