Problem
The current token identifier and payment types started as exclusively ESDT, and were then modified to include EGLD, but with a custom token representation in arguments and storage: EGLD.
Then, after Spica it became possible to use EGLD in multi-transfer, with the ticker EGLD-000000. We do have plans to transform EGLD from a special token into a regular ESDT, with this token identifier.
Furthermore, on lightspeed chains EGLD is not necessarily the native token.
So it makes little sense to keep the articifial distinction. The only problem is, because existing contracts already use the EGLD representation, migration cannot happen automatically. We need a new set of types to represent the simplified token identifiers and payments.
Proposition 1
I propose creating types TokenIdent and Payment. Unfortunately, TokenIdentifier is already taken, even though a more correct name for it would have been EgldOrEsdtTokenIdentifier.
Token identifiers are simple byte arrays, with a validation method, but nothing else.
Proposition 2
We should create an API for getting the current native token used for gas in transactions. It is worth eventually adding to the VM, but until then we can hardcode it into contracts via sc-config.toml, to help developers easily build contracts for either mainnet of lightspeed chains.
2 Likes
I honestly don’t like “TokenIdent”, dunno why..
What about the simpler form of “TokenId”, is there a reason you decided against it?
2 Likes
We ultimately decided for TokenId
Proposition 3
The new Payment object amount should be of type NonZeroBigUint, instead of BigUint. This adds a compile time check and invariant, forbidding zero payments. The Payment object should only represent valid transfers.
Absence of payment will no longer be represented as a payment of zero EGLD. Thus EGLD loses its special, default status.
The NonZeroBigUint type is new, and can have other uses as well. It is available in contracts.
Proposition 4
The call_value API should be expanded to include methods that retrieve the new Payment type.
Just like the existing methods, by default these methods should retrieve readonly references to the cached Payment objects retrieved from the VM. The methods should be as follows:
single() - Expect precisely one payment, and retrieve that payment. Crash for no payments, or for multiple payments. This is the modern equivalent of single_esdt and egld_or_single_esdt. Note that egld_or_single_esdt was theonly such method that was returning an owned object (cloning internally). This pattern will not be used again.
single_optional() - Expect either zero or one payments. Returns an Option or Payment reference, more exactly Option<Ref<‘static, Payment<M>>. Unlike `egld_or_single_esdt`, the lack of any payment is not treated as an EGLD transfer of amount zero, but rather as None. This should clean up code and remove EGLDs privileged (or default) status. It is also in line with Proposition 3.
array<const N: usize>() - Expect the exact number of transfers specified by the const length of the array. Modern equivalent of multi_esdt and multi_egld_or_esdt. I think the name array is more suggestive than the previous ones. It returns an array of references, the length of which is known at compile time. The most efficient way of handling multi-transfers, since only one length validation is enough.
Proposition 5
The call value API methods, both old and new need to return readonly references to items in the payment list. These items lie in a ManagedVec, hence the reference type is ManagedVecRef. This might, however, be confusing to developers, which might not understand what the connection to a ManagedVec is, or what that even is.
Therefore, we are proposing to rename ManagedVecRef to just Ref. Of course, the old name is kept as a type alias. This should also make the definitions and, more importantly, the types displayed by rust-analyzer a lot shorter.