Sicherheitspraktiken in der Move-Entwicklung (2): Aptos Coin

Erstellen & verwalten Sie Ihre eigene Münze: Einfach mit coin.move im offiziellen Aptos-Standardmodul.

Sicherheitspraktiken in der Move-Entwicklung (2): Aptos Coin

Im vorherigen Artikel haben wir kurz vorgestellt, wie man ein Hello-World-Programm auf dem Aptos-Netzwerk entwickelt. Von nun an werden wir uns etwas tiefer mit der Entwicklung von DeFi-Anwendungen und deren Sicherheitsbedenken befassen. Wie immer beginnen wir gerne mit einigen grundlegenden, aber wichtigen Konzepten. In diesem Artikel konzentrieren wir uns auf Aptos Coin (d.h. den fungiblen Token in Aptos [1]), einschließlich seiner Entwicklung, Verwaltung und Interaktion.

TL;DR

Dieser Artikel wird Ihnen sagen:

  • Was ist Aptos Coin?
  • Wie erstellt und verwaltet man seinen Coin?
  • Wie interagiert man mit seinem Coin?

0x1. Über Aptos Coin

Als Atome von DeFi werden Token (oder Coins) in Blockchain-Ökosystemen weit verbreitet eingesetzt. Sie können zur Darstellung vieler Arten von Dingen verwendet werden, darunter elektronische Währungen, Staking-Anteile und Stimmrechte für die Organisationsverwaltung. Bis zu einem gewissen Grad kann die tägliche Aktivität von DeFi einfach als ein riesiges Volumen von Token-Flüssen über die Blockchain-Systeme hinweg betrachtet werden.

Ethereum hat einen Satz von Standards für Token entwickelt. Der bekannteste ist ERC20, der die Schnittstellen spezifiziert, die ein Standard ERC20 Token erfüllen muss. ERC20 ist ein fungibler Token-Standard, während es auch nicht-fungible Token-Standards gibt, wie z.B. ERC721.

Ähnlich wie andere Blockchain-Systeme hat Aptos seinen eigenen Token-Standard [2] , der definiert, wie digitale Assets auf ihren jeweiligen Blockchains erstellt und verwendet werden. Speziell in Aptos wird der fungible Token als Coin bezeichnet, während der nicht-fungible Token (d.h. NFT) Token genannt wird. Im Folgenden werden wir die Erstellung, Verwaltung und Interaktion mit einem Aptos Coin besprechen.

0x2. Erstellen und Verwalten Ihres ersten Coins

Aptos bietet ein offizielles Standardmodul (ähnlich wie ERC20): coin.move. Durch den Aufruf der API dieses Moduls kann jeder Benutzer einfach seinen eigenen Coin erstellen. Außerdem bietet coin.move den Berechtigungsmechanismus zur Verwaltung von Coins, der für den Aufbau komplexer DeFi-Anwendungen wichtig und nützlich ist. Im Folgenden werden wir die Erstellung eines Coins basierend auf diesem Modul demonstrieren.

Wie im vorherigen Artikel erwähnt, können Sie ein Projekt erstellen, indem Sie den folgenden Befehl eingeben:

aptos move init --name my_coin

Dann müssen Sie eine neue Move-Datei im Ordner sources erstellen. Füllen wir diese nun mit dem folgenden Beispielcode, der ein Modul namens bsc definiert, um einen Standardcoin namens BSC zu erstellen und zu verwalten.

module BlockSec::bsc{
    use aptos_framework::coin;
    use aptos_framework::event;
    use aptos_framework::account;
    use aptos_std::type_info;
    use std::string::{utf8, String};
    use std::signer;


    struct BSC{}
    
    struct CapStore has key{
        mint_cap: coin::MintCapability<BSC>,
        freeze_cap: coin::FreezeCapability<BSC>,
        burn_cap: coin::BurnCapability<BSC>
    }

    struct BSCEventStore has key{
        event_handle: event::EventHandle<String>,
    }

    fun init_module(account: &signer){
        let (burn_cap, freeze_cap, mint_cap) = coin::initialize<BSC>(account, utf8(b"BSC"), utf8(b"BSC"), 6, true);
        move_to(account, CapStore{mint_cap: mint_cap, freeze_cap: freeze_cap, burn_cap: burn_cap});
    }

