Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create custom currencies (support for crypto like $BTC) #8

Open
carlos-verdes opened this issue Jun 5, 2024 · 11 comments
Open

Create custom currencies (support for crypto like $BTC) #8

carlos-verdes opened this issue Jun 5, 2024 · 11 comments
Assignees
Labels
enhancement New feature or request

Comments

@carlos-verdes
Copy link

You library + quantities is exactly what I was looking for long time ago, even the support for 18 decimals amount without rounding using integer for math operations, the only thing is I need to work with custom currencies, something like:

    let btc_currency = Currency("BTC", 7);
    let eth_currency = Currency("ETH", 18);

Is this an option on your library?

@mamrhein
Copy link
Owner

mamrhein commented Jun 5, 2024

Can you define your custom currencies statically or do you need to create them at runtime?

@carlos-verdes
Copy link
Author

One option I can do is to have a repository with all currencies known today (or whitelisted ones) and then update the list with PR
Another option is to be able to create currencies on runtime (new crypto currencies are created daily).

I'm trying to implement the runtime option based on this library but it's getting messy as you force to implement the Copy type class (why is that by the way?) and this force me to add lifespans everywhere.

Looking forward to reading your thoughts about both options.

@mamrhein mamrhein self-assigned this Jul 8, 2024
@mamrhein mamrhein added the enhancement New feature or request label Jul 8, 2024
@mamrhein
Copy link
Owner

mamrhein commented Jul 8, 2024

I have been looking for solutions for the dynamic extension of enums, but these seem too complex to me. ISO 4217 currencies are limited in number, rarely change and there is a universally recognized authority that maintains them. I would therefore like to stick with a static solution for these.
Cryptocurrencies are - in comparison - very numerous (over 25,000 according to Wikipedia) and there is no controlling authority. That's why a static solution seems inappropriate to me.
I will try to keep the static definition of ISO currencies and combine it with the possibility to create additional currencies at runtime via Currency::new (to be activated via a feature flag).

@carlos-verdes
Copy link
Author

I agree with you and that was the point I was trying to made, in crypto you can create a new currency in seconds and there are actually way more than 25k so the only way to manage is at runtime.

Still all the rules for a currency should apply, for example to change one amount in currency X to an amount on currency Y you need a exchange rate X/Y, I just need this algebra without the static currency types.

Thanks a lot for looking into this I really appreciate the work you put on this crate.

@mamrhein
Copy link
Owner

After some experiments I find that providing the possibility to create additional currencies at runtime would need several breaking changes to the underlying quantities crate.
What I can imagine as possible with a manageable amount of changes (and effort) at the moment is to define Currency as wrapper around two enums, namely ISO_Currency and a generic one which can then be provided by the down-stream code.
Something like:

trait MoneyUnit { ... }
enum ISO_Currency { ... }
enum Currency<T: MoneyUnit> {
  ISO(ISO_Currency),
  Custom(T),
}
struct Money<T> {
  amount: AmountT,
  unit: Currency<T>,
}

And then down-stream:

enum CustomCurrency { ... }
impl MoneyUnit for CustomCurrency { ... } // maybe simplified via macro
type Currency = moneta::Currency<CustomCurrency>
type Money = moneta::Money<CustomCurrency>

This would still force you to define the crypto currencies you need statically, but without any need to have a new version of the crate moneta.

I'd appreciate your comments.
☺︎

@carlos-verdes
Copy link
Author

I think that would work but I don't want to overcomplicate your library just for one use case.
Is there an option to give just an example of how can I build one currency on runtime and still leverage your library?

Something like this:

struct CryptoCurrency { symbol: String }

impl CryptoCurrency {
  fn new ...
}

let btc = CryptoCurrency::new("BTC");
let eth = CryptoCurrency::new("ETH");

let btc_eth_ratio = 
...

@mamrhein
Copy link
Owner

I think supporting crypto currencies is definitely a relevant use case.
But that means supporting Money and ExchangeRate, which in turn requires Currency to implement Unit.
Unit needs to be able to iterate over all its variants. This currently works because all variants are statically defined. That's why my first proposal is to define CustomCurrency as Enum.
Making the creation of Currency instances fully dynamic would require to maintain a dynamic list of the defined quantities and their properties. I tried to replace the static array of Currency variants by a dynamic structure like Vec or HashMap, guarded by a RwLock. But then it's no longer possible to get static references to Curreny instances or their symbol / name. Thus, some changes to the quantity API would be needed.
I will continue to explore both approaches in order to be able to weigh up the pros and cons.

@carlos-verdes
Copy link
Author

I think the best is to separate in two different domains (Currency and CryptoCurrency) as a first approach.

If at some point we can find the convergence without affecting current API then merge with the Enum approach.

I say that because in crypto things are really more complex, for example the symbol can change with time, same symbol can be used by two different projects, the same currency exists in different networks (for example ETH has testnet networks where the value is virtual, and then what is called mainnet network with the "real value"). You have native currencies like ETH and tokens like ERC20 (where you should have the network + smart contract address to identify the token), it's really a domain in itself.

@mamrhein
Copy link
Owner

mamrhein commented Aug 2, 2024

Finally, I managed to redesign the impl of Currency to enable the creation of new currencies.
Please, have a look at the branch custom-currencies-support.
The documentation is not yet updated, but there's a test module test_custom_currency showing the new API.
Important to note:
All currencies are registered in a global static database. ISO currencies are still available as constants and are registered in the database "lazily". Custom currencies must be created by calling Currency::new. This can be done only once w/o error, so it should be done in the main thread. Later (maybe in another thread) an instance can be obtained by calling Currency::from_symbol.
Thanks in advance for your feedback!

@carlos-verdes
Copy link
Author

Thanks for the effort, it looks really good I'll test in my project and let you know.

Only one question, I understand I can't have 2 currencies with the same symbol is that right?

@mamrhein
Copy link
Owner

mamrhein commented Aug 7, 2024

Yes, the symbol is mapped to an u64, which is used as unique identifying key. The mapping is done by stripping all whitespace and eliminaing all non-ascii characters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

When branches are created from issues, their pull requests are automatically linked.

2 participants