如何構(gòu)建和測試DAO智能合約

這篇文章主要介紹“如何構(gòu)建和測試DAO智能合約”,在日常操作中,相信很多人在如何構(gòu)建和測試DAO智能合約問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何構(gòu)建和測試DAO智能合約”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

站在用戶的角度思考問題,與客戶深入溝通,找到門頭溝網(wǎng)站設(shè)計與門頭溝網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設(shè)計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:網(wǎng)站制作、成都網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、域名注冊網(wǎng)站空間、企業(yè)郵箱。業(yè)務(wù)覆蓋門頭溝地區(qū)。

添加代幣

對于能夠與另一個合約進行交互的合約,它需要知道其他合約的接口——可用的函數(shù)。由于我們的TNS代幣具有相當(dāng)簡單的接口,因此我們可以將其包含在DAO的智能合約中, contract StoryDao聲明之上以及我們的import語句中加入:

contract LockableToken is Ownable {
    function totalSupply() public view returns (uint256);
    function balanceOf(address who) public view returns (uint256);
    function transfer(address to, uint256 value) public returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    function allowance(address owner, address spender) public view returns (uint256);
    function transferFrom(address from, address to, uint256 value) public returns (bool);
    function approve(address spender, uint256 value) public returns (bool);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    function approveAndCall(address _spender, uint256 _value, bytes _data) public payable returns (bool);
    function transferAndCall(address _to, uint256 _value, bytes _data) public payable returns (bool);
    function transferFromAndCall(address _from, address _to, uint256 _value, bytes _data) public payable returns (bool);

    function increaseLockedAmount(address _owner, uint256 _amount) public returns (uint256);
    function decreaseLockedAmount(address _owner, uint256 _amount) public returns (uint256);
    function getLockedAmount(address _owner) view public returns (uint256);
    function getUnlockedAmount(address _owner) view public returns (uint256);
}

請注意,我們不需要粘貼函數(shù)的“內(nèi)容”,而只需要粘貼它們的簽名(骨架)。這就是合約之間交互所需的全部內(nèi)容。

現(xiàn)在我們可以在DAO合約中使用這些函數(shù)。計劃如下:

  • 啟動代幣(我們已經(jīng)這樣做了)。

  • 從同一地址啟動DAO。

  • 將所有代幣從代幣啟動器發(fā)送到DAO,然后通過合約將所有權(quán)轉(zhuǎn)移到DAO本身。

  • 此時,DAO擁有所有代幣并可以使用發(fā)送功能將其出售給人員,或者可以使用批準(zhǔn)功能(在投票期間有用)等將其保留用于支出。

但DAO如何知道部署代幣的地址?我們告訴它。

首先,我們在DAO合約的頂部添加一個新變量:

LockableToken public token;

然后,我們添加一些函數(shù):

constructor(address _token) public {
    require(_token != address(0), "Token address cannot be null-address");
    token = LockableToken(_token);
}

構(gòu)造函數(shù)是在部署合約時自動調(diào)用的函數(shù)。它對于初始化鏈接合約,默認值等值很有用。在我們的例子中,我們將使用它來使用和保存TNS代幣的地址。require檢查是為了確保代幣的地址有效。

在我們處理它時,讓我們添加一個函數(shù),讓用戶可以檢查DAO中待售的代幣數(shù)量,以及更改為另一個代幣的函數(shù),如果出現(xiàn)問題并且需要進行此類更改。這種變化也需要一個事件,所以我們也要添加它。

event TokenAddressChange(address token);

function daoTokenBalance() public view returns (uint256) {
    return token.balanceOf(address(this));
}

function changeTokenAddress(address _token) onlyOwner public {
    require(_token != address(0), "Token address cannot be null-address");
    token = LockableToken(_token);
    emit TokenAddressChange(_token);
}

第一個函數(shù)設(shè)置為view因為它不會改變區(qū)塊鏈的狀態(tài);它不會改變?nèi)魏沃?。這意味著它是對區(qū)塊鏈的免費,只讀函數(shù)調(diào)用:它不需要付費交易。它還將標(biāo)記的余額作為數(shù)字返回,因此需要在函數(shù)的簽名上使用returns (uint256)進行聲明。代幣有一個balanceOf函數(shù)(參見我們上面粘貼的接口),它接受一個參數(shù)——要檢查其余額的地址。我們正在檢查DAO的余額,我們將“this”變成一個address()。