    public entry fun register(account: &signer){
        let address_ = signer::address_of(account);
        if(!coin::is_account_registered<BSC>(address_)){
            coin::register<BSC>(account);
        };
        if(!exists<BSCEventStore>(address_)){
            move_to(account, BSCEventStore{event_handle: account::new_event_handle(account)});
        };
    }

    fun emit_event(account: address, msg: String) acquires BSCEventStore{
        event::emit_event<String>(&mut borrow_global_mut<BSCEventStore>(account).event_handle, msg);
    }

    public entry fun mint_coin(cap_owner: &signer, to_address: address, amount: u64) acquires CapStore, BSCEventStore{
        let mint_cap = &borrow_global<CapStore>(signer::address_of(cap_owner)).mint_cap;
        let mint_coin = coin::mint<BSC>(amount, mint_cap);
        coin::deposit<BSC>(to_address, mint_coin);
        emit_event(to_address, utf8(b"minted BSC"));
    }

    public entry fun burn_coin(account: &signer, amount: u64) acquires CapStore, BSCEventStore{
        let owner_address = type_info::account_address(&type_info::type_of<BSC>());
        let burn_cap = &borrow_global<CapStore>(owner_address).burn_cap;
        let burn_coin = coin::withdraw<BSC>(account, amount);
        coin::burn<BSC>(burn_coin, burn_cap);
        emit_event(signer::address_of(account), utf8(b"burned BSC"));
    }

    public entry fun freeze_self(account: &signer) acquires CapStore, BSCEventStore{
        let owner_address = type_info::account_address(&type_info::type_of<BSC>());
        let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
        let freeze_address = signer::address_of(account);
        coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
        emit_event(freeze_address, utf8(b"freezed self"));
    }

    public entry fun emergency_freeze(cap_owner: &signer, freeze_address: address) acquires CapStore, BSCEventStore{
        let owner_address = signer::address_of(cap_owner);
        let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
        coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
        emit_event(freeze_address, utf8(b"emergency freezed"));
    }

    public entry fun unfreeze(cap_owner: &signer, unfreeze_address: address) acquires CapStore, BSCEventStore{
        let owner_address = signer::address_of(cap_owner);
        let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
        coin::unfreeze_coin_store<BSC>(unfreeze_address, freeze_cap);
        emit_event(unfreeze_address, utf8(b"unfreezed"));
    }
    
}

0x2.1 Grundlegendes Design

Schauen wir uns zunächst den Strukturteil an. Insgesamt sind drei Strukturen definiert.

  • Die Struktur BSC wird als eindeutiger Bezeichner des Coins verwendet. Als solche kann dieser Coin eindeutig über den Pfad BlockSec::bsc::BSC bestimmt werden.
  • Die Struktur CapStore wird verwendet, um einige Berechtigungen zu speichern, die aus dem Modul aptos_framework::coin stammen. Diese Berechtigungen entsprechen den Rechten für einige spezielle Operationen und werden später erklärt.
  • Die Struktur BSCEventStore wird zur Aufzeichnung von Benutzerereignissen verwendet.

Nach diesen Strukturen gibt es eine Funktion init_module, die zur Initialisierung des Moduls verwendet wird und nur einmal aufgerufen wird, wenn das Modul auf der Kette veröffentlicht wird. In dieser Funktion ruft das Modul coin::initialize<BSC> auf, um BlockSec::bsc::BSC als eindeutigen Bezeichner für einen neuen Coin zu registrieren.

public fun initialize<CoinType>(
    account: &signer,
    name: string::String,
    symbol: string::String,
    decimals: u8,
    monitor_supply: bool,
): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) {
    initialize_internal(account, name, symbol, decimals, monitor_supply, false)
}

/// Capability required to mint coins.
struct MintCapability<phantom CoinType> has copy, store {}

/// Capability required to freeze a coin store.
struct FreezeCapability<phantom CoinType> has copy, store {}

/// Capability required to burn coins.
struct BurnCapability<phantom CoinType> has copy, store {}

