2021-08-19 01:46:54 -07:00
|
|
|
# # ⚠ Warning
|
|
|
|
#
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
|
|
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
|
|
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
|
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
|
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
#
|
|
|
|
# [🥭 Mango Markets](https://mango.markets/) support is available at:
|
|
|
|
# [Docs](https://docs.mango.markets/)
|
|
|
|
# [Discord](https://discord.gg/67jySBhxrg)
|
|
|
|
# [Twitter](https://twitter.com/mangomarkets)
|
|
|
|
# [Github](https://github.com/blockworks-foundation)
|
|
|
|
# [Email](mailto:hello@blockworks.foundation)
|
|
|
|
|
|
|
|
|
2021-09-09 11:02:17 -07:00
|
|
|
import pyserum.enums
|
2021-08-19 01:46:54 -07:00
|
|
|
import typing
|
|
|
|
|
|
|
|
from solana.publickey import PublicKey
|
|
|
|
|
|
|
|
from .instructiontype import InstructionType
|
|
|
|
from .orders import OrderType, Side
|
|
|
|
|
|
|
|
|
|
|
|
# The index of the sender/signer depends on the instruction.
|
|
|
|
_instruction_signer_indices: typing.Dict[InstructionType, int] = {
|
|
|
|
InstructionType.InitMangoGroup: 1,
|
|
|
|
InstructionType.InitMarginAccount: 2,
|
|
|
|
InstructionType.Deposit: 2,
|
|
|
|
InstructionType.Withdraw: 2,
|
|
|
|
InstructionType.AddSpotMarket: 7,
|
|
|
|
InstructionType.AddToBasket: 2,
|
|
|
|
InstructionType.Borrow: 2,
|
|
|
|
InstructionType.CachePrices: -1, # No signer
|
|
|
|
InstructionType.CacheRootBanks: -1, # No signer
|
|
|
|
InstructionType.PlaceSpotOrder: 2,
|
|
|
|
InstructionType.AddOracle: 2,
|
|
|
|
InstructionType.AddPerpMarket: 6,
|
|
|
|
InstructionType.PlacePerpOrder: 2,
|
|
|
|
InstructionType.CancelPerpOrderByClientId: 2,
|
|
|
|
InstructionType.CancelPerpOrder: 2,
|
|
|
|
InstructionType.ConsumeEvents: -1, # No signer
|
|
|
|
InstructionType.CachePerpMarkets: -1, # No signer
|
|
|
|
InstructionType.UpdateFunding: -1, # No signer
|
|
|
|
InstructionType.SetOracle: -1, # No signer
|
|
|
|
InstructionType.SettleFunds: 2,
|
|
|
|
InstructionType.CancelSpotOrder: 1,
|
|
|
|
InstructionType.UpdateRootBank: -1, # No signer
|
|
|
|
InstructionType.SettlePnl: -1, # No signer
|
|
|
|
InstructionType.SettleBorrow: -1, # No signer
|
|
|
|
InstructionType.ForceCancelSpotOrders: -1,
|
|
|
|
InstructionType.ForceCancelPerpOrders: -1,
|
|
|
|
InstructionType.LiquidateTokenAndToken: -1,
|
|
|
|
InstructionType.LiquidateTokenAndPerp: -1,
|
|
|
|
InstructionType.LiquidatePerpMarket: -1,
|
|
|
|
InstructionType.SettleFees: -1,
|
|
|
|
InstructionType.ResolvePerpBankruptcy: -1,
|
|
|
|
InstructionType.ResolveTokenBankruptcy: -1,
|
|
|
|
InstructionType.InitSpotOpenOrders: -1,
|
|
|
|
InstructionType.RedeemMngo: -1,
|
|
|
|
InstructionType.AddMangoAccountInfo: -1,
|
|
|
|
InstructionType.DepositMsrm: -1,
|
|
|
|
InstructionType.WithdrawMsrm: -1,
|
|
|
|
}
|
|
|
|
|
|
|
|
# The index of the token IN account depends on the instruction, and for some instructions
|
|
|
|
# doesn't exist.
|
|
|
|
_token_in_indices: typing.Dict[InstructionType, int] = {
|
|
|
|
InstructionType.InitMangoGroup: -1,
|
|
|
|
InstructionType.InitMarginAccount: -1,
|
|
|
|
InstructionType.Deposit: 8,
|
|
|
|
InstructionType.Withdraw: 7,
|
|
|
|
InstructionType.AddSpotMarket: -1,
|
|
|
|
InstructionType.AddToBasket: -1,
|
|
|
|
InstructionType.Borrow: -1,
|
|
|
|
InstructionType.CachePrices: -1,
|
|
|
|
InstructionType.CacheRootBanks: -1,
|
|
|
|
InstructionType.PlaceSpotOrder: -1,
|
|
|
|
InstructionType.AddOracle: -1,
|
|
|
|
InstructionType.AddPerpMarket: -1,
|
|
|
|
InstructionType.PlacePerpOrder: -1,
|
|
|
|
InstructionType.CancelPerpOrderByClientId: -1,
|
|
|
|
InstructionType.CancelPerpOrder: -1,
|
|
|
|
InstructionType.ConsumeEvents: -1,
|
|
|
|
InstructionType.CachePerpMarkets: -1,
|
|
|
|
InstructionType.UpdateFunding: -1,
|
|
|
|
InstructionType.SetOracle: -1,
|
|
|
|
InstructionType.SettleFunds: -1,
|
|
|
|
InstructionType.CancelSpotOrder: -1,
|
|
|
|
InstructionType.UpdateRootBank: -1,
|
|
|
|
InstructionType.SettlePnl: -1,
|
|
|
|
InstructionType.SettleBorrow: -1,
|
|
|
|
InstructionType.ForceCancelSpotOrders: -1,
|
|
|
|
InstructionType.ForceCancelPerpOrders: -1,
|
|
|
|
InstructionType.LiquidateTokenAndToken: -1,
|
|
|
|
InstructionType.LiquidateTokenAndPerp: -1,
|
|
|
|
InstructionType.LiquidatePerpMarket: -1,
|
|
|
|
InstructionType.SettleFees: -1,
|
|
|
|
InstructionType.ResolvePerpBankruptcy: -1,
|
|
|
|
InstructionType.ResolveTokenBankruptcy: -1,
|
|
|
|
InstructionType.InitSpotOpenOrders: -1,
|
|
|
|
InstructionType.RedeemMngo: -1,
|
|
|
|
InstructionType.AddMangoAccountInfo: -1,
|
|
|
|
InstructionType.DepositMsrm: -1,
|
|
|
|
InstructionType.WithdrawMsrm: -1,
|
|
|
|
}
|
|
|
|
|
|
|
|
# The index of the token OUT account depends on the instruction, and for some instructions
|
|
|
|
# doesn't exist.
|
|
|
|
_token_out_indices: typing.Dict[InstructionType, int] = {
|
|
|
|
InstructionType.InitMangoGroup: -1,
|
|
|
|
InstructionType.InitMarginAccount: -1,
|
|
|
|
InstructionType.Deposit: 6,
|
|
|
|
InstructionType.Withdraw: 6,
|
|
|
|
InstructionType.AddSpotMarket: -1,
|
|
|
|
InstructionType.AddToBasket: -1,
|
|
|
|
InstructionType.Borrow: -1,
|
|
|
|
InstructionType.CachePrices: -1,
|
|
|
|
InstructionType.CacheRootBanks: -1,
|
|
|
|
InstructionType.PlaceSpotOrder: -1,
|
|
|
|
InstructionType.AddOracle: -1,
|
|
|
|
InstructionType.AddPerpMarket: -1,
|
|
|
|
InstructionType.PlacePerpOrder: -1,
|
|
|
|
InstructionType.CancelPerpOrderByClientId: -1,
|
|
|
|
InstructionType.CancelPerpOrder: -1,
|
|
|
|
InstructionType.ConsumeEvents: -1,
|
|
|
|
InstructionType.CachePerpMarkets: -1,
|
|
|
|
InstructionType.UpdateFunding: -1,
|
|
|
|
InstructionType.SetOracle: -1,
|
|
|
|
InstructionType.SettleFunds: -1,
|
|
|
|
InstructionType.CancelSpotOrder: -1,
|
|
|
|
InstructionType.UpdateRootBank: -1,
|
|
|
|
InstructionType.SettlePnl: -1,
|
|
|
|
InstructionType.SettleBorrow: -1,
|
|
|
|
InstructionType.ForceCancelSpotOrders: -1,
|
|
|
|
InstructionType.ForceCancelPerpOrders: -1,
|
|
|
|
InstructionType.LiquidateTokenAndToken: -1,
|
|
|
|
InstructionType.LiquidateTokenAndPerp: -1,
|
|
|
|
InstructionType.LiquidatePerpMarket: -1,
|
|
|
|
InstructionType.SettleFees: -1,
|
|
|
|
InstructionType.ResolvePerpBankruptcy: -1,
|
|
|
|
InstructionType.ResolveTokenBankruptcy: -1,
|
|
|
|
InstructionType.InitSpotOpenOrders: -1,
|
|
|
|
InstructionType.RedeemMngo: -1,
|
|
|
|
InstructionType.AddMangoAccountInfo: -1,
|
|
|
|
InstructionType.DepositMsrm: -1,
|
|
|
|
InstructionType.WithdrawMsrm: -1,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Some instructions (like liqudate) have a 'target' account. Most don't.
|
|
|
|
_target_indices: typing.Dict[InstructionType, int] = {
|
|
|
|
InstructionType.InitMangoGroup: -1,
|
|
|
|
InstructionType.InitMarginAccount: -1,
|
|
|
|
InstructionType.Deposit: -1,
|
|
|
|
InstructionType.Withdraw: -1,
|
|
|
|
InstructionType.AddSpotMarket: -1,
|
|
|
|
InstructionType.AddToBasket: -1,
|
|
|
|
InstructionType.Borrow: -1,
|
|
|
|
InstructionType.CachePrices: -1,
|
|
|
|
InstructionType.CacheRootBanks: -1,
|
|
|
|
InstructionType.PlaceSpotOrder: -1,
|
|
|
|
InstructionType.AddOracle: -1,
|
|
|
|
InstructionType.AddPerpMarket: -1,
|
|
|
|
InstructionType.PlacePerpOrder: -1,
|
|
|
|
InstructionType.CancelPerpOrderByClientId: -1,
|
|
|
|
InstructionType.CancelPerpOrder: -1,
|
|
|
|
InstructionType.ConsumeEvents: -1,
|
|
|
|
InstructionType.CachePerpMarkets: -1,
|
|
|
|
InstructionType.UpdateFunding: -1,
|
|
|
|
InstructionType.SetOracle: -1,
|
|
|
|
InstructionType.SettleFunds: -1,
|
|
|
|
InstructionType.CancelSpotOrder: -1,
|
|
|
|
InstructionType.UpdateRootBank: -1,
|
|
|
|
InstructionType.SettlePnl: -1,
|
|
|
|
InstructionType.SettleBorrow: -1,
|
|
|
|
InstructionType.ForceCancelSpotOrders: -1,
|
|
|
|
InstructionType.ForceCancelPerpOrders: -1,
|
|
|
|
InstructionType.LiquidateTokenAndToken: -1,
|
|
|
|
InstructionType.LiquidateTokenAndPerp: -1,
|
|
|
|
InstructionType.LiquidatePerpMarket: -1,
|
|
|
|
InstructionType.SettleFees: -1,
|
|
|
|
InstructionType.ResolvePerpBankruptcy: -1,
|
|
|
|
InstructionType.ResolveTokenBankruptcy: -1,
|
|
|
|
InstructionType.InitSpotOpenOrders: -1,
|
|
|
|
InstructionType.RedeemMngo: -1,
|
|
|
|
InstructionType.AddMangoAccountInfo: -1,
|
|
|
|
InstructionType.DepositMsrm: -1,
|
|
|
|
InstructionType.WithdrawMsrm: -1,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# # 🥭 MangoInstruction class
|
|
|
|
#
|
|
|
|
# This class packages up Mango instruction data, which can come from disparate parts of the
|
|
|
|
# transaction. Keeping it all together here makes many things simpler.
|
|
|
|
#
|
|
|
|
class MangoInstruction:
|
|
|
|
def __init__(self, instruction_type: InstructionType, instruction_data: typing.Any, accounts: typing.Sequence[PublicKey]):
|
|
|
|
self.instruction_type = instruction_type
|
|
|
|
self.instruction_data = instruction_data
|
|
|
|
self.accounts = accounts
|
|
|
|
|
|
|
|
@property
|
|
|
|
def group(self) -> PublicKey:
|
|
|
|
# Group PublicKey is always the zero index.
|
|
|
|
return self.accounts[0]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def sender(self) -> typing.Optional[PublicKey]:
|
|
|
|
account_index = _instruction_signer_indices[self.instruction_type]
|
|
|
|
if account_index < 0:
|
|
|
|
return None
|
|
|
|
return self.accounts[account_index]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def token_in_account(self) -> typing.Optional[PublicKey]:
|
|
|
|
account_index = _token_in_indices[self.instruction_type]
|
|
|
|
if account_index < 0:
|
|
|
|
return None
|
|
|
|
return self.accounts[account_index]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def token_out_account(self) -> typing.Optional[PublicKey]:
|
|
|
|
account_index = _token_out_indices[self.instruction_type]
|
|
|
|
if account_index < 0:
|
|
|
|
return None
|
|
|
|
return self.accounts[account_index]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def target_account(self) -> typing.Optional[PublicKey]:
|
|
|
|
account_index = _target_indices[self.instruction_type]
|
|
|
|
if account_index < 0:
|
|
|
|
return None
|
|
|
|
return self.accounts[account_index]
|
|
|
|
|
|
|
|
def describe_parameters(self) -> str:
|
|
|
|
instruction_type = self.instruction_type
|
|
|
|
additional_data = ""
|
|
|
|
if instruction_type == InstructionType.InitMangoGroup:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.InitMarginAccount:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.Deposit:
|
|
|
|
additional_data = f"quantity: {self.instruction_data.quantity}"
|
|
|
|
elif instruction_type == InstructionType.Withdraw:
|
|
|
|
additional_data = f"quantity: {self.instruction_data.quantity}, allow_borrow: {self.instruction_data.allow_borrow}"
|
|
|
|
elif instruction_type == InstructionType.AddSpotMarket:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.AddToBasket:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.Borrow:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.CachePrices:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.CacheRootBanks:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.PlaceSpotOrder:
|
2021-09-09 11:02:17 -07:00
|
|
|
additional_data = f"side: {Side.from_value(self.instruction_data.side)}, order_type: {OrderType.from_value(self.instruction_data.order_type)}, limit_price: {self.instruction_data.limit_price}, max_base_quantity: {self.instruction_data.max_base_quantity}, max_quote_quantity: {self.instruction_data.max_quote_quantity}, self_trade_behavior: {pyserum.enums.SelfTradeBehavior(self.instruction_data.self_trade_behavior).name}, client_id: {self.instruction_data.client_id}, limit: {self.instruction_data.limit}"
|
2021-08-19 01:46:54 -07:00
|
|
|
elif instruction_type == InstructionType.AddOracle:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.AddPerpMarket:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.PlacePerpOrder:
|
|
|
|
additional_data = f"side: {Side.from_value(self.instruction_data.side)}, order_type: {OrderType.from_value(self.instruction_data.order_type)}, price: {self.instruction_data.price}, quantity: {self.instruction_data.quantity}, client_order_id: {self.instruction_data.client_order_id}"
|
|
|
|
elif instruction_type == InstructionType.CancelPerpOrderByClientId:
|
|
|
|
additional_data = f"client ID: {self.instruction_data.client_order_id}, missing OK: {self.instruction_data.invalid_id_ok}"
|
|
|
|
elif instruction_type == InstructionType.CancelPerpOrder:
|
|
|
|
additional_data = f"order ID: {self.instruction_data.order_id}, missing OK: {self.instruction_data.invalid_id_ok}"
|
|
|
|
elif instruction_type == InstructionType.ConsumeEvents:
|
|
|
|
additional_data = f"limit: {self.instruction_data.limit}"
|
|
|
|
elif instruction_type == InstructionType.CachePerpMarkets:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.UpdateFunding:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.SetOracle:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.SettleFunds:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.CancelSpotOrder:
|
|
|
|
additional_data = f"order ID: {self.instruction_data.order_id}, side: {Side.from_value(self.instruction_data.side)}"
|
|
|
|
elif instruction_type == InstructionType.UpdateRootBank:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.SettlePnl:
|
|
|
|
pass
|
|
|
|
elif instruction_type == InstructionType.SettleBorrow:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return additional_data
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
|
|
|
parameters = self.describe_parameters() or "None"
|
|
|
|
return f"« {self.instruction_type.name}: {parameters} »"
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"{self}"
|