I am building a smart contract to allow a member to receive different types of coins using dynamic_object_field with the coin metadata as the name and the coin value as the value.
// Make sure metadata_id: ID is the id of the CoinMetadata from the client
// eq: metadata: &CoinMatadata and metadata_id: object::id(metadata)
// It is easy to mock an ID object than constructing a real CoinMetadata object
public fun receive_coin<T>(member: &mut Member, coin: Coin<T>, metadata_id: ID, ctx: &TxContext){
let member_id = object::id(member);
let amount = coin::value(&coin);
let received_at = tx_context::epoch(ctx);
let from_amount = if(dynamic_object_field::exists_<ID>(&member.id, metadata_id)) {
let existing_coin = dynamic_object_field::borrow_mut<ID, Coin<T>>(&mut member.id, metadata_id);
coin::join(existing_coin, coin);
coin::value(existing_coin)
}else {
dynamic_object_field::add<ID, Coin<T>>(&mut member.id, metadata_id, coin);
0u64
};
let to_amount = from_amount + amount;
let received_coin_event = MemberReceivedCoinEvent {
metadata_id,
member_id,
amount,
from_amount,
to_amount,
received_at
};
event::emit(received_coin_event);
}
The problem I am facing is I have to pass the metadata_id: ID ( or supposedly metadata: &CoinMetadata ) as an argument in the function signature. I decide to use metadata_id: ID instead of metadata: &CoinMetadata because it is easier to write a test but passing metadata_id: ID still requires some validation to make sure that the metadata_id is really the ID of the CoinMetaData.
My question is, is it possible to get metadata from the coin object at the API level on-chain? The coin and metadata are highly correlated, passing the two arguments in a fun signature is very painful.
My question is, is it possible to get metadata from the coin object at the API level on-chain? The coin and metadata are highly correlated, passing the two arguments in a fun signature is very painful.
This is not possible. However, I think you can accomplish your goal (allow a member to receive different types of coins, and aggregate coins of the same type) by using the type of the coin instead of the metadata object ID as your dynamic field key, like this:
use std::type_name;
public fun receive_coin<T>(member: &mut Member, coin: Coin<T>, ctx: &TxContext) {
let member_id = object::id(member);
let amount = coin::value(&coin);
let received_at = tx_context::epoch(ctx);
let coin_type = type_name::get<T>();
let from_amount = if (dynamic_object_field::exists_(&member.id, coin_type)) {
let existing_coin = dynamic_object_field::borrow_mut(&mut member.id, coin_type);
coin::join(existing_coin, coin);
coin::value(existing_coin)
} else {
dynamic_object_field::add(&mut member.id, coin_type, coin);
0u64
};
...
public fun get_name_as_bytes<T>(): vector<u8>{
let name: std::ascii::String = type_name::into_string(type_name::get<T>());
let value = std::ascii::into_bytes(name);
get_type_from_bytes(value)
}
public fun get_name_as_string<T>(): String{
let name: vector<u8> = get_name_as_bytes<T>();
string::utf8(name)
}
// let value = b"00000000000000000000000000000001::loyaltychain::loy::LOY";
// let expected = b"LOY";
public fun get_type_from_bytes(name: vector<u8>): vector<u8> {
// https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/move-stdlib/sources/type_name.move#L11
let ascii_colon = 58u8;
let count = std::vector::length(&name);
let i = count;
let type_name = b"";
while(i > 0 ) {
let char = std::vector::borrow(&name, i-1);
if (*char == ascii_colon) {
break
};
std::vector::insert(&mut type_name, *char, 0);
i = i - 1;
};
type_name
}
and the test in tests/modules/util_test.move
#[test]
public fun test_get_name_as_bytes(){
use loyaltychain::util;
use loyaltychain::loy::{LOY};
let expected = b"LOY";
let result = util::get_name_as_bytes<LOY>();
assert!(result == expected, 0);
}
#[test]
public fun test_get_name_as_string(){
use loyaltychain::util;
use loyaltychain::loy::{LOY};
let expected = string::utf8(b"LOY");
let result = util::get_name_as_string<LOY>();
assert!(result == expected, 0);
}
#[test]
public fun test_get_type_from_bytes(){
use loyaltychain::util;
//It returns LOY without a fully qualified name
{
let expected = b"LOY";
let value = b"00000000000000000000000000000000::loyaltychain::loy::LOY";
let result = util::get_type_from_bytes(value);
std::debug::print(&string::utf8(result));
assert!(result == expected, 0);
};
//It returns LOY
{
let expected = b"LOY";
let value = b"LOY";
let result = util::get_type_from_bytes(value);
std::debug::print(&string::utf8(result));
assert!(result == expected, 0);
};
}