Nach der Registrierung werden alle generischen Funktionen, die den Typ BlockSec::bsc::BSC in aptos_framework::coin verwenden, auf diesen Coin angewendet. Dieser Registrierungsprozess gibt drei Berechtigungen zurück, nämlich MintCapability, FreezeCapability und BurnCapability. Diese Berechtigungen sind für das Prägen von Coins, das Einfrieren von Benutzerkonten und das Verbrennen von Coins erforderlich. Bis zu einem gewissen Grad ist die Funktionalität einer solchen Berechtigungsstruktur ähnlich einem Schlüssel, der zum Öffnen eines Schlosses zu einer bestimmten Berechtigung verwendet wird. Wenn jemand den Schlüssel hat, kann er die entsprechende Berechtigung erhalten. Hier speichern wir diese Berechtigungen in der Struktur CapStore (im Besitz des Administrators/Herausgebers dieses Moduls) für die spätere Verwendung.

Inzwischen wird während des Registrierungsprozesses eine CoinInfo-Struktur unter der Adresse des Administrators gespeichert, um relevante Informationen aufzuzeichnen:

/// Information about a specific coin type. Stored on the creator of the coin's account.
struct CoinInfo<phantom CoinType> has key {
    name: string::String,
    /// Symbol of the coin, usually a shorter version of the name.
    /// For example, Singapore Dollar is SGD.
    symbol: string::String,
    /// Number of decimals used to get its user representation.
    /// For example, if `decimals` equals `2`, a balance of `505` coins should
    /// be displayed to a user as `5.05` (`505 / 10 ** 2`).
    decimals: u8,
    /// Amount of this coin type in existence.
    supply: Option<OptionalAggregator>,
}

Nach dem Aufruf der Funktion init_module wurde Ihr Coin auf der Kette registriert. Niemand kann diesen Coin jedoch verwenden, da vorerst keine Zirkulation besteht. Um den Coin nutzbar zu machen, müssen einige Operationen, einschließlich Ausgabe, Zuteilung und Vernichtung, unterstützt werden. Diese Operationen erfordern die bei der Registrierung des Coins erhaltenen Berechtigungen.

0x2.2 Coin-Verwaltung

Dieser Coin ist so konzipiert, dass er die folgenden Regeln befolgt:

  • Nur der Administrator (Admin) kann Coins prägen.
  • Benutzer können ihre eigenen Coins jederzeit verbrennen.
  • Benutzer können ihre Konten jederzeit einfrieren/entfroren.

Entsprechend definieren wir fünf Verwaltungsfunktionen, nämlich mint_coin, burn_coin, freeze_self, emergency_freeze und unfreeze. Die ersten beiden Funktionen sind für das Prägen und Verbrennen von Coins zuständig; die letzteren drei werden zum Einfrieren und Entfroren von Konten verwendet.

Prägen von Coins

In unserem Modul wird die Funktion mint_coin zum Prägen von Coins verwendet. Da nur Admins Coins prägen können, müssen wir in dieser Funktion die entsprechende Berechtigung überprüfen.

public entry fun mint_coin(cap_owner: &signer, to_address: address, amount: u64) acquires CapStore, BSCEventStore{
    let mint_cap = &borrow_global<CapStore>(signer::address_of(cap_owner)).mint_cap;
    let mint_coin = coin::mint<BSC>(amount, mint_cap);
    coin::deposit<BSC>(to_address, mint_coin);
    emit_event(to_address, utf8(b"minted BSC"));
}

Diese Funktion benötigt drei Parameter:

  • cap_owner ist vom Typ &signer, d.h. der Initiator der Transaktion.
  • to_address gibt die Adresse an, an die die geprägten Coins eingezahlt werden.
  • amount gibt die Anzahl der geprägten Coins an.

Sie besteht aus drei Schritten: Erwerb der Berechtigung zum Prägen von Coins, Prägen von Coins und Einzahlen von Coins.

