Facebook幣Libra學習-6.發行屬於本身的代幣Token案例(含源碼)

 

在這個簡短的概述中,咱們描述了咱們在eToro標記化資產背後實施技術的初步經驗,即MoveIR語言中的(eToken),用於在Libra網絡上進行部署。數據庫

Libra協議是一個肯定性狀態機,它將數據存儲在版本化數據庫中。使用新穎的領域特定語言:Move。Move容許可編程事務和模塊重用代碼和狀態 - 相似於咱們定義爲智能合約。編程

目前,Libra不容許發佈模塊,這就是爲何eToken的源代碼應該做爲測試運行的緣由。能夠在此處找到此Move IR eToken實現的完整源代碼。安全

此測試已在提交哈希中使用Libra版本執行#004d472。能夠在此處找到存儲庫。微信

Move中間表示語言
隨着Libra的發佈,定義了一種名爲Move的新的特定於域的語言。直到如今,Libra團隊還沒有提供更高級別的實施。隨着該協議的最近宣佈,一個名爲'Move IR'的lanaguge的一個不太符合人體工程學的中間表示被釋放網絡

Move內化了內存全部權和借用的概念,與Rust的運做方式很是類似。然而,Move語言的新穎性是定義「資源」的方式。dom

「資源」是一種利用全部權模型的結構數據類型,但永遠不能僅複製Move和借用。這是Move語言的核心功能,由於它保證沒有定義的資源意外重複,從而消除了雙重支出或從新發生攻擊的可能性。ide

所以,「資源」的概念與數字資產概念很​​好地對應是明智的函數

eToken實施工具

modules:

module Capability {

    // Capability is responsible for declaring an account's permissions.
    // We define the notion of an owner, minter and blacklisted.
    // Only the owner can assign blacklisted and minter capabilities.

    // The owner is defined by hardcoding its account before publishing the module.

    // -----------------------------------------------------------------

    // Declare owner as a resource. It's only meant to be published once
    resource Owner { }

    // Declare a resource that declares an address's capabilities.
    // Every address using EToken will need to publish a resource themselves.
    // However, only the owner can change its content.
    resource T {
        minter: bool,
        blacklisted: bool,
    }

    // Every account should execute this once before using Capability and EToken module.
    // If the sender is the hardcoded owner, then owner capability is published.
    // Reverts if already published
    public publish() {
        let sender: address;
        sender = get_txn_sender();

        // Publish owner capability if sender is the privileged account
        // Uncomment the following line in production and use a real owner account: if (move(sender) == 0x0) {
        if (true) {
            // Always branch to here when testing, otherwise the test can't complete as the sender address is randomly chosen.
            Self.grant_owner_capability();
        }

        // Publish a new capability with no permissions.
        move_to_sender<T>(T{ minter: false, blacklisted: false });

        return;    
    }

    // Internal function that grants owner capability
    grant_owner_capability() {
        move_to_sender<Owner>(Owner {});
        return;
    }

    // Grants minter capability to receiver, but can only succeed if sender owns the owner capability.
    public grant_minter_capability(receiver: address, owner_capability: &R#Self.Owner) {
        let capability_ref: &mut R#Self.T;

        release(move(owner_capability));

        // Pull a mutable reference to the receiver's capability, and change its permission.
        capability_ref = borrow_global<T>(move(receiver));
        *(&mut move(capability_ref).minter) = true;

        return;
    }

    // Grants blacklist capability to receiver, but can only succeed if sender owns the owner capability.
    public grant_blacklisted_capability(receiver: address, owner_capability: &R#Self.Owner) {
        let capability_ref: &mut R#Self.T;

        release(move(owner_capability));

        // Pull a mutable reference to the receiver's capability, and change its permission.
        capability_ref = borrow_global<T>(move(receiver));
        *(&mut move(capability_ref).blacklisted) = true;

        return;
    }

    // This returns an immutable reference to the owner capability if it exists.
    // Is used by the owner to show ownership to privileged functions.
    // Reverts if owner capability does not exist.
    public borrow_owner_capability(): &R#Self.Owner {
        let sender: address;
        let owner_capability_ref: &mut R#Self.Owner;
        let owner_capability_immut_ref: &R#Self.Owner;

