From cb4e6f4010579d900cb8535607c5f40d2ae458b1 Mon Sep 17 00:00:00 2001 From: Abel Armoa <30988000+aarmoa@users.noreply.github.com> Date: Tue, 6 Aug 2024 00:29:55 -0300 Subject: [PATCH] (feat) Added support for the new messages in exchange module, included in the chain upgrade v1.13. Added unit tests and example scripts --- CHANGELOG.md | 9 + .../exchange/23_MsgDecreasePositionMargin.py | 56 ++++++ .../exchange/24_MsgUpdateSpotMarket.py | 54 ++++++ .../exchange/25_MsgUpdateDerivativeMarket.py | 56 ++++++ .../exchange/26_MsgAuthorizeStakeGrants.py | 51 +++++ .../exchange/27_MsgActivateStakeGrant.py | 48 +++++ pyinjective/composer.py | 137 ++++++++++++-- pyinjective/constant.py | 1 + pyinjective/core/market.py | 62 ++++++- pyinjective/core/tokens_file_loader.py | 2 +- pyproject.toml | 2 +- tests/core/test_market.py | 125 +++++++++++-- tests/test_composer.py | 174 +++++++++++++++++- 13 files changed, 734 insertions(+), 43 deletions(-) create mode 100644 examples/chain_client/exchange/23_MsgDecreasePositionMargin.py create mode 100644 examples/chain_client/exchange/24_MsgUpdateSpotMarket.py create mode 100644 examples/chain_client/exchange/25_MsgUpdateDerivativeMarket.py create mode 100644 examples/chain_client/exchange/26_MsgAuthorizeStakeGrants.py create mode 100644 examples/chain_client/exchange/27_MsgActivateStakeGrant.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 90addb65..7e525b92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. +## [1.6.1] - 2024-08-07 +### Added +- Added support for the following messages in the chain "exchange" module: + - MsgDecreasePositionMargin + - MsgUpdateSpotMarket + - MsgUpdateDerivativeMarket + - MsgAuthorizeStakeGrants + - MsgActivateStakeGrant + ## [1.6.0] - 2024-07-30 ### Added - Support for all queries in the chain "tendermint" module diff --git a/examples/chain_client/exchange/23_MsgDecreasePositionMargin.py b/examples/chain_client/exchange/23_MsgDecreasePositionMargin.py new file mode 100644 index 00000000..5914063d --- /dev/null +++ b/examples/chain_client/exchange/23_MsgDecreasePositionMargin.py @@ -0,0 +1,56 @@ +import asyncio +import os +from decimal import Decimal + +import dotenv + +from pyinjective.async_client import AsyncClient +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + dotenv.load_dotenv() + configured_private_key = os.getenv("INJECTIVE_PRIVATE_KEY") + + # select network: local, testnet, mainnet + network = Network.testnet() + + # initialize grpc client + client = AsyncClient(network) + composer = await client.composer() + await client.sync_timeout_height() + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=configured_private_key, + ) + + # load account + priv_key = PrivateKey.from_hex(configured_private_key) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + await client.fetch_account(address.to_acc_bech32()) + subaccount_id = address.get_subaccount_id(index=0) + + # prepare trade info + market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" + + # prepare tx msg + msg = composer.msg_decrease_position_margin( + sender=address.to_acc_bech32(), + market_id=market_id, + source_subaccount_id=subaccount_id, + destination_subaccount_id=subaccount_id, + amount=Decimal(2), + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([msg]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/exchange/24_MsgUpdateSpotMarket.py b/examples/chain_client/exchange/24_MsgUpdateSpotMarket.py new file mode 100644 index 00000000..67d73a4d --- /dev/null +++ b/examples/chain_client/exchange/24_MsgUpdateSpotMarket.py @@ -0,0 +1,54 @@ +import asyncio +import os +from decimal import Decimal + +import dotenv + +from pyinjective.async_client import AsyncClient +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + dotenv.load_dotenv() + configured_private_key = os.getenv("INJECTIVE_PRIVATE_KEY") + + # select network: local, testnet, mainnet + network = Network.testnet() + + # initialize grpc client + client = AsyncClient(network) + await client.initialize_tokens_from_chain_denoms() + composer = await client.composer() + await client.sync_timeout_height() + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=configured_private_key, + ) + + # load account + priv_key = PrivateKey.from_hex(configured_private_key) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + await client.fetch_account(address.to_acc_bech32()) + + # prepare tx msg + message = composer.msg_update_spot_market( + admin=address.to_acc_bech32(), + market_id="0x215970bfdea5c94d8e964a759d3ce6eae1d113900129cc8428267db5ccdb3d1a", + new_ticker="INJ/USDC 2", + new_min_price_tick_size=Decimal("0.01"), + new_min_quantity_tick_size=Decimal("0.01"), + new_min_notional=Decimal("2"), + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/exchange/25_MsgUpdateDerivativeMarket.py b/examples/chain_client/exchange/25_MsgUpdateDerivativeMarket.py new file mode 100644 index 00000000..da2aa593 --- /dev/null +++ b/examples/chain_client/exchange/25_MsgUpdateDerivativeMarket.py @@ -0,0 +1,56 @@ +import asyncio +import os +from decimal import Decimal + +import dotenv + +from pyinjective.async_client import AsyncClient +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + dotenv.load_dotenv() + configured_private_key = os.getenv("INJECTIVE_PRIVATE_KEY") + + # select network: local, testnet, mainnet + network = Network.testnet() + + # initialize grpc client + client = AsyncClient(network) + await client.initialize_tokens_from_chain_denoms() + composer = await client.composer() + await client.sync_timeout_height() + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=configured_private_key, + ) + + # load account + priv_key = PrivateKey.from_hex(configured_private_key) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + await client.fetch_account(address.to_acc_bech32()) + + # prepare tx msg + message = composer.msg_update_derivative_market( + admin=address.to_acc_bech32(), + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + new_ticker="INJ/USDT PERP 2", + new_min_price_tick_size=Decimal("1"), + new_min_quantity_tick_size=Decimal("1"), + new_min_notional=Decimal("2"), + new_initial_margin_ratio=Decimal("0.40"), + new_maintenance_margin_ratio=Decimal("0.085"), + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/exchange/26_MsgAuthorizeStakeGrants.py b/examples/chain_client/exchange/26_MsgAuthorizeStakeGrants.py new file mode 100644 index 00000000..91d52808 --- /dev/null +++ b/examples/chain_client/exchange/26_MsgAuthorizeStakeGrants.py @@ -0,0 +1,51 @@ +import asyncio +import os +from decimal import Decimal + +import dotenv + +from pyinjective.async_client import AsyncClient +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + dotenv.load_dotenv() + configured_private_key = os.getenv("INJECTIVE_PRIVATE_KEY") + + # select network: local, testnet, mainnet + network = Network.testnet() + + # initialize grpc client + client = AsyncClient(network) + await client.initialize_tokens_from_chain_denoms() + composer = await client.composer() + await client.sync_timeout_height() + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=configured_private_key, + ) + + # load account + priv_key = PrivateKey.from_hex(configured_private_key) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + await client.fetch_account(address.to_acc_bech32()) + + # prepare tx msg + grant_authorization = composer.create_grant_authorization( + grantee="inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", + amount=Decimal("1"), + ) + message = composer.msg_authorize_stake_grants(sender=address.to_acc_bech32(), grants=[grant_authorization]) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/exchange/27_MsgActivateStakeGrant.py b/examples/chain_client/exchange/27_MsgActivateStakeGrant.py new file mode 100644 index 00000000..c0ca5d3b --- /dev/null +++ b/examples/chain_client/exchange/27_MsgActivateStakeGrant.py @@ -0,0 +1,48 @@ +import asyncio +import os + +import dotenv + +from pyinjective.async_client import AsyncClient +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + dotenv.load_dotenv() + configured_private_key = os.getenv("INJECTIVE_PRIVATE_KEY") + + # select network: local, testnet, mainnet + network = Network.testnet() + + # initialize grpc client + client = AsyncClient(network) + await client.initialize_tokens_from_chain_denoms() + composer = await client.composer() + await client.sync_timeout_height() + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=configured_private_key, + ) + + # load account + priv_key = PrivateKey.from_hex(configured_private_key) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + await client.fetch_account(address.to_acc_bech32()) + + # prepare tx msg + message = composer.msg_activate_stake_grant( + sender=address.to_acc_bech32(), granter="inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r" + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/pyinjective/composer.py b/pyinjective/composer.py index 16bbc536..bfb87b81 100644 --- a/pyinjective/composer.py +++ b/pyinjective/composer.py @@ -8,7 +8,7 @@ from google.protobuf import any_pb2, json_format, timestamp_pb2 from pyinjective import constant -from pyinjective.constant import ADDITIONAL_CHAIN_FORMAT_DECIMALS, INJ_DENOM +from pyinjective.constant import ADDITIONAL_CHAIN_FORMAT_DECIMALS, INJ_DECIMALS, INJ_DENOM from pyinjective.core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket from pyinjective.core.token import Token from pyinjective.proto.cosmos.authz.v1beta1 import authz_pb2 as cosmos_authz_pb, tx_pb2 as cosmos_authz_tx_pb @@ -453,6 +453,10 @@ def binary_options_order( chain_trigger_price=chain_trigger_price, ) + def create_grant_authorization(self, grantee: str, amount: Decimal) -> injective_exchange_pb.GrantAuthorization: + chain_formatted_amount = int(amount * Decimal(f"1e{INJ_DECIMALS}")) + return injective_exchange_pb.GrantAuthorization(grantee=grantee, amount=str(chain_formatted_amount)) + # region Auction module def MsgBid(self, sender: str, bid_amount: float, round: float): be_amount = Decimal(str(bid_amount)) * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") @@ -570,14 +574,14 @@ def msg_instant_spot_market_launch( base_token = self.tokens[base_denom] quote_token = self.tokens[quote_denom] - chain_min_price_tick_size = min_price_tick_size * Decimal( - f"1e{quote_token.decimals - base_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + chain_min_price_tick_size = ( + quote_token.chain_formatted_value(min_price_tick_size) / base_token.chain_formatted_value(Decimal("1")) + ) * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + chain_min_quantity_tick_size = base_token.chain_formatted_value(min_quantity_tick_size) * Decimal( + f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}" ) - chain_min_quantity_tick_size = min_quantity_tick_size * Decimal( - f"1e{base_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" - ) - chain_min_notional = min_notional * Decimal( - f"1e{quote_token.decimals - base_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + chain_min_notional = quote_token.chain_formatted_value(min_notional) * Decimal( + f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}" ) return injective_exchange_tx_pb.MsgInstantSpotMarketLaunch( @@ -609,15 +613,17 @@ def msg_instant_perpetual_market_launch( ) -> injective_exchange_tx_pb.MsgInstantPerpetualMarketLaunch: quote_token = self.tokens[quote_denom] - chain_min_price_tick_size = min_price_tick_size * Decimal( - f"1e{quote_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + chain_min_price_tick_size = quote_token.chain_formatted_value(min_price_tick_size) * Decimal( + f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}" ) chain_min_quantity_tick_size = min_quantity_tick_size * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") chain_maker_fee_rate = maker_fee_rate * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") chain_taker_fee_rate = taker_fee_rate * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") chain_initial_margin_ratio = initial_margin_ratio * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") chain_maintenance_margin_ratio = maintenance_margin_ratio * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") - chain_min_notional = min_notional * Decimal(f"1e{quote_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + chain_min_notional = quote_token.chain_formatted_value(min_notional) * Decimal( + f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + ) return injective_exchange_tx_pb.MsgInstantPerpetualMarketLaunch( sender=sender, @@ -656,15 +662,17 @@ def msg_instant_expiry_futures_market_launch( ) -> injective_exchange_tx_pb.MsgInstantPerpetualMarketLaunch: quote_token = self.tokens[quote_denom] - chain_min_price_tick_size = min_price_tick_size * Decimal( - f"1e{quote_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + chain_min_price_tick_size = quote_token.chain_formatted_value(min_price_tick_size) * Decimal( + f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}" ) chain_min_quantity_tick_size = min_quantity_tick_size * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") chain_maker_fee_rate = maker_fee_rate * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") chain_taker_fee_rate = taker_fee_rate * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") chain_initial_margin_ratio = initial_margin_ratio * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") chain_maintenance_margin_ratio = maintenance_margin_ratio * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") - chain_min_notional = min_notional * Decimal(f"1e{quote_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + chain_min_notional = quote_token.chain_formatted_value(min_notional) * Decimal( + f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + ) return injective_exchange_tx_pb.MsgInstantExpiryFuturesMarketLaunch( sender=sender, @@ -1306,7 +1314,9 @@ def msg_instant_binary_options_market_launch( chain_min_quantity_tick_size = min_quantity_tick_size * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") chain_maker_fee_rate = maker_fee_rate * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") chain_taker_fee_rate = taker_fee_rate * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") - chain_min_notional = min_notional * Decimal(f"1e{quote_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + chain_min_notional = quote_token.chain_formatted_value(min_notional) * Decimal( + f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + ) return injective_exchange_tx_pb.MsgInstantBinaryOptionsMarketLaunch( sender=sender, @@ -1802,6 +1812,103 @@ def msg_admin_update_binary_options_market( status=status, ) + def msg_decrease_position_margin( + self, + sender: str, + source_subaccount_id: str, + destination_subaccount_id: str, + market_id: str, + amount: Decimal, + ): + market = self.derivative_markets[market_id] + + additional_margin = market.margin_to_chain_format(human_readable_value=amount) + return injective_exchange_tx_pb.MsgDecreasePositionMargin( + sender=sender, + source_subaccount_id=source_subaccount_id, + destination_subaccount_id=destination_subaccount_id, + market_id=market_id, + amount=str(int(additional_margin)), + ) + + def msg_update_spot_market( + self, + admin: str, + market_id: str, + new_ticker: str, + new_min_price_tick_size: Decimal, + new_min_quantity_tick_size: Decimal, + new_min_notional: Decimal, + ) -> injective_exchange_tx_pb.MsgUpdateSpotMarket: + market = self.spot_markets[market_id] + + chain_min_price_tick_size = new_min_price_tick_size * Decimal( + f"1e{market.quote_token.decimals - market.base_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + ) + chain_min_quantity_tick_size = new_min_quantity_tick_size * Decimal( + f"1e{market.base_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + ) + chain_min_notional = new_min_notional * Decimal( + f"1e{market.quote_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + ) + + return injective_exchange_tx_pb.MsgUpdateSpotMarket( + admin=admin, + market_id=market_id, + new_ticker=new_ticker, + new_min_price_tick_size=f"{chain_min_price_tick_size.normalize():f}", + new_min_quantity_tick_size=f"{chain_min_quantity_tick_size.normalize():f}", + new_min_notional=f"{chain_min_notional.normalize():f}", + ) + + def msg_update_derivative_market( + self, + admin: str, + market_id: str, + new_ticker: str, + new_min_price_tick_size: Decimal, + new_min_quantity_tick_size: Decimal, + new_min_notional: Decimal, + new_initial_margin_ratio: Decimal, + new_maintenance_margin_ratio: Decimal, + ) -> injective_exchange_tx_pb.MsgUpdateDerivativeMarket: + market = self.derivative_markets[market_id] + + chain_min_price_tick_size = new_min_price_tick_size * Decimal( + f"1e{market.quote_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + ) + chain_min_quantity_tick_size = new_min_quantity_tick_size * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + chain_min_notional = new_min_notional * Decimal( + f"1e{market.quote_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + ) + chain_initial_margin_ratio = new_initial_margin_ratio * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + chain_maintenance_margin_ratio = new_maintenance_margin_ratio * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + + return injective_exchange_tx_pb.MsgUpdateDerivativeMarket( + admin=admin, + market_id=market_id, + new_ticker=new_ticker, + new_min_price_tick_size=f"{chain_min_price_tick_size.normalize():f}", + new_min_quantity_tick_size=f"{chain_min_quantity_tick_size.normalize():f}", + new_min_notional=f"{chain_min_notional.normalize():f}", + new_initial_margin_ratio=f"{chain_initial_margin_ratio.normalize():f}", + new_maintenance_margin_ratio=f"{chain_maintenance_margin_ratio.normalize():f}", + ) + + def msg_authorize_stake_grants( + self, sender: str, grants: List[injective_exchange_pb.GrantAuthorization] + ) -> injective_exchange_tx_pb.MsgAuthorizeStakeGrants: + return injective_exchange_tx_pb.MsgAuthorizeStakeGrants( + sender=sender, + grants=grants, + ) + + def msg_activate_stake_grant(self, sender: str, granter: str) -> injective_exchange_tx_pb.MsgActivateStakeGrant: + return injective_exchange_tx_pb.MsgActivateStakeGrant( + sender=sender, + granter=granter, + ) + # endregion # region Insurance module diff --git a/pyinjective/constant.py b/pyinjective/constant.py index 07274205..95fe2aa7 100644 --- a/pyinjective/constant.py +++ b/pyinjective/constant.py @@ -7,6 +7,7 @@ ADDITIONAL_CHAIN_FORMAT_DECIMALS = 18 TICKER_TOKENS_SEPARATOR = "/" INJ_DENOM = "inj" +INJ_DECIMALS = 18 devnet_config = ConfigParser() devnet_config.read(os.path.join(os.path.dirname(__file__), "denoms_devnet.ini")) diff --git a/pyinjective/core/market.py b/pyinjective/core/market.py index 3fb5fed4..ea28c9fe 100644 --- a/pyinjective/core/market.py +++ b/pyinjective/core/market.py @@ -36,6 +36,15 @@ def price_to_chain_format(self, human_readable_value: Decimal) -> Decimal: return extended_chain_formatted_value + def notional_to_chain_format(self, human_readable_value: Decimal) -> Decimal: + decimals = self.quote_token.decimals + chain_formatted_value = human_readable_value * Decimal(f"1e{decimals}") + quantization_factor = self.min_notional if self.min_notional > Decimal("0") else self.min_quantity_tick_size + quantized_value = (chain_formatted_value // quantization_factor) * quantization_factor + extended_chain_formatted_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + + return extended_chain_formatted_value + def quantity_from_chain_format(self, chain_value: Decimal) -> Decimal: return chain_value / Decimal(f"1e{self.base_token.decimals}") @@ -43,12 +52,18 @@ def price_from_chain_format(self, chain_value: Decimal) -> Decimal: decimals = self.base_token.decimals - self.quote_token.decimals return chain_value * Decimal(f"1e{decimals}") + def notional_from_chain_format(self, chain_value: Decimal) -> Decimal: + return chain_value / Decimal(f"1e{self.quote_token.decimals}") + def quantity_from_extended_chain_format(self, chain_value: Decimal) -> Decimal: return self._from_extended_chain_format(chain_value=self.quantity_from_chain_format(chain_value=chain_value)) def price_from_extended_chain_format(self, chain_value: Decimal) -> Decimal: return self._from_extended_chain_format(chain_value=self.price_from_chain_format(chain_value=chain_value)) + def notional_from_extended_chain_format(self, chain_value: Decimal) -> Decimal: + return self._from_extended_chain_format(chain_value=self.notional_from_chain_format(chain_value=chain_value)) + def _from_extended_chain_format(self, chain_value: Decimal) -> Decimal: return chain_value / Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") @@ -89,12 +104,7 @@ def price_to_chain_format(self, human_readable_value: Decimal) -> Decimal: return extended_chain_formatted_value def margin_to_chain_format(self, human_readable_value: Decimal) -> Decimal: - decimals = self.quote_token.decimals - chain_formatted_value = human_readable_value * Decimal(f"1e{decimals}") - quantized_value = (chain_formatted_value // self.min_quantity_tick_size) * self.min_quantity_tick_size - extended_chain_formatted_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") - - return extended_chain_formatted_value + return self.notional_to_chain_format(human_readable_value=human_readable_value) def calculate_margin_in_chain_format( self, human_readable_quantity: Decimal, human_readable_price: Decimal, leverage: Decimal @@ -109,6 +119,15 @@ def calculate_margin_in_chain_format( return extended_chain_formatted_margin + def notional_to_chain_format(self, human_readable_value: Decimal) -> Decimal: + decimals = self.quote_token.decimals + chain_formatted_value = human_readable_value * Decimal(f"1e{decimals}") + quantization_factor = self.min_notional if self.min_notional > Decimal("0") else self.min_quantity_tick_size + quantized_value = (chain_formatted_value // quantization_factor) * quantization_factor + extended_chain_formatted_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + + return extended_chain_formatted_value + def quantity_from_chain_format(self, chain_value: Decimal) -> Decimal: return chain_value @@ -116,7 +135,10 @@ def price_from_chain_format(self, chain_value: Decimal) -> Decimal: return chain_value * Decimal(f"1e-{self.quote_token.decimals}") def margin_from_chain_format(self, chain_value: Decimal) -> Decimal: - return chain_value * Decimal(f"1e-{self.quote_token.decimals}") + return self.notional_from_chain_format(chain_value=chain_value) + + def notional_from_chain_format(self, chain_value: Decimal) -> Decimal: + return chain_value / Decimal(f"1e{self.quote_token.decimals}") def quantity_from_extended_chain_format(self, chain_value: Decimal) -> Decimal: return self._from_extended_chain_format(chain_value=self.quantity_from_chain_format(chain_value=chain_value)) @@ -125,7 +147,10 @@ def price_from_extended_chain_format(self, chain_value: Decimal) -> Decimal: return self._from_extended_chain_format(chain_value=self.price_from_chain_format(chain_value=chain_value)) def margin_from_extended_chain_format(self, chain_value: Decimal) -> Decimal: - return self._from_extended_chain_format(chain_value=self.margin_from_chain_format(chain_value=chain_value)) + return self.notional_from_extended_chain_format(chain_value=chain_value) + + def notional_from_extended_chain_format(self, chain_value: Decimal) -> Decimal: + return self._from_extended_chain_format(chain_value=self.notional_from_chain_format(chain_value=chain_value)) def _from_extended_chain_format(self, chain_value: Decimal) -> Decimal: return chain_value / Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") @@ -206,6 +231,15 @@ def calculate_margin_in_chain_format( return extended_chain_formatted_margin + def notional_to_chain_format(self, human_readable_value: Decimal) -> Decimal: + decimals = self.quote_token.decimals + chain_formatted_value = human_readable_value * Decimal(f"1e{decimals}") + quantization_factor = self.min_notional if self.min_notional > Decimal("0") else self.min_quantity_tick_size + quantized_value = (chain_formatted_value // quantization_factor) * quantization_factor + extended_chain_formatted_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + + return extended_chain_formatted_value + def quantity_from_chain_format(self, chain_value: Decimal, special_denom: Optional[Denom] = None) -> Decimal: # Binary option markets do not have a base market to provide the number of decimals decimals = 0 if special_denom is None else special_denom.base @@ -215,6 +249,12 @@ def price_from_chain_format(self, chain_value: Decimal, special_denom: Optional[ decimals = self.quote_token.decimals if special_denom is None else special_denom.quote return chain_value * Decimal(f"1e-{decimals}") + def margin_from_chain_format(self, chain_value: Decimal) -> Decimal: + return self.notional_from_chain_format(chain_value=chain_value) + + def notional_from_chain_format(self, chain_value: Decimal) -> Decimal: + return chain_value / Decimal(f"1e{self.quote_token.decimals}") + def quantity_from_extended_chain_format( self, chain_value: Decimal, special_denom: Optional[Denom] = None ) -> Decimal: @@ -227,5 +267,11 @@ def price_from_extended_chain_format(self, chain_value: Decimal, special_denom: chain_value=self.price_from_chain_format(chain_value=chain_value, special_denom=special_denom) ) + def margin_from_extended_chain_format(self, chain_value: Decimal) -> Decimal: + return self.notional_from_extended_chain_format(chain_value=chain_value) + + def notional_from_extended_chain_format(self, chain_value: Decimal) -> Decimal: + return self._from_extended_chain_format(chain_value=self.notional_from_chain_format(chain_value=chain_value)) + def _from_extended_chain_format(self, chain_value: Decimal) -> Decimal: return chain_value / Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") diff --git a/pyinjective/core/tokens_file_loader.py b/pyinjective/core/tokens_file_loader.py index 72dd30ed..0e84bebd 100644 --- a/pyinjective/core/tokens_file_loader.py +++ b/pyinjective/core/tokens_file_loader.py @@ -15,7 +15,7 @@ def load_json(self, json: List[Dict]) -> List[Token]: name=token_info["name"], symbol=token_info["symbol"], denom=token_info["denom"], - address=token_info["address"], + address=token_info.get("address", ""), decimals=token_info["decimals"], logo=token_info["logo"], updated=-1, diff --git a/pyproject.toml b/pyproject.toml index ba4ba921..20eb0112 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "injective-py" -version = "1.6.1-rc1" +version = "1.6.1" description = "Injective Python SDK, with Exchange API Client" authors = ["Injective Labs "] license = "Apache-2.0" diff --git a/tests/core/test_market.py b/tests/core/test_market.py index fcb1dabd..2a6d26e5 100644 --- a/tests/core/test_market.py +++ b/tests/core/test_market.py @@ -33,7 +33,18 @@ def test_convert_price_to_chain_format(self, inj_usdt_spot_market: SpotMarket): quantized_value = ( expected_value // inj_usdt_spot_market.min_price_tick_size ) * inj_usdt_spot_market.min_price_tick_size - quantized_chain_format_value = quantized_value * Decimal("1e18") + quantized_chain_format_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + + assert quantized_chain_format_value == chain_value + + def test_convert_notional_to_chain_format(self, inj_usdt_spot_market: SpotMarket): + original_notional = Decimal("123.456789") + + chain_value = inj_usdt_spot_market.notional_to_chain_format(human_readable_value=original_notional) + notional_decimals = inj_usdt_spot_market.quote_token.decimals + expected_value = original_notional * Decimal(f"1e{notional_decimals}") + quantized_value = (expected_value // inj_usdt_spot_market.min_notional) * inj_usdt_spot_market.min_notional + quantized_chain_format_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") assert quantized_chain_format_value == chain_value @@ -54,6 +65,15 @@ def test_convert_price_from_chain_format(self, inj_usdt_spot_market: SpotMarket) assert expected_price == human_readable_price + def test_convert_notional_from_chain_format(self, inj_usdt_spot_market: SpotMarket): + expected_notional = Decimal("123.456") + + notional_decimals = inj_usdt_spot_market.quote_token.decimals + chain_format_notional = expected_notional * Decimal(f"1e{notional_decimals}") + human_readable_notional = inj_usdt_spot_market.notional_from_chain_format(chain_value=chain_format_notional) + + assert expected_notional == human_readable_notional + def test_convert_quantity_from_extended_chain_format(self, inj_usdt_spot_market: SpotMarket): expected_quantity = Decimal("123.456") @@ -79,6 +99,19 @@ def test_convert_price_from_extended_chain_format(self, inj_usdt_spot_market: Sp assert expected_price == human_readable_price + def test_convert_notional_from_extended_chain_format(self, inj_usdt_spot_market: SpotMarket): + expected_notional = Decimal("123.456") + + notional_decimals = inj_usdt_spot_market.quote_token.decimals + chain_format_notional = ( + expected_notional * Decimal(f"1e{notional_decimals}") * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + ) + human_readable_notional = inj_usdt_spot_market.notional_from_extended_chain_format( + chain_value=chain_format_notional + ) + + assert expected_notional == human_readable_notional + class TestDerivativeMarket: def test_convert_quantity_to_chain_format(self, btc_usdt_perp_market: DerivativeMarket): @@ -88,7 +121,7 @@ def test_convert_quantity_to_chain_format(self, btc_usdt_perp_market: Derivative quantized_value = ( original_quantity // btc_usdt_perp_market.min_quantity_tick_size ) * btc_usdt_perp_market.min_quantity_tick_size - quantized_chain_format_value = quantized_value * Decimal("1e18") + quantized_chain_format_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") assert quantized_chain_format_value == chain_value @@ -101,7 +134,7 @@ def test_convert_price_to_chain_format(self, btc_usdt_perp_market: DerivativeMar quantized_value = ( expected_value // btc_usdt_perp_market.min_price_tick_size ) * btc_usdt_perp_market.min_price_tick_size - quantized_chain_format_value = quantized_value * Decimal("1e18") + quantized_chain_format_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") assert quantized_chain_format_value == chain_value @@ -114,7 +147,18 @@ def test_convert_margin_to_chain_format(self, btc_usdt_perp_market: DerivativeMa quantized_value = ( expected_value // btc_usdt_perp_market.min_quantity_tick_size ) * btc_usdt_perp_market.min_quantity_tick_size - quantized_chain_format_value = quantized_value * Decimal("1e18") + quantized_chain_format_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + + assert quantized_chain_format_value == chain_value + + def test_convert_notional_to_chain_format(self, btc_usdt_perp_market: DerivativeMarket): + original_notional = Decimal("123.456789") + + chain_value = btc_usdt_perp_market.notional_to_chain_format(human_readable_value=original_notional) + notional_decimals = btc_usdt_perp_market.quote_token.decimals + expected_value = original_notional * Decimal(f"1e{notional_decimals}") + quantized_value = (expected_value // btc_usdt_perp_market.min_notional) * btc_usdt_perp_market.min_notional + quantized_chain_format_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") assert quantized_chain_format_value == chain_value @@ -144,6 +188,15 @@ def test_convert_margin_from_chain_format(self, btc_usdt_perp_market: Derivative assert expected_margin == human_readable_margin + def test_convert_notional_from_chain_format(self, btc_usdt_perp_market: DerivativeMarket): + expected_notional = Decimal("123.456") + + notional_decimals = btc_usdt_perp_market.quote_token.decimals + chain_format_notional = expected_notional * Decimal(f"1e{notional_decimals}") + human_readable_notional = btc_usdt_perp_market.notional_from_chain_format(chain_value=chain_format_notional) + + assert expected_notional == human_readable_notional + def test_convert_quantity_from_extended_chain_format(self, btc_usdt_perp_market: DerivativeMarket): expected_quantity = Decimal("123.456") @@ -176,6 +229,19 @@ def test_convert_margin_from_extended_chain_format(self, btc_usdt_perp_market: D assert expected_margin == human_readable_margin + def test_convert_notional_from_extended_chain_format(self, btc_usdt_perp_market: DerivativeMarket): + expected_notional = Decimal("123.456") + + notional_decimals = btc_usdt_perp_market.quote_token.decimals + chain_format_notional = ( + expected_notional * Decimal(f"1e{notional_decimals}") * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + ) + human_readable_notional = btc_usdt_perp_market.notional_from_extended_chain_format( + chain_value=chain_format_notional + ) + + assert expected_notional == human_readable_notional + class TestBinaryOptionMarket: def test_convert_quantity_to_chain_format_with_fixed_denom(self, first_match_bet_market: BinaryOptionMarket): @@ -196,7 +262,7 @@ def test_convert_quantity_to_chain_format_with_fixed_denom(self, first_match_bet quantized_value = (chain_formatted_quantity // Decimal(str(fixed_denom.min_quantity_tick_size))) * Decimal( str(fixed_denom.min_quantity_tick_size) ) - quantized_chain_format_value = quantized_value * Decimal("1e18") + quantized_chain_format_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") assert quantized_chain_format_value == chain_value @@ -209,7 +275,7 @@ def test_convert_quantity_to_chain_format_without_fixed_denom(self, first_match_ quantized_value = ( original_quantity // first_match_bet_market.min_quantity_tick_size ) * first_match_bet_market.min_quantity_tick_size - quantized_chain_format_value = quantized_value * Decimal("1e18") + quantized_chain_format_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") assert quantized_chain_format_value == chain_value @@ -233,7 +299,7 @@ def test_convert_price_to_chain_format_with_fixed_denom(self, first_match_bet_ma quantized_value = (expected_value // Decimal(str(fixed_denom.min_price_tick_size))) * Decimal( str(fixed_denom.min_price_tick_size) ) - quantized_chain_format_value = quantized_value * Decimal("1e18") + quantized_chain_format_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") assert quantized_chain_format_value == chain_value @@ -246,7 +312,7 @@ def test_convert_price_to_chain_format_without_fixed_denom(self, first_match_bet quantized_value = ( expected_value // first_match_bet_market.min_price_tick_size ) * first_match_bet_market.min_price_tick_size - quantized_chain_format_value = quantized_value * Decimal("1e18") + quantized_chain_format_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") assert quantized_chain_format_value == chain_value @@ -270,7 +336,7 @@ def test_convert_margin_to_chain_format_with_fixed_denom(self, first_match_bet_m quantized_value = (expected_value // Decimal(str(fixed_denom.min_quantity_tick_size))) * Decimal( str(fixed_denom.min_quantity_tick_size) ) - quantized_chain_format_value = quantized_value * Decimal("1e18") + quantized_chain_format_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") assert quantized_chain_format_value == chain_value @@ -283,7 +349,7 @@ def test_convert_margin_to_chain_format_without_fixed_denom(self, first_match_be quantized_value = ( expected_value // first_match_bet_market.min_quantity_tick_size ) * first_match_bet_market.min_quantity_tick_size - quantized_chain_format_value = quantized_value * Decimal("1e18") + quantized_chain_format_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") assert quantized_chain_format_value == chain_value @@ -314,7 +380,7 @@ def test_calculate_margin_for_buy_with_fixed_denom(self, first_match_bet_market: quantized_margin = (expected_margin // Decimal(str(fixed_denom.min_quantity_tick_size))) * Decimal( str(fixed_denom.min_quantity_tick_size) ) - quantized_chain_format_margin = quantized_margin * Decimal("1e18") + quantized_chain_format_margin = quantized_margin * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") assert quantized_chain_format_margin == chain_value @@ -334,7 +400,7 @@ def test_calculate_margin_for_buy_without_fixed_denom(self, first_match_bet_mark quantized_margin = (expected_margin // Decimal(str(first_match_bet_market.min_quantity_tick_size))) * Decimal( str(first_match_bet_market.min_quantity_tick_size) ) - quantized_chain_format_margin = quantized_margin * Decimal("1e18") + quantized_chain_format_margin = quantized_margin * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") assert quantized_chain_format_margin == chain_value @@ -354,10 +420,21 @@ def test_calculate_margin_for_sell_without_fixed_denom(self, first_match_bet_mar quantized_margin = (expected_margin // Decimal(str(first_match_bet_market.min_quantity_tick_size))) * Decimal( str(first_match_bet_market.min_quantity_tick_size) ) - quantized_chain_format_margin = quantized_margin * Decimal("1e18") + quantized_chain_format_margin = quantized_margin * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") assert quantized_chain_format_margin == chain_value + def test_convert_notional_to_chain_format(self, first_match_bet_market: BinaryOptionMarket): + original_notional = Decimal("123.456789") + + chain_value = first_match_bet_market.notional_to_chain_format(human_readable_value=original_notional) + notional_decimals = first_match_bet_market.quote_token.decimals + expected_value = original_notional * Decimal(f"1e{notional_decimals}") + quantized_value = (expected_value // first_match_bet_market.min_notional) * first_match_bet_market.min_notional + quantized_chain_format_value = quantized_value * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + + assert quantized_chain_format_value == chain_value + def test_convert_quantity_from_chain_format_with_fixed_denom(self, first_match_bet_market: BinaryOptionMarket): original_quantity = Decimal("123.456789") fixed_denom = Denom( @@ -415,6 +492,15 @@ def test_convert_price_from_chain_format_without_fixed_denom(self, first_match_b assert original_price == human_readable_price + def test_convert_notional_from_chain_format(self, first_match_bet_market: BinaryOptionMarket): + expected_notional = Decimal("123.456") + + notional_decimals = first_match_bet_market.quote_token.decimals + chain_format_notional = expected_notional * Decimal(f"1e{notional_decimals}") + human_readable_notional = first_match_bet_market.notional_from_chain_format(chain_value=chain_format_notional) + + assert expected_notional == human_readable_notional + def test_convert_quantity_from_extended_chain_format_with_fixed_denom( self, first_match_bet_market: BinaryOptionMarket ): @@ -489,3 +575,16 @@ def test_convert_price_from_extended_chain_format_without_fixed_denom( ) assert original_price == human_readable_price + + def test_convert_notional_from_extended_chain_format(self, first_match_bet_market: BinaryOptionMarket): + expected_notional = Decimal("123.456") + + notional_decimals = first_match_bet_market.quote_token.decimals + chain_format_notional = ( + expected_notional * Decimal(f"1e{notional_decimals}") * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + ) + human_readable_notional = first_match_bet_market.notional_from_extended_chain_format( + chain_value=chain_format_notional + ) + + assert expected_notional == human_readable_notional diff --git a/tests/test_composer.py b/tests/test_composer.py index e65a872b..b3dd1fda 100644 --- a/tests/test_composer.py +++ b/tests/test_composer.py @@ -5,7 +5,7 @@ from google.protobuf import json_format from pyinjective.composer import Composer -from pyinjective.constant import ADDITIONAL_CHAIN_FORMAT_DECIMALS +from pyinjective.constant import ADDITIONAL_CHAIN_FORMAT_DECIMALS, INJ_DECIMALS from pyinjective.core.network import Network from tests.model_fixtures.markets_fixtures import btc_usdt_perp_market # noqa: F401 from tests.model_fixtures.markets_fixtures import first_match_bet_market # noqa: F401 @@ -321,9 +321,7 @@ def test_msg_instant_spot_market_launch(self, basic_composer): expected_min_quantity_tick_size = min_quantity_tick_size * Decimal( f"1e{base_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" ) - expected_min_notional = min_notional * Decimal( - f"1e{quote_token.decimals - base_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" - ) + expected_min_notional = min_notional * Decimal(f"1e{quote_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}") message = basic_composer.msg_instant_spot_market_launch( sender=sender, @@ -1440,7 +1438,7 @@ def test_msg_emergency_settle_market(self, basic_composer): ) assert dict_message == expected_message - def test_msg_external_transfer(self, basic_composer): + def test_msg_increase_position_margin(self, basic_composer): market = list(basic_composer.derivative_markets.values())[0] sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" source_subaccount_id = "0x893f2abf8034627e50cbc63923120b1122503ce0000000000000000000000001" @@ -1470,6 +1468,36 @@ def test_msg_external_transfer(self, basic_composer): ) assert dict_message == expected_message + def test_msg_decrease_position_margin(self, basic_composer): + market = list(basic_composer.derivative_markets.values())[0] + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + source_subaccount_id = "0x893f2abf8034627e50cbc63923120b1122503ce0000000000000000000000001" + destination_subaccount_id = "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000" + amount = Decimal(100) + + expected_amount = market.margin_to_chain_format(human_readable_value=amount) + + message = basic_composer.msg_decrease_position_margin( + sender=sender, + source_subaccount_id=source_subaccount_id, + destination_subaccount_id=destination_subaccount_id, + market_id=market.id, + amount=amount, + ) + + expected_message = { + "sender": sender, + "sourceSubaccountId": source_subaccount_id, + "destinationSubaccountId": destination_subaccount_id, + "marketId": market.id, + "amount": f"{expected_amount.normalize():f}", + } + dict_message = json_format.MessageToDict( + message=message, + always_print_fields_with_no_presence=True, + ) + assert dict_message == expected_message + def test_msg_rewards_opt_out(self, basic_composer): sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" @@ -1519,6 +1547,142 @@ def test_msg_admin_update_binary_options_market(self, basic_composer): ) assert dict_message == expected_message + def test_msg_update_spot_market(self, basic_composer): + market = list(basic_composer.spot_markets.values())[0] + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + new_ticker = "NEW/TICKER" + min_price_tick_size = Decimal("0.0009") + min_quantity_tick_size = Decimal("10") + min_notional = Decimal("5") + + expected_min_price_tick_size = min_price_tick_size * Decimal( + f"1e{market.quote_token.decimals - market.base_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + ) + expected_min_quantity_tick_size = min_quantity_tick_size * Decimal( + f"1e{market.base_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + ) + expected_min_notional = min_notional * Decimal( + f"1e{market.quote_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + ) + + message = basic_composer.msg_update_spot_market( + admin=sender, + market_id=market.id, + new_ticker=new_ticker, + new_min_price_tick_size=min_price_tick_size, + new_min_quantity_tick_size=min_quantity_tick_size, + new_min_notional=min_notional, + ) + + expected_message = { + "admin": sender, + "marketId": market.id, + "newTicker": new_ticker, + "newMinPriceTickSize": f"{expected_min_price_tick_size.normalize():f}", + "newMinQuantityTickSize": f"{expected_min_quantity_tick_size.normalize():f}", + "newMinNotional": f"{expected_min_notional.normalize():f}", + } + dict_message = json_format.MessageToDict( + message=message, + always_print_fields_with_no_presence=True, + ) + assert dict_message == expected_message + + def test_msg_update_derivative_market(self, basic_composer): + market = list(basic_composer.derivative_markets.values())[0] + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + new_ticker = "NEW/TICKER" + min_price_tick_size = Decimal("0.0009") + min_quantity_tick_size = Decimal("10") + min_notional = Decimal("5") + initial_margin_ratio = Decimal("0.05") + maintenance_margin_ratio = Decimal("0.009") + + expected_min_price_tick_size = min_price_tick_size * Decimal( + f"1e{market.quote_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + ) + expected_min_quantity_tick_size = min_quantity_tick_size * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + expected_min_notional = min_notional * Decimal( + f"1e{market.quote_token.decimals + ADDITIONAL_CHAIN_FORMAT_DECIMALS}" + ) + expected_initial_margin_ratio = initial_margin_ratio * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + expected_maintenance_margin_ratio = maintenance_margin_ratio * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") + + message = basic_composer.msg_update_derivative_market( + admin=sender, + market_id=market.id, + new_ticker=new_ticker, + new_min_price_tick_size=min_price_tick_size, + new_min_quantity_tick_size=min_quantity_tick_size, + new_min_notional=min_notional, + new_initial_margin_ratio=initial_margin_ratio, + new_maintenance_margin_ratio=maintenance_margin_ratio, + ) + + expected_message = { + "admin": sender, + "marketId": market.id, + "newTicker": new_ticker, + "newMinPriceTickSize": f"{expected_min_price_tick_size.normalize():f}", + "newMinQuantityTickSize": f"{expected_min_quantity_tick_size.normalize():f}", + "newMinNotional": f"{expected_min_notional.normalize():f}", + "newInitialMarginRatio": f"{expected_initial_margin_ratio.normalize():f}", + "newMaintenanceMarginRatio": f"{expected_maintenance_margin_ratio.normalize():f}", + } + dict_message = json_format.MessageToDict( + message=message, + always_print_fields_with_no_presence=True, + ) + assert dict_message == expected_message + + def test_msg_authorize_stake_grants(self, basic_composer): + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + amount = Decimal("100") + grant_authorization = basic_composer.create_grant_authorization( + grantee="inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", + amount=amount, + ) + + message = basic_composer.msg_authorize_stake_grants( + sender=sender, + grants=[grant_authorization], + ) + + expected_amount = amount * Decimal(f"1e{INJ_DECIMALS}") + expected_message = { + "sender": sender, + "grants": [ + { + "grantee": grant_authorization.grantee, + "amount": str(int(expected_amount)), + } + ], + } + dict_message = json_format.MessageToDict( + message=message, + always_print_fields_with_no_presence=True, + ) + assert dict_message == expected_message + + def test_msg_activate_stake_grant(self, basic_composer): + sender = "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r" + granter = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + + message = basic_composer.msg_activate_stake_grant( + sender=sender, + granter=granter, + ) + + expected_message = { + "sender": sender, + "granter": granter, + } + dict_message = json_format.MessageToDict( + message=message, + always_print_fields_with_no_presence=True, + ) + assert dict_message == expected_message + def test_msg_ibc_transfer(self, basic_composer): sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" source_port = "transfer"