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.

public protocol DynamicType: Codable {
    static var lookupIndex: Int { get }
    
    init(data: Data)
    func toData() -> Data
}

Simple? We think so! Just apply this protocol 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.

In order to handle those issues we introduced DynamicAdapter.

final class DynamicAdapter<T>: ScaleCodecAdapter<T> {
    init(provider: DynamicAdapterProvider) {
        self.provider = provider
        super.init()
    }
}

It's initialzier takes DynamicAdapterProvider

final class DynamicAdapterProvider {}

This a dynamic adapter provider that provides an adapter based on the provided dynamic type via the method:

func adapterProvider(for type: DynamicType.Type) async throws -> AdapterProtocol {}

Back to DynamicAdapter. Since it's a subclass of ScaleCodecAdapter, it overrides two crucial methods defined there:

override func read(_ type: T.Type?, from reader: DataReader) throws -> T {}

override func write(value: T) throws -> Data {}

The first method is used for decoding data dynamically to a specified generic type T using the provided Data Reader.

The second method encodes provided value to Data.

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:

public struct Index: DynamicType, Codable {
    public let value: BigUInt
    
    public init(value: BigUInt) {
        self.value = value
    }
}

// MARK: - Index + DynamicType

extension Index {
    public static var lookupIndex: Int { 4 }
    
    public init(data: Data) {
        self.value = BigUInt(Data(data.reversed()))
    }
    
    public func toData() -> Data {
        Data(value.serialize().reversed())
    }
}

public struct Balance {
    public let value: BigUInt
    public init(value: BigUInt) {
        self.value = value
    }
}

// MARK: - Balance + DynamicType

extension Balance: DynamicType {
    public static var lookupIndex: Int { 6 }
    
    public init(data: Data) {
        self.value = BigUInt(Data(data.reversed()))
    }
  
    public func toData() -> Data {
        Data(value.serialize().reversed())
    }
}

As we don't know exact internal type for these types above, we decided to make them wrappers around BigUInt. 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 BigUInt's content might be not even enough, so we fixed this to fill extra 0 bytes to create actual value.

Also, BigUInt by default works with Big Ending, but at same time SCALE codec works with Little Ending. Hence, Data 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