代幣地址更改功能允許所有者(admin)更改代幣地址。它與構(gòu)造函數(shù)的邏輯相同。

讓我們看看我們?nèi)绾巫屓藗儸F(xiàn)在購買代幣。

購買代幣

根據(jù)該系列的前一部分,用戶可以通過以下方式購買代幣:

  • 如果已經(jīng)列入白名單,請使用后備功能。換句話說,只需將以太送到DAO合約即可。

  • 使用whitelistAddress功能發(fā)送超過白名單所需的費用。

  • 直接調(diào)用buyTokens函數(shù)。

但是,有一個警告。當(dāng)有人從外部調(diào)用buyTokens函數(shù)時,如果DAO中沒有足夠的代幣可供出售,我們希望它失敗提示。但是當(dāng)有人通過白名單功能通過在第一次白名單嘗試中發(fā)送太多以太來購買代幣時,我們不希望它失敗,因為白名單處理過程將被取消。以太坊中的交易要么一切都必須成功,要么就是一無所獲。所以我們將制作兩個buyTokens函數(shù)。

// This goes at the top of the contract with other properties
uint256 public tokenToWeiRatio = 10000;

function buyTokensThrow(address _buyer, uint256 _wei) external {

    require(whitelist[_buyer], "Candidate must be whitelisted.");
    require(!blacklist[_buyer], "Candidate must not be blacklisted.");

    uint256 tokens = _wei * tokenToWeiRatio;
    require(daoTokenBalance() >= tokens, "DAO must have enough tokens for sale");
    token.transfer(_buyer, tokens);
}

function buyTokensInternal(address _buyer, uint256 _wei) internal {
    require(!blacklist[_buyer], "Candidate must not be blacklisted.");
    uint256 tokens = _wei * tokenToWeiRatio;
    if (daoTokenBalance() < tokens) {
        msg.sender.transfer(_wei);
    } else {
        token.transfer(_buyer, tokens);
    }
}

因此,存在1億個TNS代幣。如果我們?yōu)槊總€以太設(shè)置10000個代幣的價格,則每個代幣的價格降至4-5美分,這是可以接受的。

這些函數(shù)在對違禁用戶和其他因素進行完整性檢查后進行一些計算,并立即將代幣發(fā)送給買方,買方可以按照自己的意愿開始使用它們——無論是投票還是在交易所銷售。如果DAO中的代幣數(shù)量少于買方試圖購買的代幣,則退還買方。

部分token.transfer(_buyer, tokens)是我們使用TNS代幣合約來啟動從當(dāng)前位置(DAO)到目標(biāo)_buyertokens金額。

現(xiàn)在我們知道人們可以獲得代幣,讓我們看看我們是否可以實施提交。

結(jié)構(gòu)和提交

根據(jù)我們的介紹帖子,提交一個條目將花費0.0001 eth倍于故事中的條目數(shù)量。我們只需要計算未刪除的提交(因為提交可以刪除),所以讓我們添加這個所需的屬性和一個方法來幫助我們。

uint256 public submissionZeroFee = 0.0001 ether;
uint256 public nonDeletedSubmissions = 0;

function calculateSubmissionFee() view internal returns (uint256) {
    return submissionZeroFee * nonDeletedSubmissions;
}

注意:Solidity具有內(nèi)置時間和以太單位。在這里閱讀更多相關(guān)信息。

此費用只能由業(yè)主更改,但只能降低。為了增加,需要投票。讓我們寫下減函數(shù):

function lowerSubmissionFee(uint256 _fee) onlyOwner external {
    require(_fee < submissionZeroFee, "New fee must be lower than old fee.");
    submissionZeroFee = _fee;
    emit SubmissionFeeChanged(_fee);
}

我們發(fā)出一個事件來通知所有觀察客戶費用已經(jīng)改變,所以讓我們聲明這個事件:

event SubmissionFeeChanged(uint256 newFee);

提交可以是最多256個字符的文本,并且相同的限制適用于圖像。只有他們的類型改變。這是自定義結(jié)構(gòu)的一個很好的用例。讓我們定義一個新的數(shù)據(jù)類型。