Zuerst, am Anfang der Funktion mint_coin, kann die Kontoadresse des Transaktionsinitiators über signer::address_of(cap_owner) abgerufen werden. Danach wird borrow_global<CapStore> verwendet, um zu bestätigen, ob das Konto CapStore besitzt, um zu verifizieren, dass es der Admin des Moduls ist. Dadurch können wir sicherstellen, dass nur der Admin Coins prägen kann, während andere Benutzer in diesem Schritt fehlschlagen.

Zweitens ruft die Funktion mint_coin dann die Funktion mint des Moduls aptos_framework::coin auf, um Coins zu prägen.

public fun mint<CoinType>(
        amount: u64,
        _cap: &MintCapability<CoinType>,
    ): Coin<CoinType> acquires CoinInfo {
    if (amount == 0) {
        return zero<CoinType>()
    };

    let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
    if (option::is_some(maybe_supply)) {
        let supply = option::borrow_mut(maybe_supply);
        optional_aggregator::add(supply, (amount as u128));
    };

    Coin<CoinType> { value: amount }
}

Hier wird MintCapability benötigt. Insbesondere wird ein Parameter namens _cap als Referenz auf MintCapability benötigt. Dann wird die mit der MintCapability verbundene Berechtigung entsprechend übertragen. Obwohl keine explizite Zugriffskontrolle vorhanden ist, wird die Überprüfung durch die Move-Sprache erzwungen.

Drittens ruft die Funktion mint_coin die Funktion deposit auf, um die geprägten Coins an die angegebene to_address einzuzahlen.

Tipp #1: Zugriffskontrollen für privilegierte Konten können mit einer ähnlichen Methode überprüft werden.

Verbrennen von Coins

Das Verfahren zum Verbrennen von Coins unterscheidet sich vom Prägen von Coins. Insbesondere darf nur der Admin die Funktion mint_coin aufrufen, während jeder Benutzer die Funktion burn_coin aufrufen kann. Zu diesem Zweck muss die Funktion burn_coin die Berechtigung vorübergehend eskalieren, d.h. die BurnCapability-Berechtigung für diese Benutzer erhalten.

public entry fun burn_coin(account: &signer, amount: u64) acquires CapStore, BSCEventStore{
    let owner_address = type_info::account_address(&type_info::type_of<BSC>());
    let burn_cap = &borrow_global<CapStore>(owner_address).burn_cap;
    let burn_coin = coin::withdraw<BSC>(account, amount);
    coin::burn<BSC>(burn_coin, burn_cap);
    emit_event(signer::address_of(account), utf8(b"burned BSC"));
}

Diese Funktion benötigt zwei Parameter:

  • account ist vom Typ &signer, d.h. der Initiator der Transaktion.
  • amount gibt die Anzahl der verbrannten Coins an.

Sie besteht ebenfalls aus drei Schritten: Erwerb der Berechtigung zum Verbrennen von Coins, Abheben von Coins und Verbrennen von Coins.

Offensichtlich erfordert die Funktion burn des Moduls aptos_framework::coin, dass der Aufrufer eine Referenz auf BurnCapability übergibt. Diese Berechtigung ist jedoch im CapStore des Administrators gespeichert. Daher müssen wir es gewöhnlichen Benutzern erlauben, diese Berechtigung zu erhalten, um die von ihnen gehaltenen Coins zu verbrennen.

public fun burn<CoinType>(
    coin: Coin<CoinType>,
    _cap: &BurnCapability<CoinType>,
) acquires CoinInfo {
    let Coin { value: amount } = coin;
    assert!(amount > 0, error::invalid_argument(EZERO_COIN_AMOUNT));

    let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
    if (option::is_some(maybe_supply)) {
        let supply = option::borrow_mut(maybe_supply);
        optional_aggregator::sub(supply, (amount as u128));
    }
}

Um dieses Ziel zu erreichen, können wir den von der Move-Sprache bereitgestellten Operator borrow_global verwenden. Er dient zum Lesen eines bestimmten Datentyps aus dem unveränderlichen globalen Speicher eines Kontos. Durch die Verwendung dieses Operators kann ein Modul die vom Administrator besessene Berechtigung an andere Benutzer verleihen. D.h. die Adresse des Administrators wird benötigt, um die gewünschte Berechtigung zu erhalten.