        sender = get_txn_sender();
        owner_capability_ref = borrow_global<Owner>(move(sender));
        owner_capability_immut_ref = freeze(move(owner_capability_ref));

        return move(owner_capability_immut_ref);
    }

    // This returns an immutable reference to the general capability if it exists.
    // Should be used by every account to prove capabilities.
    // Reverts if capability does not exist.
    public borrow_capability(): &R#Self.T {
        let sender: address;
        let capability_ref: &mut R#Self.T;
        let capability_immut_ref: &R#Self.T;

        sender = get_txn_sender();
        capability_ref = borrow_global<T>(move(sender));
        capability_immut_ref = freeze(move(capability_ref));

        return move(capability_immut_ref);
    }

    // Return whether the capability allows minting.
    public is_minter(capability: &R#Self.T): bool {
        let is_minter: bool;
        is_minter = *(&move(capability).minter);
        return move(is_minter);
    }

    // Return true the capability is not blacklisted.
    public is_not_blacklisted(capability: &R#Self.T): bool {
        let is_blacklisted: bool;
        is_blacklisted = *(&move(capability).blacklisted);
        return !move(is_blacklisted);
    }

    // Reverts if capability does not allow minting
    public require_minter(capability: &R#Self.T) {
        let is_minter: bool;
        is_minter = Self.is_minter(move(capability));
        assert(move(is_minter), 0);
        return;
    }

    // Reverts if capability is blacklisted
    public require_not_blacklisted(capability: &R#Self.T) {
        let is_not_blacklisted: bool;
        is_not_blacklisted = Self.is_not_blacklisted(move(capability));
        assert(move(is_not_blacklisted), 0);
        return;
    }
}

module EToken {

    // This module is responsible for an actual eToken.
    // For it to be useful a capability has to be published by using the Capability module above.
    
    // -----------------------------------------------------------------

    import Transaction.Capability;

    // Declare the eToken resource, storing an account's total balance.
    resource T {
        value: u64,
    }

    // Publishes an initial zero eToken to the sender.
    // Should be called once before using this module.
    public publish() {
        move_to_sender<T>(T{ value: 0 });
        return;
    }

    // Mint new eTokens.
    // Reverts if capability does not allow it.
    public mint(value: u64, capability: &R#Capability.T): R#Self.T {
        Capability.require_minter(move(capability));
        return T{value: move(value)};
    }

    // Returns an account's eToken balance.
    // Reverts if an initial eToken hasn't been published.
    public balance(): u64 {
        let sender: address;
        let token_ref: &mut R#Self.T;
        let token_value: u64;

        sender = get_txn_sender();
        token_ref = borrow_global<T>(move(sender));
        token_value = *(&move(token_ref).value);

        return move(token_value);
    }

    // Deposit owned tokens to an payee's address, and destroy the tokens to deposit,
    // Reverts if user is blacklisted.
    public deposit(payee: address, to_deposit: R#Self.T, capability: &R#Capability.T) {
        let payee_token_ref: &mut R#Self.T;
        let payee_token_value: u64;
        let to_deposit_value: u64;

        Capability.require_not_blacklisted(move(capability));

        payee_token_ref = borrow_global<T>(move(payee));
        payee_token_value = *(&copy(payee_token_ref).value);

        // Unpack and destroy to_deposit tokens
        T{ value: to_deposit_value } = move(to_deposit);

        // Increase the payees balance with the destroyed token amount
        *(&mut move(payee_token_ref).value) = move(payee_token_value) + move(to_deposit_value);

        return;
    }

    // Withdraw an amount of tokens of the sender and return it.
    // This works by splitting the token published and returning the specified amount as tokens. 
    public withdraw(amount: u64, capability: &R#Capability.T): R#Self.T {
        let sender: address;
        let sender_token_ref: &mut R#Self.T;
        let value: u64;

        Capability.require_not_blacklisted(move(capability));

        sender = get_txn_sender();
        sender_token_ref = borrow_global<T>(move(sender));
        value = *(&copy(sender_token_ref).value);

        // Make sure that sender has enough tokens
        assert(copy(value) >= copy(amount), 1);

        // Split the senders token and return the amount specified
        *(&mut move(sender_token_ref).value) = move(value) - copy(amount);
        return T{ value: move(amount) };
    }
}