struct Submission {
    bytes content;
    bool image;
    uint256 index;
    address submitter;
    bool exists;
}

這就像我們智能合約中的“對象類型”。該對象具有不同類型的屬性。contentbytes類型值。image屬性是一個布爾值,表示它是否是圖像(true/false)。index是一個數(shù)字等于提交時的順序數(shù)字; 它在所有提交列表中的索引(0,1,2,3 ......)。submitter是提交條目的帳戶的地址,并且exists標(biāo)志,因為在映射中,即使密鑰尚不存在,所有密鑰的所有值都被初始化為默認值(false)。

換句話說,當(dāng)你有一個address => bool映射時,該映射已經(jīng)將世界上的所有地址都設(shè)置為“false”。這就是以太坊的運作方式。因此,通過檢查提交是否存在于某個哈希,我們會得到“是”,而提交可能根本就不存在。存在標(biāo)志有助于此。它讓我們檢查提交是否存在且存在——即提交,而不是僅由EVM隱式添加。此外,它使以后更容易“刪除”條目。

注意:從技術(shù)上講,我們還可以檢查以確保提交者的地址不是零地址。

當(dāng)我們在這里時,讓我們定義兩個事件:一個用于刪除條目,一個用于創(chuàng)建條目。

event SubmissionCreated(uint256 index, bytes content, bool image, address submitter);
event SubmissionDeleted(uint256 index, bytes content, bool image, address submitter);

但是有一個問題。以太坊中的映射是不可迭代的:我們無法在沒有嚴(yán)重黑客攻擊的情況下遍歷它們。

為了遍歷它們,我們將為這些提交創(chuàng)建一個標(biāo)識符數(shù)組,其中數(shù)組的鍵將是提交的索引,而值將是我們將為每個提交生成的唯一哈希值。keccak256為我們提供了keccak256哈希算法,用于從任意值生成哈希值,我們可以將其與當(dāng)前塊號一起使用,以確保條目不會在同一塊中重復(fù),并為每個條目獲得一定程度的唯一性。我們這樣使用它: keccak256(abi.encodePacked(_content, block.number));。我們需要encodePacked傳遞給算法的變量,因為它需要我們的一個參數(shù)。這就是這個函數(shù)的作用。

我們還需要在某處存儲提交內(nèi)容,所以讓我們再定義兩個合約變量。

mapping (bytes32 => Submission) public submissions;
bytes32[] public submissionIndex;

好的,我們現(xiàn)在嘗試構(gòu)建createSubmission函數(shù)。

function createSubmission(bytes _content, bool _image) external payable {
 uint256 fee = calculateSubmissionFee();
 require(msg.value >= fee, "Fee for submitting an entry must be sufficient.");
 bytes32 hash = keccak256(abi.encodePacked(_content, block.number));
 require(!submissions[hash].exists, "Submission must not already exist in same block!");
 submissions[hash] = Submission( _content, _image, submissionIndex.push(hash), msg.sender, true );
 emit SubmissionCreated( submissions[hash].index, submissions[hash].content, submissions[hash].image, submissions[hash].submitter ); nonDeletedSubmissions += 1; 
}

讓我們逐行說明:

function createSubmission(bytes _content, bool _image) external payable {

該函數(shù)接受字節(jié)內(nèi)容(字節(jié)是一個動態(tài)大小的字節(jié)數(shù)組,對存儲任意數(shù)量的數(shù)據(jù)很有用)和一個布爾標(biāo)志,表示該輸入是否是圖像。該函數(shù)只能從外部世界調(diào)用,并且應(yīng)支付,這意味著它在交易調(diào)用時接受以太。

uint256 fee = calculateSubmissionFee();
require(msg.value >= fee, "Fee for submitting an entry must be sufficient.");

接下來,我們計算提交新條目的成本,然后檢查與交易一起發(fā)送的價值是否等于或大于費用。

bytes32 hash = keccak256(abi.encodePacked(_content, block.number));
require(!submissions[hash].exists, "Submission must not already exist in same block!");

然后我們計算這個條目的哈希值(bytes32是一個32字節(jié)的固定大小數(shù)組,所以32個字符也是keccak256輸出)。我們使用此哈希來查明是否已存在具有該哈希的提交,如果確實存在,則取消所有內(nèi)容。

submissions[hash] = Submission( _content, _image, submissionIndex.push(hash), msg.sender, true );

此部分在submissions映射中的哈希位置創(chuàng)建新提交。它只是通過合約中上面定義的新結(jié)構(gòu)傳遞值。請注意,雖然你可能習(xí)慣使用其他語言的new關(guān)鍵字,但這里沒有必要(或允許)。然后我們發(fā)出事件(不言自明),最后,還有nonDeletedSubmissions += 1;:這是增加下次提交費用的原因(參見calculateSubmissionFee)。

但是這里缺少很多邏輯。我們?nèi)匀恍枰?/p>

  • 圖像的帳戶

  • 檢查提交帳戶的白名單/黑名單存在和1個TNS代幣所有權(quán)。

