Dynamic Types

At some point, we're going to support as many as possible different networks in Substrate ecosystem. So we've started this long term support by introducing dynamic types resolving.

annotation class DynamicType(val lookupIndex: Int)

Simple? We think so! Just apply this annotation to every class you want to be automatically resolved via our magic dynamic type resolver.

But this is not all. Unfortunately, as we don't know exact type in the runtime, its size, and whatnot, we need some backing methods in order to solve this complicated issue.

Therefore, you need to inherit every dynamic type instance from abstract class FromByteArray:

abstract class FromByteArray(byteArray: ByteArray): ByteArrayConvertible

It also implements interface byte array convertible, so in the end it's some unknown type that can either be created from byte array, and convert itself to byte array.

This might sound very complicated, but let's take a look at some examples of what we've made in our library to get more understanding on this topic, so you might express yourself by making more of such dynamic types!

Every network has their Index and Balance types which might be integers of any possible size.

Thankfully, they're backed by static keys 4 and 6. We hope this won't change ever, but we keep our eyes open.

So, here are these two types implementations:

@DynamicType(lookupIndex = 4)
class Index(byteArray: ByteArray): FromByteArray(byteArray) {
    constructor(value: BigInteger) : this(value.toByteArray().reversedArray())

    val value = BigInteger(byteArray.reversedArray())
    override fun toByteArray(): ByteArray = value.toByteArray().reversedArray()
}

/**
 * Balance representation, used in transfers, etc
 */
@DynamicType(lookupIndex = 6)
class Balance(byteArray: ByteArray): FromByteArray(byteArray) {
    constructor(value: BigInteger) : this(value.toByteArray().reversedArray())

    val value = BigInteger(byteArray.reversedArray())
    override fun toByteArray(): ByteArray = value.toByteArray().reversedArray()
}

As we don't know exact internal type for these types above, we decided to make them wrappers around BigInteger. Because it doesn't have a limitation on the size!

So it can be created from any possible integer that is encoded in this type, and when we convert it back to byte array, our smart SCALE codec takes only first X bytes to create this internal type.

During the development of this solution, we found that sometimes BigInteger's content might be not even enough, so we fixed this to fill extra 0 bytes to create actual value.

Also, BigInteger by default works with Big Ending, but at same time SCALE codec works with Little Ending. Hence, ByteArray reversing above.

After all, this is very powerful technique which literally allows you to do whatever you want.

It parses internal type in its designated way, gives you a final byte array, which you can use however you want to create the object of your own wish.

NOTE: not all of the internal types currently supported. Mostly only integers, strings, and other primitives. We're looking forward to supporting all of possible internal types including but not limited to enums and structs, so you can customize any potential object.

Last updated