Practice 2: Creating Token Price Model

Model Overview

We will be creating the below model:

from credmark.cmf.model import Model
from credmark.cmf.types import Price, Some, Token

from models.dtos.price import DexPoolAggregationInput, PoolPriceInfo


@Model.describe(slug='contrib.example-token-price',
                version='1.0',
                display_name='Token Price - weighted by liquidity',
                description='The Current Credmark Supported Price Algorithm',
                developer='Credmark',
                input=Token,
                output=Price)
class TokenPriceModel(Model):
    """
    Return token's price
    """

    WEIGHT_POWER = 4

    def run(self, input: Token) -> Price:
        all_pool_infos = self.context.run_model('price.dex-pool',
                                                input=input,
                                                return_type=Some[PoolPriceInfo])

        # DexPoolAggregationInput is a DTO that embodies
        # -   Token
        # -   Pricing setup, and
        # -   pool information

        pool_aggregator_input = DexPoolAggregationInput(
            **all_pool_infos.dict(),
            **input.dict(),
            weight_power=self.WEIGHT_POWER,
            debug=False)

        return self.context.run_model('price.pool-aggregator',
                                      input=pool_aggregator_input,
                                      return_type=Price)

Model description

Create a new class as a subclass of credmark.cmf.model.Model and then decorate it with the @Model.describe decorator for the model meta data:

@Model.describe(slug='contrib.example-token-price',
                version='1.0',
                display_name='Token Price - weighted by liquidity',
                description='The Current Credmark Supported Price Algorithm',
                developer='Credmark',
                input=Token,
                output=Price)

In @Model.describe, it starts with the unique reference to the model in field slug, and version. Bump the version up every time you update the model because cache will rely on this. display_name, description and developer are descriptive fields, please furnish with explanation and helpful information for the model user.

The input and output fields are critically important fields, because they determines the I/O interface for the model. We use DTO or the general dict type (DTO preferred). Here we specify Token as input and Price as output, both of them are pre-defined DTOs in Cmf. Below are the excerpt of how Price and Token are defined.

class Price(DTO):
    price: float = DTOField(0.0, description='Value of one Token')
    src: Union[str, None] = DTOField(None, description='Source')

class Token(Contract):
    """
    Token represents a fungible Token that conforms to ERC20
    standards
    """
    ...

Model classes

The definition of the model class is shown below.

class TokenPriceModel(Model)

The model class TokenPriceModel needs to be inherited from the Model class. Here we also inherit from another class PriceWeight as it needs some of its defined methods. The model class can have its own methods and variable but it must implement its own method of run(self, input), which takes input data (a Token) and returns a result Price.

Access utilities with self.context, log using self.logger. The type hints for the input argument and output of the run method need to be the same as specified in the @Model.describe decorator.

def run(self, input: Token) -> Price:

Modeling

self.context.run_model method invoke the call to the other model with input and transform the output to a DTO specified. Here, we call price.dex-pool to return DTO type Some[PoolPriceInfo]. Some is a DTO type that can contain some objects of the same type. It's the generic form for list.

        all_pool_infos = self.context.run_model('price.dex-pool',
                                                input=input,
                                                return_type=Some[PoolPriceInfo])

Next, we prepare PoolPriceAggregator DTO which packages the token to be priced, pricing parameters and the pools' information for the final model call to price.pool-aggregator.

        # DexPoolAggregationInput is a DTO that embodies
        # -   token
        # -   pricing setup, and
        # -   pool information

        pool_aggregator_input = DexPoolAggregationInput(
            **all_pool_infos.dict(),
            **input.dict(),
            weight_power=self.WEIGHT_POWER,
            debug=False)

In the end of the model, we use self.context.run_model to call model price.pool-aggregator with the data preprared the input and returns the Price as the output.

        return self.context.run_model('price.pool-aggregator',
                                      input=pool_aggregator_input,
                                      return_type=Price)

Run the final model

Finally, we launch the model. The command-line with the modeling tool with the run command is:

credmark-dev run contrib.example-token-price -i \
'{"address":"0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852"}' -j

And the output will look like this:

2022-10-25 14:47:25,361 - credmark.cmf.engine.context - INFO - Using latest block number 15823165
{
    "slug": "contrib.example-token-price",
    "version": "1.0",
    "chainId": 1,
    "blockNumber": 15823165,
    "output": {
        "price": 45268462101.77488,
        "src": "uniswap-v2|Non-zero:1|Zero:0"
    },
    "dependencies": {
        "dex.primary-tokens": {
            "0.1": 15
        },
        "contract.metadata": {
            "1.0": 1
        },
        "uniswap-v2.get-pools": {
            "1.7": 5
        },
        "uniswap-v2.get-pool-price-info": {
            "1.11": 10
        },
        "uniswap-v2.get-pool-info-token-price": {
            "1.12": 5
        },
        "uniswap-v2.get-weighted-price": {
            "1.7": 4
        },
        "sushiswap.get-v2-factory": {
            "1.0": 1
        },
        "sushiswap.get-pools": {
            "1.5": 1
        },
        "sushiswap.get-pool-info-token-price": {
            "1.10": 1
        },
        "uniswap-v3.get-pools": {
            "1.5": 1
        },
        "uniswap-v3.get-pool-info-token-price": {
            "1.15": 1
        },
        "price.dex-pool": {
            "0.4": 1
        },
        "price.pool-aggregator": {
            "1.8": 1
        },
        "contrib.example-token-price": {
            "1.0": 1
        }
    }
}

For all patient reader, the model source is available at https://github.com/credmark/credmark-models-py/blob/main/models/contrib/kunlun/example_token_price.py

Last updated