Skip to content

A CosmWasm contract to enable 1-direction 1:1 conversion of one asset to another.

Notifications You must be signed in to change notification settings

osmosis-labs/transmuter

Repository files navigation

Transmuter

CI

A CosmWasm contract for X:Y swapping between multiple tokens with no fees.

Stored Codes

Version Network Code Id
1.0.0 osmo-test-5 3084
osmosis-1 148
2.0.0 osmo-test-5 4643
osmosis-1 254
3.0.0 osmo-test-5 8319
osmosis-1 814

Overview

transmuter is designed to be used as a cosmwasmpool module. This module enables users to create pools of CosmWasm contracts for token swapping.

Joining or exiting the pool can also be seen as swapping between the pool token and the share token, therefor the share token is treated as a swappable asset and can be exchanged with other tokens using poolmanager's msgs. The share token is also referred to as the Alloyed Asset.

Since the contract is designed to be used as a cosmwasmpool, the code needs to be upload through UploadCosmWasmPoolCodeAndWhiteListProposal or MigratePoolContractsProposal in case of migration instead of the usual StoreCodeProposal.

Alloyed Asset

Alloyed Asset is an asset that is minted when a user joins the pool and burned when a user exits the pool.

  • The amount of Alloyed Asset minted is equal to the amount of tokens that the user has deposited.
  • The amount of Alloyed Asset burned is equal to the amount of tokens that the user has withdrawn.
  • The Alloyed Asset is treated as a swappable asset and can be exchanged 1:1 with other tokens.
    • Alloyed Asset as token out will be minted to the user.
    • Alloyed Asset as token in will be burned from the user.

Since the Alloyed Asset represents tokens that are deposited in the pool, it can be viewed as a token whose value is backed by the underlying tokens in the pool. The risk exposure from each of the underlying tokens to the Alloyed Asset is determined by the weight of each token in the pool. To facilitate risk management, we aim to limit changes in risk through Limiters.

Limiters

There are 2 types of limiters:

Each of these limiters can be used to restrict the maximum weight of each token in the pool. This is important because tokens with higher weights pose a greater risk exposure to the Alloyed Asset. These limiters can be used in combination with each other.

Change Limiter

The Change Limiter determines the upper bound limit based on the Simple Moving Average (SMA) of the pool asset's weights. The SMA is calculated using data points that are divided into divisions, which are compressed for efficient storage read and reduced gas consumption since calculating average of sliding window can require a lot of gas due to read operations.

This can be used in different timeframes to prevent both fast and slow bleeding of the pool asset's weights.

Static Limiter

The Static Limiter determines the upper bound limit based on the pool asset's weights. This serves as limitation for worst case scenarios allowed.

Normalization Factors

Each asset can have different weights in terms of value in the pool, so to make the value of each asset equal, the value of each asset is normalized by a factor. The factor is determined by the admin of the pool and can be changed by the admin.

For example, asset A and asset B have the same value in their display denom, but exponent of A is 6 and exponent of B is 18. To equalize the value of each asset, the normalization factor for A is calculated as 10^(18-6) = 10^12, making the value of A equivalent to B when B's normalization factor is 10^(18-18) = 10^0 = 1.

To explain the logic behind this calculation, the formula is normalization_factor(x) = LCM([asset_weights_per_referenced_unit]) / asset_weight_per_referenced_unit(x):

  • LCM(A, B) = A * B / GCD(A, B) = 10^18 * 10^6 / 10^6 = 10^18
  • normalization_factor(A) = 10^18 / 10^6 = 10^12
  • normalization_factor(B) = 10^18 / 10^18 = 10^0 = 1

With this, we can compare the value of each asset in the pool through the normalized value.


Interface

Create pool

To crate a pool, it requires sending MsgCreateCosmWasmPool which includes code_id and instantiation_msg for the contract, cosmwasmpool module will instantiate the contract under the hood and register the pool.

instantiation_msg example:

{
    "pool_asset_configs": [
      { "denom": "ibc/a..", "normalization_factor": "1000000" },
      { "denom": "ibc/b..", "normalization_factor": "1" }
    ],
    "alloyed_asset_subdenom": "alloyed",
    "alloyed_asset_normalization_factor": "1",
    "admin": "osmo1a..",
    "moderator": "osmo1b.."
}

pool_asset_configs: Vec, alloyed_asset_subdenom: String, alloyed_asset_normalization_factor: Uint128, admin: Option, moderator: Option,

  • pool_asset_denoms - list of denoms that will be used as pool assets
  • alloyed_asset_subdenom - subdenom of the alloyed asset, the resulted denom will be factory/{contract_address}/{alloyed_asset_subdenom}
  • admin - admin address of the contract, it can be transferred later

Join and Exit pool

To join the pool, user needs to the execute the contract with the following message:

{ "join_pool": {} }

And attach funds along with the message with the denom that is registered in the pool.

To exit the pool, user needs to the execute the contract with the following message:

{
  "exit_pool": {
    "tokens_out": [
      { "denom": "uaaa", "amount": "1000000" },
      { "denom": "ubbb", "amount": "1000000" }
    ]
  }
}

Swap

The swap can be performed through poolmanager's msgs which will get routed to the contract's sudo entrypoint.

Administration

Admin address can be set on instantiation of the contract. The admin can be changed by sending:

{ "transfer_admin": { "candidate": "osmo1..." } }

Only candidate can claim the admin role by sending:

{ "claim_admin": {} }

The following are admin only operations:

Set Active Status

As for now, cosmwasmpool module still haven't route pool deactivation to the contract. So for now the only way to deactivate the pool is to have an admin who can deactivate the pool.

Admin is set on instantiate and can send the following msg:

{ "set_active_status": { "active": true } }

With deactivation, the pool will not be able to accept any execute or sudo request except for set_active_status.

Set Alloyed Denom Metadata

Set metadata for alloyed denom.

{
  "set_alloyed_denom_metadata": {
    "base": "alloyed",
    "description": "Alloyed Asset",
    "display": "alloyed",
    "name": "Alloyed Asset",
    "symbol": "ALD",
    "denom_units": [
      {
        "denom": "alloyed",
        "exponent": 0,
        "aliases": []
      },
      {
        "denom": "ualloyed",
        "exponent": 6,
        "aliases": []
      }
    ]
  }
}

Register, Update and Deregister Limiters

register_limiter can be used to register a new limiter.

Example for registering change limiter:

{
  "register_limiter": {
    "denom": "token1",
    "label": "1h",
    "limiter_params": {
      "change_limiter": {
        "window_config": {
          "window_size": "3600000000000",
          "division_count": "5"
        },
        "boundary_offset": "0.2"
      }
    }
  }
}

Example for registering static limiter:

{
  "register_limiter": {
    "denom": "token1",
    "label": "static",
    "limiter_params": {
      "static_limiter": {
        "upper_limit": "0.7"
      }
    }
  }
}

For updating limiter params, change limiter's boundary offset can be updated:

{
  "set_change_limiter_boundary_offset": {
    "denom": "token1",
    "label": "1h",
    "boundary_offset": "0.2"
  }
}

and static limiter's upper limit can be updated:

{
  "set_change_limiter_boundary_offset": {
    "denom": "token1",
    "label": "static",
    "upper_limit": "0.8"
  }
}

Apart from existing ways for updating limiter params, to change any other parameters like window_size, ones need to deregister the limiter and register it again with the new params since those operations requires reconfiguring stored data.

deregister_limiter can be used to deregister the limiter.

{
  "deregister_limiter": {
    "denom": "token1",
    "label": "1h"
  }
}

Assets Management

Admin can add_new_assets

{
  "add_new_assets": {
    "asset_configs": [
      { "denom": "ibc/a..", "normalization_factor": "1000000" },
      { "denom": "ibc/b..", "normalization_factor": "1" }
    ]
  }
}

rescale_normalization_factor which will multiply the normalization factor of each asset with the given factor. This is needed if the soon-to-be added asset requires readjustment of the normalization factor due to LCM of the old asset composition differs from the new one.

{
  "rescale_normalization_factor": {
    "numerator": "10000",
    "denominator": "1"
  }
}

and moderator can mark_corrupted_assets which is needed for Risk and Mitigation strategy.

{
  "mark_corrupted_assets": {
    "assets": ["ibc/a.."]
  }
}

Access Control List

There are 2 special roles in the contract:

  • Admin - Can perform pool management tasks
  • Moderator - Can perform incidence response tasks
Execute Message \ Authorized Role Admin Moderator Admin Candidate
rescale_normalization_factor
add_new_assets
mark_corrupted_assets
unmark_corrupted_assets
register_limiter
deregister_limiter
set_change_limiter_boundary_offset
set_static_limiter_upper_limit
set_alloyed_denom_metadata
set_active_status
transfer_admin
cancel_admin_transfer
reject_admin_transfer
claim_admin
renounce_adminship
assign_moderator
remove_moderator

Apart from the table above, other execute messages has no role restrictions.

Risk and Mitigation

Each underlying asset that makes up the pool (therefore the alloyed asset's value) has it's own risk. In case one of the asset has depegged, it can lead to the loss of the pool's value.

To limit the risk, each asset limiters mentioned above to limit the risk and giving time for pool moderator to react.

In case of a depegged asset, the pool moderator can mark the asset as corrupted, what's following is that, the said asset:

  • can only decrease in both amount and weight, which means, if user wants to redeem another non-corrupted asset, user must redeem equal amount of corrupted asset (because not doing that will make corrupted asset weight increases)
  • in case there is an altruistic actor that wishes to redeem all corrupted asset, they can do so in single transaction and ignore all the limiters
  • Once corrupted assets reaches 0, it will be removed from the pool and resume its operation
  • The limiters will require new setting afterwards since asset weight will no longer account for removed assets