Die Transaktionsinitiatorin der Funktion burn_coin ist jedoch der Benutzer und nicht der Administrator. Daher kann die Administratoradresse nicht über signer (wie in der Funktion mint_coin) abgerufen werden. Glücklicherweise kann sie über aptos_std::type_info mit BSC abgerufen werden, um die Adresse des Moduls zu erhalten, in dem diese Struktur definiert ist. Da das Modul unter der Administratoradresse veröffentlicht wurde, können wir die Administratoradresse entsprechend weiter abrufen und schließlich die BurnCapability-Berechtigung erhalten.

Tipp #2: Der _borrow_global_ Operator kann verwendet werden, um Berechtigungen eines Moduls vorübergehend zu erhalten.

Nachdem die BurnCapability erhalten wurde, kann das Modul die Coins des angegebenen Betrags vom Benutzer abheben und die Coins mit dieser Berechtigung verbrennen.

Einfrieren und Entfroren von Coin-Konten

Basierend auf der obigen Diskussion können wir nun die Verwaltung der Coin-Konten leicht durchgehen. Insbesondere stellen wir die Funktion freeze_self zur Verfügung, damit Benutzer ihre Coin-Konten einfrieren können. Hier stellen wir auch die Funktion emergency_freeze für das Notfall-Einfrieren zur Verfügung, die nur vom Administrator verwendet werden kann. Darüber hinaus sollten Benutzer aufgrund der Existenz des Notfall-Einfriermechanismus nicht in der Lage sein, sich selbst zu entfroren. Daher erfordert die Funktion unfreeze ebenfalls, dass der Administrator die Benutzerkonten entfroren kann.

public entry fun freeze_self(account: &signer) acquires CapStore, BSCEventStore{
    let owner_address = type_info::account_address(&type_info::type_of<BSC>());
    let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
    let freeze_address = signer::address_of(account);
    coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
    emit_event(freeze_address, utf8(b"freezed self"));
}

public entry fun emergency_freeze(cap_owner: &signer, freeze_address: address) acquires CapStore, BSCEventStore{
    let owner_address = signer::address_of(cap_owner);
    let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
    coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
    emit_event(freeze_address, utf8(b"emergency freezed"));
}

public entry fun unfreeze(cap_owner: &signer, unfreeze_address: address) acquires CapStore, BSCEventStore{
    let owner_address = signer::address_of(cap_owner);
    let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
    coin::unfreeze_coin_store<BSC>(unfreeze_address, freeze_cap);
    emit_event(unfreeze_address, utf8(b"unfreezed"));
}

Tipp #3: Seien Sie vorsichtig, wenn Sie Berechtigungen über Funktionen zurückgeben! Böswillige Benutzer, die diese Berechtigungen erhalten, können sie missbrauchen, um den Coin-Holdern Schaden zuzufügen!

0x3. Mit dem Coin interagieren

Hier konzentrieren wir uns auf die Art und Weise, wie mit dem Coin interagiert wird.

0x3.1 Registrierung

Sie haben vielleicht bemerkt, dass in dem Modul eine Funktion register existiert:

public entry fun register(account: &signer){
    let address_ = signer::address_of(account);
    if(!coin::is_account_registered<BSC>(address_)){
        coin::register<BSC>(account);
    };
    if(!exists<BSCEventStore>(address_)){
        move_to(account, BSCEventStore{event_handle: account::new_event_handle(account)});
    };
}

Diese Funktion dient dazu, Benutzern die Rechte zur Nutzung von Coins und Ereignisrekordern zu registrieren. Um einen bestimmten Coin zu verwenden, legt das Modul aptos_framework::coin fest, dass der Benutzer zuerst explizit das Recht zur Nutzung des Coins über die Funktion aptos_framework::coin::register registrieren muss.

public fun register<CoinType>(account: &signer) {
    let account_addr = signer::address_of(account);
    assert!(
        !is_account_registered<CoinType>(account_addr),
        error::already_exists(ECOIN_STORE_ALREADY_PUBLISHED),
    );

    account::register_coin<CoinType>(account_addr);
    let coin_store = CoinStore<CoinType> {
        coin: Coin { value: 0 },
        frozen: false,
        deposit_events: account::new_event_handle<DepositEvent>(account),
        withdraw_events: account::new_event_handle<WithdrawEvent>(account),
    };
    move_to(account, coin_store);
}