我們先做圖像吧。我們的原始計劃表示,每50個文本只能提交一張圖像。我們還需要兩個合約屬性:

uint256 public imageGapMin = 50;
uint256 public imageGap = 0;

當(dāng)然你已經(jīng)可以假設(shè)我們將如何處理這個問題?讓我們在創(chuàng)建新submissions[hash] = ...的之前立即將以下內(nèi)容添加到我們的createSubmission方法中。

if (_image) {
    require(imageGap >= imageGapMin, "Image can only be submitted if more than {imageGapMin} texts precede it.");
    imageGap = 0;
} else {
    imageGap += 1;
}

非常簡單:如果條目應(yīng)該是圖像,那么首先檢查圖像之間的間隙是否超過49,如果是,則將其重置為0。否則,將間隙增加一。就像那樣,每50次(或更多次)提交現(xiàn)有內(nèi)容可以成為一個圖像。

最后,讓我們進行訪問檢查。我們可以在費用計算之前和緊接在函數(shù)入口點之后放置此代碼,因為訪問檢查應(yīng)該首先發(fā)生。

require(token.balanceOf(msg.sender) >= 10**token.decimals());
require(whitelist[msg.sender], "Must be whitelisted");
require(!blacklist[msg.sender], "Must not be blacklisted");

第一行檢查消息發(fā)送者是否具有比代幣合約中小數(shù)位數(shù)更多的代幣(因為我們可以更改代幣地址,因此可能另一個代幣將在稍后使用我們的代幣,并且可能沒有18位小數(shù)。)。換句話說,在我們的例子中,10**token.decimals10**18,即1000 000 000 000 000 000,1后跟18個零。如果我們的代幣有18位小數(shù),那就是1.000000000000000000,或者是一(1)個TNS代幣。請注意,在分析此代碼時,你的編譯器或linter可能會給你一些警告。這是因為代幣的decimals屬性是公共的,因此它的getter函數(shù)是decimals()自動生成的,但它沒有明確列在我們在合約頂部列出的代幣的接口中。為了解決這個問題,我們可以通過添加以下行來更改接口:

function decimals() public view returns (uint256);

還有一件事:因為使用目前設(shè)定為1%的合約的所有者費用,讓我們放棄所有者可以提取的金額并將其余部分保留在DAO中。最簡單的方法是跟蹤所有者可以提取多少,并在每次提交創(chuàng)建后增加該數(shù)量。讓我們在合約中添加一個新屬性:

uint256 public withdrawableByOwner = 0;

然后將其添加到我們的createSubmission函數(shù)的末尾:

withdrawableByOwner += fee.div(daofee);

我們可以通過這樣的功能讓所有者退出:

function withdrawToOwner() public {
 owner.transfer(withdrawableByOwner);
 withdrawableByOwner = 0;
}

這會將允許的金額發(fā)送給所有者,并將計數(shù)器重置為0.如果所有者不想取出全部金額,我們可以為該情況添加另一個函數(shù):

function withdrawAmountToOwner(uint256 _amount) public {
    uint256 withdraw = _amount;
    if (withdraw > withdrawableByOwner) {
        withdraw = withdrawableByOwner;
    }
    owner.transfer(withdraw);
    withdrawableByOwner = withdrawableByOwner.sub(withdraw);
}

由于我們經(jīng)常會通過哈希引用提交,讓我們編寫一個函數(shù)來檢查提交是否存在,以便我們可以替換我們的submissions[hash].exists檢查:

function submissionExists(bytes32 hash) public view returns (bool) { return submissions[hash].exists; }

還需要一些其他幫助函數(shù)來讀取提交內(nèi)容:

function getSubmission(bytes32 hash) public view returns (bytes content, bool image, address submitter) {
    return (submissions[hash].content, submissions[hash].image, submissions[hash].submitter);
}

function getAllSubmissionHashes() public view returns (bytes32[]) {
    return submissionIndex;
}

function getSubmissionCount() public view returns (uint256) {
    return submissionIndex.length;
}

getSubmission獲取提交數(shù)據(jù),getAllSubmissionHashes獲取系統(tǒng)中的所有唯一哈希,getSubmissionCount列出總共提交的數(shù)量(包括已刪除的提交)。我們在客戶端(在UI中)使用這些功能的組合來獲取內(nèi)容。

完整的createSubmission函數(shù)現(xiàn)在看起來像這樣:

function createSubmission(bytes _content, bool _image) storyActive external payable {

    require(token.balanceOf(msg.sender) >= 10**token.decimals());
    require(whitelist[msg.sender], "Must be whitelisted");
    require(!blacklist[msg.sender], "Must not be blacklisted");

    uint256 fee = calculateSubmissionFee();
    require(msg.value >= fee, "Fee for submitting an entry must be sufficient.");

    bytes32 hash = keccak256(abi.encodePacked(_content, block.number));
    require(!submissionExists(hash), "Submission must not already exist in same block!");

    if (_image) {
        require(imageGap >= imageGapMin, "Image can only be submitted if more than {imageGapMin} texts precede it.");
        imageGap = 0;
    } else {
        imageGap += 1;
    }

    submissions[hash] = Submission(
        _content,
        _image,
        submissionIndex.push(hash),
        msg.sender,
        true
    );

    emit SubmissionCreated(
        submissions[hash].index,
        submissions[hash].content,
        submissions[hash].image,
        submissions[hash].submitter
    );

    nonDeletedSubmissions += 1;
    withdrawableByOwner += fee.div(daofee);
}

刪除

那么刪除提交呢?這很容易:我們只是將exists標(biāo)志切換為false!

function deleteSubmission(bytes32 hash) internal {
    require(submissionExists(hash), "Submission must exist to be deletable.");
    Submission storage sub = submissions[hash];

    sub.exists = false;
    deletions[submissions[hash].submitter] += 1;

    emit SubmissionDeleted(
        sub.index,
        sub.content,
        sub.image,
        sub.submitter
    );

    nonDeletedSubmissions -= 1;
}

首先,我們確保提交存在且尚未刪除;然后我們從存儲中檢索它。接下來,我們將其exists標(biāo)志設(shè)置為false,將該地址的DAO中的刪除次數(shù)增加1(在跟蹤用戶以后刪除的條目數(shù)時非常有用;這可能導(dǎo)致黑名單?。?,我們發(fā)出刪除事件。

最后,我們通過減少系統(tǒng)中未刪除的提交數(shù)量來減少新的提交創(chuàng)建費用。我們不要忘記在我們的合約中添加一個新屬性:一個用于跟蹤這些刪除。

mapping (address => uint256) public deletions;

部署變得更加復(fù)雜

現(xiàn)在我們在另一個合約中使用代幣,我們需要更新部署腳本(3_deploy_storydao)以將代幣的地址傳遞給StoryDao的構(gòu)造函數(shù),如下所示:

var Migrations = artifacts.require("./Migrations.sol");
var StoryDao = artifacts.require("./StoryDao.sol");
var TNSToken = artifacts.require("./TNSToken.sol");

module.exports = function(deployer, network, accounts) {
  if (network == "development") {
    deployer.deploy(StoryDao, TNSToken.address, {from: accounts[0]});
  } else {
    deployer.deploy(StoryDao, TNSToken.address);
  }
};

到此,關(guān)于“如何構(gòu)建和測試DAO智能合約”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

文章標(biāo)題:如何構(gòu)建和測試DAO智能合約
網(wǎng)頁網(wǎng)址:http://muchs.cn/article0/jpegio.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信小程序、網(wǎng)頁設(shè)計公司、網(wǎng)站建設(shè)、網(wǎng)站收錄、小程序開發(fā)、外貿(mào)建站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護公司