Back to Blog

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

Code Auditing
November 21, 2022
18 min read

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

TL;DR

Dieser Artikel wird Ihnen Folgendes erklären:

  • Was ist Aptos Coin?
  • Wie erstelle und verwalte ich meinen Coin?
  • Wie interagiere ich mit meinem Coin?

0x1. Über Aptos Coin

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

Ethereum hat einen Satz von Standards für Tokens entwickelt. Der bekannteste ist ERC20, der Schnittstellen definiert, denen ein Standard ERC20 Token entsprechen muss. ERC20 ist ein Standard für fungible Tokens, während es auch Standards für nicht-fungible Tokens gibt, wie z.B. ERC721.

Ähnlich wie andere Blockchain-Systeme verfügt auch Aptos über seinen Token-Standard [2], der definiert, wie digitale Assets auf den jeweiligen Blockchains erstellt und verwendet werden. Insbesondere 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. Erstelle und verwalte deinen ersten Coin

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

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

aptos move init --name my_coin

Anschließend müssen Sie eine neue Move-Datei unter dem Ordner sources erstellen. Füllen wir sie nun mit dem folgenden Beispielcode, der ein Modul namens bsc definiert, um einen Standard-Coin 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 Grundlegende Gestaltung

Betrachten wir zuerst den Strukturteil. Insgesamt werden drei Strukturen definiert.

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

Nach diesen Strukturen gibt es die 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 Identifikator 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 im 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 bestimmten Rechts 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 spätere Verwendung.

Während des Registrierungsprozesses wird außerdem eine Struktur CoinInfo 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 ist Ihr Coin auf der Kette registriert. Allerdings kann niemand diesen Coin verwenden, da derzeit keine Zirkulation existiert. Um den Coin nutzbar zu machen, müssen einige Operationen, einschließlich Emission, Zuteilung und Vernichtung, unterstützt werden. Diese Operationen erfordern die Berechtigungen, die wir bei der Registrierung des Coins erhalten haben.

0x2.2 Coin-Verwaltung

Dieser Coin ist so konzipiert, dass er den folgenden Regeln folgt:

  • 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 bzw. Verbrennen von Coins zuständig; die letzteren drei dienen zum Einfrieren und Entfroren von Konten.

Prägen von Coins

In unserem Modul wird die Funktion mint_coin zum Prägen von Coins verwendet. Da nur Admins Coins prägen dürfen, müssen wir die entsprechende Berechtigung in dieser Funktion ü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 erfordert drei Parameter:

  • cap_owner ist vom Typ &signer, d. h. der Initiator der Transaktion.
  • to_address gibt die Adresse an, auf 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, zu Beginn 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 wird die Funktion mint_coin dann die Funktion mint des Moduls aptos_framework::coin aufrufen, 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 muss ein Parameter namens _cap als Referenz auf MintCapability übergeben werden. Dann wird die damit 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 verifiziert werden.

Verbrennen von Coins

Der Vorgang des Verbrennens von Coins unterscheidet sich vom Prägen. 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 vorübergehend die Berechtigung 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 erfordert zwei Parameter:

  • account ist vom Typ &signer, d. h. der Initiator der Transaktion.
  • amount gibt die Anzahl der zu verbrennenden 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, aber diese Berechtigung ist in der CapStore des Admins gespeichert. Daher müssen wir gewöhnlichen Benutzern erlauben, diese Berechtigung zum Verbrennen der von ihnen gehaltenen Coins zu erhalten.

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 dies zu erreichen, können wir den von der Move-Sprache bereitgestellten Operator borrow_global verwenden. Er wird verwendet, um einen bestimmten Datentyp aus dem unveränderlichen globalen Speicher eines Kontos zu lesen. Mit diesem Operator kann ein Modul anderen Benutzern die Berechtigung des Admins ausleihen. Das heißt, die Admin-Adresse wird benötigt, um die gewünschte Berechtigung zu erhalten.

Da der Transaktionsinitiator der Funktion burn_coin jedoch der Benutzer und nicht der Admin ist, kann die Admin-Adresse nicht wie bei der Funktion mint_coin über signer ermittelt werden. Glücklicherweise kann sie über aptos_std::type_info mit BSC ermittelt werden, um die Adresse des Moduls zu erhalten, in dem diese Struktur definiert ist. Da das Modul unter der Admin-Adresse veröffentlicht wurde, können wir die Admin-Adresse entsprechend ermitteln und schließlich die BurnCapability-Berechtigung erhalten.

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

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 Auftauen von Coin-Konten

Basierend auf den obigen Ausführungen können wir nun die Verwaltung der Coin-Konten leicht nachvollziehen. Insbesondere stellen wir die Funktion freeze_self bereit, damit Benutzer ihre Coin-Konten einfrieren können. Hier stellen wir auch die Funktion emergency_freeze für Notfall-Einfrierungen bereit, die nur vom Admin verwendet werden kann. Darüber hinaus sollten Benutzer aufgrund der Existenz des Notfall-Einfriermechanismus nicht in der Lage sein, sich selbst aufzutauen. Daher erfordert die Funktion unfreeze ebenfalls, dass der Admin die Benutzerkonten auftaut.

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önnten sie missbrauchen, um Coin-Inhabern Schaden zuzufügen!

0x3. Interagiere mit dem Coin

Hier konzentrieren wir uns auf die Art und Weise der Interaktion mit dem Coin.

0x3.1 Registrierung