Benutzer können diesen Coin nur normal halten, wenn sie diesen Coin-Typ über diese Funktion registrieren. Das heißt, wenn Sie einen bestimmten Coin nicht halten möchten, können andere diesen Coin nicht ohne Ihre Zustimmung in Ihr Konto einzahlen. Die Registrierung platziert tatsächlich eine CoinStore-Struktur (des Ziel-Coin-Typs) in Ihrem Konto. Diese CoinStore-Struktur enthält eine Coin-Struktur, um Ihren Saldo aufzuzeichnen.

Tipp #4: Im Gegensatz zu Ethereum-Token können Aptos-Coins nicht gehalten und betrieben werden, ohne dass ein Benutzer sich explizit registriert hat.

0x3.2 Übertragung

Angenommen, Sie haben jetzt einige BSC-Coins, dann können Sie diese Coins übertragen, indem Sie die Funktion transfer des Moduls aptos_framework::coin aufrufen.

public entry fun transfer<CoinType>(
    from: &signer,
    to: address,
    amount: u64,
) acquires CoinStore {
    let coin = withdraw<CoinType>(from, amount);
    deposit(to, coin);
}

Beachten Sie, dass dies eine Einstiegsfunktion ist, die vom coin-Modul bereitgestellt wird. Die Logik besteht aus Aufrufen zweier öffentlicher Funktionen, nämlich withdraw und deposit. Die Funktion withdraw benötigt die &signer-Berechtigung, die zum Abheben eines bestimmten Betrags an Vermögenswerten von Ihrem Konto in einen Coin verwendet wird. Die Funktion deposit kann einen Coin in jedes registrierte Konto des Coins einzahlen. Diese Funktion benötigt keine zusätzlichen Berechtigungen und zahlt die angegebenen Coins auf die Kontoadresse ein. Schließlich werden die übertragenen Coins automatisch mit den Coins zusammengeführt, die in der CoinStore-Struktur der Zieladresse gespeichert sind.

Tipp #5: Nach dem Abheben befinden sich die Vermögenswerte im Coin unter der Kontrolle der aktuellen _transfer_ Funktion. Diese Funktion kann diese Vermögenswerte ohne zusätzliche Berechtigungen an die _deposit_ Funktion übergeben.

0x3.3 Aufteilen und Zusammenführen

Im Gegensatz zu Ethereum-Token kann die Zirkulation von Coins nicht durch Änderung der Benutzersalden aktualisiert werden. Stattdessen kann dies durch das Abheben der Coin-Struktur im coin-Modul erreicht werden. Auf diese Weise realisieren Benutzer die Vermögenszirkulation, indem sie diese Struktur an andere Module übergeben. Da eine Struktur nur von dem Modul manipuliert werden kann, das sie definiert, bietet das coin-Modul einige Schnittstellen zur Bedienung der Coin-Struktur, einschließlich des Teilens von Coins in kleinere Einheiten und des Zusammenführens mehrerer Coins, um den Anforderungen verschiedener Szenarien gerecht zu werden.

1. Die Funktion extract wird zum Aufteilen von Coins verwendet. Sie empfängt eine Coin-Struktur, extrahiert einen Teil des Vermögenswerts daraus, um eine neue Coin-Struktur zu erzeugen, und gibt die neue Struktur zurück.

public fun extract<CoinType>(coin: &mut Coin<CoinType>, amount: u64): Coin<CoinType> {
    assert!(coin.value >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE));
    coin.value = coin.value - amount;
    Coin { value: amount }
}

2. Die Funktion extract_all wird verwendet, um den gesamten Wert der ursprünglichen Coin-Struktur zu extrahieren und in eine neue Coin-Struktur einzuzahlen. Infolgedessen wird der Wert der ursprünglichen Coin-Struktur Null (auch als zero_coin bezeichnet). Die zero_coin-Struktur kann durch Aufrufen der Funktion destroy_zero vernichtet werden.

public fun extract_all<CoinType>(coin: &mut Coin<CoinType>): Coin<CoinType> {
    let total_value = coin.value;
    coin.value = 0;
    Coin { value: total_value }
}

public fun destroy_zero<CoinType>(zero_coin: Coin<CoinType>) {
    let Coin { value } = zero_coin;
    assert!(value == 0, error::invalid_argument(EDESTRUCTION_OF_NONZERO_TOKEN))
}

3. Die Funktion merge wird zum Zusammenführen von Coins verwendet. Sie können den Wert von zwei Coin-Strukturen, source_coin und dst_coin, in die dst_coin-Struktur zusammenführen und die source_coin-Struktur vernichten.

public fun merge<CoinType>(dst_coin: &mut Coin<CoinType>, source_coin: Coin<CoinType>) {
    spec {
        assume dst_coin.value + source_coin.value <= MAX_U64;
    };
    dst_coin.value = dst_coin.value + source_coin.value;
    let Coin { value: _ } = source_coin;
}

4. Die Funktion zero wird zur Erzeugung einer zero_coin-Struktur verwendet.

public fun zero<CoinType>(): Coin<CoinType> {
    Coin<CoinType> {
        value: 0
    }
}

0x4. Testen des Coins

Um diesen Coin schnell zu testen, können Sie ihn zuerst mit dem folgenden Befehl veröffentlichen (vergessen Sie nicht, Ihre Publisher-Kontoadresse in Move.toml einzustellen!)

$ aptos move publish --package-dir ./
Compiling, may take a little while to download git dependencies...
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING my_coin
package size 2751 bytes
Do you want to submit a transaction for a range of [868300 - 1302400] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
{
  "Result": {
    "transaction_hash": "xxx",
    ...

Auf diese Weise wurde der Coin erfolgreich auf der Kette veröffentlicht, hat aber keine Zirkulation. Sie müssen Ihr Konto registrieren, um die geprägten Coins zu erhalten.

$ aptos move run --function-id default::bsc::register
Do you want to submit a transaction for a range of [153100 - 229600] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
{
  "Result": {
    "transaction_hash": "xxx",
    ...

Beachten Sie, dass Ihre-Adresse im zweiten Befehl durch Ihre eigene Adresse ersetzt werden muss (siehe account in .aptos/config.yaml). Geben Sie Ihre Adresse im Browser ein und klicken Sie auf den Tab Resources. Sie sehen, dass dieses Konto jetzt 100 BSC-Coins hat.

Wenn Sie Coins an ein anderes Konto übertragen möchten, vergessen Sie nicht, dieses Konto zur Verwendung des Coins zu registrieren. Da die Funktion transfer eine generische Funktion ist, müssen Sie den generischen Parameter als BSC und to_address angeben, wie folgt:

$ aptos move run — function-id 0x1::coin::transfer — type-args BSC-module-address::bsc::BSC — args address:To_address u64:1

Dieser Befehl ruft die Funktion transfer des Coin-Moduls auf, um 1 BSC-Coin an To_address zu übertragen. Hier ist 0x1::coin::transfer die Funktions-ID der Funktion transfer. Denken Sie daran, dass BlockSec::bsc::BSC der Bezeichner Ihres Coins ist, der generische Parameter muss darauf gesetzt werden. Außerdem sollte BSC-module-address durch die Adresse des Modul-Publishers ersetzt werden, die in Move.toml als BlockSec zugewiesen ist.

0x5. Was kommt als Nächstes

Nachdem wir verstanden haben, wie man seinen eigenen Coin erstellt, verwaltet und mit ihm interagiert, werden wir zeigen, wie man das erste DeFi-Grundlagenprojekt baut: Automated Market Maker (AMM). Weitere interessante Themen im Zusammenhang mit der Move-Entwicklung und Sicherheitspraktiken werden behandelt. Bleiben Sie dran!

Referenz

[1] https://aptos.dev/concepts/coin-and-token/aptos-coin/
[2] https://aptos.dev/concepts/coin-and-token/index
[3] https://aptos.dev/concepts/coin-and-token/aptos-token

Sign up for the latest updates