script:

// Performs simple testing to crudely verify the published modules above.

import Transaction.Capability;
import Transaction.EToken;

main() {
    let sender: address;
    let owner_capability: &R#Capability.Owner;
    let capability: &R#Capability.T;
    let minted_tokens: R#EToken.T;
    let balance: u64;

    sender = get_txn_sender();

    // Publish initial capability
    Capability.publish();

    // Borrow owner_capability for minter delegation
    owner_capability = Capability.borrow_owner_capability();

    // Delegate itself as a minter
    Capability.grant_minter_capability(copy(sender), move(owner_capability));

    // Borrow general capability for proof of minting capability
    capability = Capability.borrow_capability();

    // Publish an eToken account
    EToken.publish();

    // Mint 100 eTokens and prove minter capability
    minted_tokens = EToken.mint(100, copy(capability));

    // Deposit the freshly minted tokens to itself
    EToken.deposit(move(sender), move(minted_tokens), move(capability));

    // Test that the balance corresponds with the intended behaviour
    balance = EToken.balance();
    assert(move(balance) == 100, 3);

    return;
}
View Code

 

eToken目前部署在以太坊區塊鏈上,包括爲生產中使用標記化而實現的幾個重要功能。區塊鏈

最重要的功能以下所列。粗體功能也已在Move IR中實現。

角色(破壞者,黑名單)
造幣
燃燒
暫停
可升級
咱們將一個角色定義爲Move實現中的一個功能,命名更改是爲了遵照Libra本身的硬幣實現的標準。

能力
爲了可以授予minter和blacklist權限,咱們必須指定模塊的全部者。做爲模塊的全部者,用戶能夠將賬戶添加爲小工具和黑名單。

咱們首先定義全部者資源,該資源僅用於發佈一次。它在Capability模塊中聲明。

resource Owner { }
而後,咱們經過將已發佈資源Move到指定全部者來授予全部權。在這裏,咱們在嘗試使用該語言時遇到了一些問題,以保證全部者功能只發布一次。

在初始模塊發佈期間,Move IR定義彷佛不支持僅定義爲可執行一次的函數,也稱爲構造函數。

固然,這將是授予「全部者」能力的理想場所。

此外,Move IR不直接支持全局變量,這多是將函數定義爲已執行的不安全方式。

爲了規避這些限制,咱們使用硬編碼的全部者地址建立了模塊,從而建立了單例資源。所以,只有在全部者做爲發件人發佈功能資源時纔會執行全部權授予:

public publish() {
let sender: address;
sender = get_txn_sender();

// Publish owner capability if sender is the privileged account
// Replace 0x0 address with a real owner account address
if (move(sender) == 0x0) {
Self.grant_owner_capability();
}

...
模塊不能表明發件人以外的其餘賬戶發佈資源。從而,讓賬戶徹底控制與他們相關的內容。publish()所以,該功能必須由但願得到有效能力的全部賬戶執行,這對於進一步的令牌使用是強制性的。

Neverthelss,強制執行硬編碼的全部者地址並非一個優雅的解決方案。咱們向Libra團隊提出了這個問題,團隊成員建議實施合成糖替換硬編碼地址fx。Self.publisher_address。

實際全部權授予是經過調用內部函數來完成的,該函數grant_owner_capability()建立Owner資源並將其發佈到發件人賬戶。這是經過執行如下表達式完成的:

move_to_sender<Owner>(Owner {});
經過將所述功能實現爲內部,VM保證它只能由模塊內部執行。經過僅在發件人是指定的全部者地址時調用該函數,咱們確保它只發布一次。

該publish()還發布沒有權限調用它,由於須要進一步令牌使用的全部賬戶的能力。

它只會在已經存在的狀況下恢復。

經過將全部權定義爲資源,咱們能夠確保它不能被複制。它還爲咱們提供了一種使人愉快的類型安全的方式來保護特權功能,例如授予其餘人的鑄造能力。這是經過簡單地要求借用Owner資源做爲特權函數的參數來實現的。賬戶只能經過調用borrow_owner_capability()函數來獲取借用的引用,若是函數存在於發送方地址,則返回借用的引用。如下摘錄舉例說明了全部者特權函數:

// Grants minter capability to receiver, but can only succeed if sender owns the owner capability.
public grant_minter_capability(receiver: address, owner_capability: &R#Self.Owner) {
let capability_ref: &mut R#Self.T;

release(move(owner_capability));

...
借用的全部者功能僅用於類型安全性,所以當即釋放給發送方:若是功能成功執行,則會改變位於該receiver地址的功能資源。

...

// Pull a mutable reference to the receiver's capability, and change its permission.
capability_ref = borrow_global<T>(move(receiver));
*(&mut move(capability_ref).minter) = true;

...
代幣
經過使用功能模塊定義eToken的角色和權限,咱們如今能夠繼續實際的令牌實現。

咱們首先聲明令牌資源,該資源包含所述令牌的數量。

resource T {
value: u64,
}
與其餘智能合約語言相比,Move的優點顯而易見。若是咱們要存放一些令牌,咱們必須控制令牌的內存全部權。咱們只能經過拆分現有的自有令牌(也稱爲撤銷)或鑄造新鮮令牌來得到此全部權。

這種全部權屬性保證了相同的令牌不能存在於其餘地方,從而消除了因錯誤複製而致使的錯誤,從而致使雙重花費和其餘錯誤行爲。

此外,內存全部權模型還要求必須明確銷燬擁有的令牌或將其Move到另外一個全部者。這能夠保證令牌不會被意外鎖定在模塊內,永遠不會被再次檢索。

經過利用這種類型安全的屬性,咱們能夠定義存放自有令牌的功能。

public deposit(payee: address, to_deposit: R#Self.T, capability: &R#Capability.T) {

...

Capability.require_not_blacklisted(move(capability));

payee_token_ref = borrow_global<T>(move(payee));
payee_token_value = *(&copy(payee_token_ref).value);

// Unpack and destory to_deposit tokens
T{ value: to_deposit_value } = move(to_deposit);

// Increase the payees balance with the destroyed token amount
*(&mut move(payee_token_ref).value) = move(payee_token_value) + move(to_deposit_value);

...
咱們首先確保用戶未被列入黑名單。接下來,咱們經過解壓縮其內部數量變量來銷燬所擁有的令牌。最後,咱們經過解壓縮的金額增長收款人的代幣。

與存款相反,當從發件人的賬戶中提取令牌時,咱們將令牌分紅兩部分,並將新令牌的全部權歸還給來電者。

經過首先減小發件人令牌數量,而後返回新的令牌資源來完成拆分。

*(&mut move(sender_token_ref).value) = move(value) - copy(amount);
return T{ value: move(amount) };
結論
總而言之,Libra和Move IR是智能合約開發中值得歡迎的一步。擁有強大的資產保證能夠幫助開發人員減小容易出錯的代碼並加快Move速度。

儘管如此,Move IR仍然處於早期階段,而且在當前迭代中不是用戶友好的。因爲某種緣由,它被稱爲「中間表明」:-)

咱們將密切關注這一發展,並對新的發展感到興奮。

嘗試並運行測試
若是您有興趣瞭解Libra網絡和Move IR,咱們建議您運行測試以熟悉上述概念。要運行原始eToken實現測試,您應該執行如下步驟:

克隆Libra存儲庫(最好是在引言中聲明的提交哈希)
跟隨LibraREADME如何編譯和安裝它。
克隆此存儲庫
將位於src/eToken.mvir此存儲庫中的eToken Move IR源代碼複製到位於的Libra存儲庫中的測試文件夾language/functional_tests/tests/testsuite/modules/
在Libra存儲庫中的某處執行如下命令: cargo test -p functional_tests eToken

 

Libra國內開發者微信交流羣:

不能入羣請加管理微信

相關文章
相關標籤/搜索