Möglicherweise haben Sie bemerkt, dass im 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 Ereignisaufzeichnern zu registrieren. Um einen bestimmten Coin zu verwenden, schreibt das Modul aptos_framework::coin vor, 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 dann normal halten, wenn sie diesen Coin-Typ über diese Funktion registriert haben. Das heißt, wenn Sie einen bestimmten Coin nicht halten möchten, können andere diesen Coin nicht ohne Ihre Zustimmung auf Ihr Konto einzahlen. Die Registrierung fügt tatsächlich eine Struktur CoinStore (des Ziel-Coin-Typs) in Ihr Konto ein. Diese CoinStore-Struktur enthält eine Coin-Struktur, um Ihr Guthaben zu speichern.

Tipp#4: Im Gegensatz zu Ethereum-Tokens kann ein Aptos-Coin nicht gehalten und betrieben werden, ohne dass der Benutzer ihn explizit registriert hat.

0x3.2 Überweisung

Angenommen, Sie besitzen nun einige BSC-Coins, dann können Sie diese Coins durch Aufruf der Funktion transfer des Moduls aptos_framework::coin überweisen.

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 Entry-Funktion des coin-Moduls ist. Die Logik besteht aus Aufrufen zweier öffentlicher Funktionen, nämlich withdraw und deposit. Die Funktion withdraw erfordert die &signer-Berechtigung, die zum Abheben eines bestimmten Betrags von Ihren Vermögenswerten von Ihrem Konto in einen Coin verwendet wird. Die Funktion deposit kann einen Coin auf 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 überwiesenen Coins automatisch mit den in der CoinStore-Struktur der Zieladresse gespeicherten Coins zusammengeführt.

Tipp#5: Nach dem Abheben liegen die Vermögenswerte im Coin unter der Kontrolle der aktuellen _transfer_ Funktion. Diese Funktion kann diese Vermögenswerte an die _deposit_ Funktion liefern, ohne zusätzliche Berechtigungen zu erwerben.

0x3.3 Aufteilen und Zusammenführen

Im Gegensatz zu Ethereum-Tokens kann die Zirkulation von Coins nicht durch Änderung der Benutzerguthaben aktualisiert werden. Stattdessen kann sie durch Abheben der Struktur Coin im coin-Modul erreicht werden. Dadurch 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 Aufteilens 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 der Vermögenswerte 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 bekannt als zero_coin). Die zero_coin-Struktur kann durch Aufruf 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 verwendet, um eine zero_coin-Struktur zu erzeugen.

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

0x4. Teste den Coin

Um diesen Coin schnell zu testen, können Sie ihn zuerst mit dem folgenden Befehl bereitstellen (vergessen Sie nicht, Ihre Herausgeber-Konto-Adresse in Move.toml festzulegen!)

$ 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 Your-address 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 die Registerkarte Resources. Sie können sehen, dass dieses Konto nun 100 BSC-Coins hat.

Wenn Sie Coins an ein anderes Konto übertragen möchten, vergessen Sie nicht, dieses Konto für die Nutzung des Coins zu registrieren. Da die Funktion transfer eine generische Funktion ist, müssen Sie den generischen Parameter als BSC und die 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 transfer-Funktion. Denken Sie daran, dass BlockSec::bsc::BSC der Identifikator Ihres Coins ist, der generische Parameter muss darauf gesetzt werden. Außerdem sollte BSC-module-address durch die Konto-Adresse des Modul-Herausgebers ersetzt werden, die in Move.toml als BlockSec zugewiesen ist.

0x5. Was kommt als Nächstes

Nachdem Sie verstanden haben, wie Sie Ihren eigenen Coin erstellen, verwalten und damit interagieren, werden wir zeigen, wie Sie das erste Eckpfeilerprojekt von DeFi bauen: 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
The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis
Security Insights

The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis

This BlockSec deep-dive analyzes the KelpDAO $290M rsETH cross-chain bridge exploit (April 18, 2026), attributed to the Lazarus Group, tracing a causal chain across three layers: how a single-point DVN dependency enabled the attack, how DeFi composability cascaded the damage through Aave V3 lending markets to freeze WETH liquidity exceeding $6.7B across Ethereum, Arbitrum, Base, Mantle, and Linea, and how the crisis forced decentralized governance to exercise centralized emergency powers. The article examines three parameters that shaped the cascade's severity (LTV, pool depth, and cross-chain deployment count) and provides an exclusive technical breakdown of Arbitrum Security Council's forced state transition, an atomic contract upgrade that moved 30,766 ETH without the holder's signature.

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026

This BlockSec weekly security report covers four attack incidents detected between April 13 and April 19, 2026, across multiple chains such as Ethereum, Unichain, Arbitrum, and NEAR, with total estimated losses of approximately $310M. The highlighted incident is the $290M KelpDAO rsETH bridge exploit, where an attacker poisoned the RPC infrastructure of the sole LayerZero DVN to fabricate a cross-chain message, triggering a cascading WETH freeze across five chains and an Arbitrum Security Council forced state transition that raises questions about the actual trust boundaries of decentralized systems. Other incidents include a $242K MMR proof forgery on Hyperbridge, a $1.5M signed integer abuse on Dango, and an $18.4M circular swap path exploit on Rhea Finance's Burrowland protocol.

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026

This BlockSec weekly security report covers four DeFi attack incidents detected between April 6 and April 12, 2026, across Linea, BNB Chain, Arbitrum, Optimism, Avalanche, and Base, with total estimated losses of approximately $928.6K. Notable incidents include a $517K approval-related exploit where a user mistakenly approved a permissionless SquidMulticall contract enabling arbitrary external calls, a $193K business logic flaw in the HB token's reward-settlement logic that allowed direct AMM reserve manipulation, a $165.6K exploit in Denaria's perpetual DEX caused by a rounding asymmetry compounded with an unsafe cast, and a $53K access control issue in XBITVault caused by an initialization-dependent check that failed open. The report provides detailed vulnerability analysis and attack transaction breakdowns for each incident.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit