Repeated Minting in tests does not add amounts

If I mint my coin 2 times to the same address, the amounts do not add up. Only the last mint amount shows up in test!!??

module package_addr::my_coin;
//https://docs.sui.io/guides/developer/coin
use sui::coin::{Self, Coin, TreasuryCap, CoinMetadata};

public struct MY_COIN has drop {}
const TOTAL_SUPPLY: u64 = 1_000_000_000_000_000_000;//includes decimal zeros
const INITIAL_SUPPLY: u64 = 900_000_000_000_000_000;
const REMAINING: u64 = 
100_000_000_000_000_000;

fun init(witness: MY_COIN, ctx: &mut TxContext) {
		let (mut treasury, metadata) = coin::create_currency(
				witness,
				9,
				b"MY_COIN",
				b"",
				b"",
				option::none(),
				ctx,
		);
    mint( &mut treasury, INITIAL_SUPPLY, ctx.sender(), ctx);
		transfer::public_freeze_object(metadata);
		transfer::public_transfer(treasury, ctx.sender())
}

public fun mint(
		treasury_cap: &mut TreasuryCap<MY_COIN>,
		amount: u64,
		recipient: address,
		ctx: &mut TxContext,
) {
		let coin = coin::mint(treasury_cap, amount, ctx);
		transfer::public_transfer(coin, recipient)
}

//----------== Test
#[test_only]use sui::test_scenario;
#[test_only] use sui::coin::value;
#[test_only] use std::debug::print as pp;
#[test_only] use std::string::{utf8};

#[test]
fun test_init() {
let admin = @0xAd;
let bob = @0xb0;
let mut sce = test_scenario::begin(admin);
{
    let otw = MY_COIN{};
    init(otw, sce.ctx());
};

sce.next_tx(admin);
{
  let coin = sce.take_from_sender<Coin<MY_COIN>>();
  pp(&utf8(b"admin balc1"));
  pp(&value(&coin));
  assert!(value(&coin) == INITIAL_SUPPLY, 441);
  sce.return_to_sender(coin);
};

//mint 2nd time
sce.next_tx(admin);
{
  let mut treasury = sce.take_from_sender<TreasuryCap<MY_COIN>>();
      mint(
        &mut treasury,
        2000,
        admin,//sce.ctx().sender(),
        sce.ctx()
    );
  sce.return_to_sender(treasury);
};

sce.next_tx(admin);
{
  let coin = sce.take_from_sender<Coin<MY_COIN>>();
  pp(&utf8(b"admin balc2"));
  pp(&value(&coin));// it shows 2000, instead of INITIAL_SUPPLY+2000  !!??
  assert!(value(&coin) == INITIAL_SUPPLY+2000, 442);// Failed here
  sce.return_to_sender(coin);
};
sce.end();
}
2 Likes

Yes, the issue is because they are different Coin objects. In Sui Move, each mint operation creates a new Coin<T> object rather than automatically combining it with existing coins of the same type.

So you will need create another mock transaction to get both coin objects and join them… just shown below

module package_addr::my_coin;
//https://docs.sui.io/guides/developer/coin
use sui::coin::{Self, Coin, TreasuryCap, CoinMetadata};

public struct MY_COIN has drop {}
const TOTAL_SUPPLY: u64 = 1_000_000_000_000_000_000;//includes decimal zeros
const INITIAL_SUPPLY: u64 = 900_000_000_000_000_000;
const REMAINING: u64 = 
100_000_000_000_000_000;

fun init(witness: MY_COIN, ctx: &mut TxContext) {
		let (mut treasury, metadata) = coin::create_currency(
				witness,
				9,
				b"MY_COIN",
				b"",
				b"",
				option::none(),
				ctx,
		);
    mint( &mut treasury, INITIAL_SUPPLY, ctx.sender(), ctx);
		transfer::public_freeze_object(metadata);
		transfer::public_transfer(treasury, ctx.sender())
}

public fun mint(
		treasury_cap: &mut TreasuryCap<MY_COIN>,
		amount: u64,
		recipient: address,
		ctx: &mut TxContext,
) {
		let coin = coin::mint(treasury_cap, amount, ctx);
		transfer::public_transfer(coin, recipient)
}

//----------== Test
#[test_only]use sui::test_scenario;
#[test_only] use sui::coin::value;
#[test_only] use std::debug::print as pp;
#[test_only] use std::string::{utf8};

#[test]
fun test_init() {
    let admin = @0xAd;
    let mut sce = test_scenario::begin(admin);
    {
        let otw = MY_COIN{};
        init(otw, sce.ctx());
    };

    // First transaction - check initial supply
    sce.next_tx(admin);
    {
        let coin = sce.take_from_sender<Coin<MY_COIN>>();
        pp(&utf8(b"admin balc1"));
        pp(&value(&coin));
        assert!(value(&coin) == INITIAL_SUPPLY, 441);
        sce.return_to_sender(coin);
    };

    // Second transaction - mint additional coins
    sce.next_tx(admin);
    {
        let mut treasury = sce.take_from_sender<TreasuryCap<MY_COIN>>();
        mint(
            &mut treasury,
            2000,
            admin,
            sce.ctx()
        );
        sce.return_to_sender(treasury);
    };

    // Third transaction - check combined balance
    sce.next_tx(admin);
    {
        // Take both coins from sender
        let coin1 = sce.take_from_sender<Coin<MY_COIN>>(); // Initial coin
        let coin2 = sce.take_from_sender<Coin<MY_COIN>>(); // Newly minted coin
        
        // Combine them
        let mut combined_coin = coin1;
        coin::join(&mut combined_coin, coin2);
        
        pp(&utf8(b"admin balc2"));
        pp(&value(&combined_coin));
        assert!(value(&combined_coin) == INITIAL_SUPPLY + 2000, 442);
        sce.return_to_sender(combined_coin);
    };
    sce.end();
}

Key points:

  • In the first transaction, we transfer the initial coin back to the sender instead of returning it

  • In the second transaction, we mint new coins which creates a separate Coin object

  • In the third transaction, we:

      Take both coins from the sender
    
      Use coin::join to combine them
    
      Verify the total value
    

This matches real Sui behavior where:

Each mint creates a new Coin object

Wallets typically combine coins automatically

You need to explicitly join coins in tests since the test scenario doesn't automatically combine them

The error occurred because you were only checking one of the two Coin objects that existed after the second mint operation.

I already tested it and code the following

Thank you! That is very detailed explanation :slight_smile:

1 Like