Facebook Currency Libra Learning-6. Issue your own token Token case (with source code)

Keywords: PHP network Database less Attribute

In this short overview, we describe our initial experience with implementing technology behind eToro's tagged assets, the MoveIR language (eToken), for deployment on the Libra network.

The Libra protocol is a deterministic state machine that stores data in a versioned database.Use a new domain-specific language: Move.Move allows programmable transactions and modules to reuse code and state - similar to what we define as a smart contract.

Libra currently does not allow modules to be published, which is why eToken's source code should be used as a test run.The complete source code for this Move IR eToken implementation can be found here.

This test was executed #004d472 using the Libra version in the submission hash.You can find the repository here.

Move Intermediate Representation Language
With the release of Libra, a new domain-specific language called Move was defined.Until now, the Libra team has not provided a higher level of implementation.With the recent announcement of the agreement, a less ergonomic intermediate representation of lanaguge named'Move IR'was released

Move internalizes the concepts of memory ownership and borrowing, much like Rust does.However, the novelty of the Move language is how resources are defined.

Resource is a structured data type that leverages the ownership model, but can never just copy Move and borrow.This is a core feature of the Move language because it guarantees unexpected duplication of undefined resources, eliminating the possibility of double spending or a recurrence of an attack.

It would therefore be wise for the concept of "resource" to correspond well with the concept of digital assets

eToken implementation

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;
}

 

eToken is currently deployed on the Taifang block chain, including several important functions implemented for the use of marking in production.

The most important functions are listed below.Bold functionality has also been implemented in Move IR.

Role (Destroyer, Blacklist)
Coinage
burning
suspend
Upgradable
We define a role as a feature in the Move implementation, and the naming changes are made to comply with Libra's own coin implementation standards.

ability
In order to be able to grant minter and blacklist privileges, we must specify the owner of the module.As the owner of the module, users can add accounts as gadgets and blacklists.

First, we define the owner resource, which is used only once for publishing.It is declared in the Capability module.

resource Owner { }
We then grant ownership by moving the published resource Move to the designated owner.Here, we have some problems trying to use the language to ensure that the owner feature is only published once.

During the initial module release, the Move IR definition does not seem to support functions defined only once as executable, also known as constructors.

Of course, this would be an ideal place to grant the Owner capability.

In addition, Move IR does not directly support global variables, which may define functions as unsafe ways to execute.

To circumvent these limitations, we created a module with a hard-coded owner address, creating a single resource.Therefore, ownership grants are only performed when the owner publishes a feature resource as the sender:

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();
}

...
A module cannot publish resources on behalf of an account other than the sender.This gives the account full control over what is relevant to them.publish(), therefore, this functionality must be performed by all accounts that wish to gain valid capabilities, which is mandatory for further token use.

Neverthelss, enforcing hard-coded owner addresses is not an elegant solution.We asked the Libra team this question, and the team members suggested that the hard-coded address fx be replaced with synthetic sugar.Self.publisher_address.

The actual ownership grant is done by calling an internal function, grant_owner_capability(), which creates an Owner resource and publishes it to the sender account.This is done by executing the following expression:

move_to_sender<Owner>(Owner {});
By implementing these functions internally, the VM guarantees that it can only be executed internally by modules.By calling this function only when the sender is the specified owner address, we ensure that it is only published once.

The publish() also publishes the ability to call it without permission because all accounts that require further tokens are used.

It will only recover if it already exists.

By defining ownership as a resource, we can ensure that it cannot be copied.It also provides us with an enjoyable, type-safe way to protect privileged functions, such as granting others casting power.This is achieved by simply requiring the borrowing of Owner resources as parameters to privileged functions.Accounts can only obtain a borrowed reference by calling the borrow_owner_capability() function, which returns the borrowed reference if the function exists at the sender's address.The following excerpt illustrates the owner privilege function:

// 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));

...
The borrowed owner functionality is only for type security and is therefore immediately released to the sender: if the functionality is successfully executed, it will change the functional resources located at that receiver address.

...

// 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;

...
Token
By defining eToken's roles and permissions using functional modules, we can now proceed with the actual token implementation.

First, we declare a token resource that contains the number of tokens described.

resource T {
value: u64,
}
Move has obvious advantages over other smart contract languages.If we want to store some tokens, we must control the memory ownership of the token.We can only acquire this ownership by splitting an existing own token (also known as undo) or casting a new one.

This ownership property ensures that the same token cannot exist elsewhere, eliminating errors caused by incorrect replication, resulting in double costs and other erroneous behavior.

In addition, the memory ownership model requires that you explicitly destroy the token you own or Move it to another owner.This ensures that tokens are not accidentally locked in modules and never retrieved again.

By utilizing this type of security attribute, we can define the ability to store our own tokens.

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);

...
First, we make sure that users are not blacklisted.Next, we destroy the token we own by unzipping its internal quantity variable.Finally, we increase the payee's token by uncompressing the amount.

In contrast to deposits, when a token is extracted from the sender's account, we divide the token into two parts and return ownership of the new token to the caller.

The split is accomplished by first reducing the number of sender tokens and then returning a new token resource.

*(&mut move(sender_token_ref).value) = move(value) - copy(amount);
return T{ value: move(amount) };
conclusion
In short, Libra and Movee IR are welcome steps in smart contract development.Having a strong asset guarantee can help developers reduce error-prone code and speed up Move.

Nevertheless, Move IR is still in its early stages and is not user-friendly in the current iteration.For some reason, it is called "intermediate representative":-)

We will keep an eye on this development and be excited about it.

Try and run tests
If you are interested in learning about Libra networks and Move IR, we recommend that you run tests to familiarize yourself with these concepts.To run the original eToken implementation test, you should perform the following steps:

Clone the Libra repository (preferably the submit hash declared in the introduction)
Follow how LibraREADME compiles and installs it.
Clone this repository
Copy the eToken Move IR source code located in the src/eToken.mvir repository to the test folder language/functional_tests/tests/testsuite/modules/located in the Libra repository
Execute the following command somewhere in the Libra repository: cargo test -p functional_tests eToken

 

Libra Domestic Developer WeChat Exchange Group:

Can't join group Please manage WeChat

Posted by ChompGator on Fri, 19 Jul 2019 11:40:02 -0700