From 33839fb6b0ef7be5240cc203f330ef3192259920 Mon Sep 17 00:00:00 2001 From: atvanguard <3612498+atvanguard@users.noreply.github.com> Date: Thu, 29 Feb 2024 00:04:28 +0530 Subject: [PATCH 01/14] generate trader view precompile --- .../hubble-v2/interfaces/ITraderViewer.sol | 21 ++ .../contracts/juror/matching_validation.go | 12 + precompile/contracts/traderviewer/README.md | 23 ++ precompile/contracts/traderviewer/config.go | 64 ++++ .../contracts/traderviewer/config_test.go | 67 ++++ .../contracts/traderviewer/contract.abi | 1 + precompile/contracts/traderviewer/contract.go | 355 ++++++++++++++++++ .../contracts/traderviewer/contract_test.go | 109 ++++++ precompile/contracts/traderviewer/event.go | 50 +++ precompile/contracts/traderviewer/module.go | 63 ++++ 10 files changed, 765 insertions(+) create mode 100644 contracts/contracts/hubble-v2/interfaces/ITraderViewer.sol create mode 100644 precompile/contracts/traderviewer/README.md create mode 100644 precompile/contracts/traderviewer/config.go create mode 100644 precompile/contracts/traderviewer/config_test.go create mode 100644 precompile/contracts/traderviewer/contract.abi create mode 100644 precompile/contracts/traderviewer/contract.go create mode 100644 precompile/contracts/traderviewer/contract_test.go create mode 100644 precompile/contracts/traderviewer/event.go create mode 100644 precompile/contracts/traderviewer/module.go diff --git a/contracts/contracts/hubble-v2/interfaces/ITraderViewer.sol b/contracts/contracts/hubble-v2/interfaces/ITraderViewer.sol new file mode 100644 index 0000000000..a78c103be2 --- /dev/null +++ b/contracts/contracts/hubble-v2/interfaces/ITraderViewer.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { IClearingHouse } from "./IClearingHouse.sol"; + +interface ITraderViewer { + function getNotionalPositionAndMargin(address trader, bool includeFundingPayments, IClearingHouse.Mode mode) external view returns(uint256 notionalPosition, int256 margin, uint256 requiredMargin); + + function getTraderDataForMarket(address trader, uint256 ammIndex, IClearingHouse.Mode mode) external view returns( + bool isIsolated, uint256 notionalPosition, int256 unrealizedPnl, uint256 requiredMargin, int256 pendingFunding + ); + + function getCrossMarginAccountData(address trader, IClearingHouse.Mode mode) + external + view + returns(uint256 notionalPosition, uint256 requiredMargin, int256 unrealizedPnl, int256 pendingFunding); + + function getTotalFundingForCrossMarginPositions(address trader) external view returns(int256 totalFunding); + +} diff --git a/precompile/contracts/juror/matching_validation.go b/precompile/contracts/juror/matching_validation.go index 3a26152033..6635f4c96d 100644 --- a/precompile/contracts/juror/matching_validation.go +++ b/precompile/contracts/juror/matching_validation.go @@ -434,6 +434,18 @@ func reducesPosition(positionSize *big.Int, baseAssetQuantity *big.Int) bool { } func getRequiredMargin(bibliophile b.BibliophileClient, order ILimitOrderBookOrder) *big.Int { + if false { // @todo find apt condition + return getRequiredMarginNew(bibliophile, order) + } + return getRequiredMarginLegacy(bibliophile, order) +} + +func getRequiredMarginNew(bibliophile b.BibliophileClient, order ILimitOrderBookOrder) *big.Int { + // @todo implement + return big.NewInt(0) +} + +func getRequiredMarginLegacy(bibliophile b.BibliophileClient, order ILimitOrderBookOrder) *big.Int { price := order.Price upperBound, _ := bibliophile.GetUpperAndLowerBoundForMarket(order.AmmIndex.Int64()) if order.BaseAssetQuantity.Sign() == -1 && order.Price.Cmp(upperBound) == -1 { diff --git a/precompile/contracts/traderviewer/README.md b/precompile/contracts/traderviewer/README.md new file mode 100644 index 0000000000..d81e622b2b --- /dev/null +++ b/precompile/contracts/traderviewer/README.md @@ -0,0 +1,23 @@ +There are some must-be-done changes waiting in the generated file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. +Additionally there are other files you need to edit to activate your precompile. +These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". +For testing take a look at other precompile tests in contract_test.go and config_test.go in other precompile folders. +See the tutorial in for more information about precompile development. + +General guidelines for precompile development: +1- Set a suitable config key in generated module.go. E.g: "yourPrecompileConfig" +2- Read the comment and set a suitable contract address in generated module.go. E.g: +ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") +3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Typically, custom codes are required in only those areas. +Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. +4- Set gas costs in generated contract.go +5- Force import your precompile package in precompile/registry/registry.go +6- Add your config unit tests under generated package config_test.go +7- Add your contract unit tests under generated package contract_test.go +8- Additionally you can add a full-fledged VM test for your precompile under plugin/vm/vm_test.go. See existing precompile tests for examples. +9- Add your solidity interface and test contract to contracts/contracts +10- Write solidity contract tests for your precompile in contracts/contracts/test +11- Write TypeScript DS-Test counterparts for your solidity tests in contracts/test +12- Create your genesis with your precompile enabled in tests/precompile/genesis/ +13- Create e2e test for your solidity test in tests/precompile/solidity/suites.go +14- Run your e2e precompile Solidity tests with './scripts/run_ginkgo.sh` diff --git a/precompile/contracts/traderviewer/config.go b/precompile/contracts/traderviewer/config.go new file mode 100644 index 0000000000..8ab51eab34 --- /dev/null +++ b/precompile/contracts/traderviewer/config.go @@ -0,0 +1,64 @@ +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package traderviewer + +import ( + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" +) + +var _ precompileconfig.Config = &Config{} + +// Config implements the precompileconfig.Config interface and +// adds specific configuration for TraderViewer. +type Config struct { + precompileconfig.Upgrade + // CUSTOM CODE STARTS HERE + // Add your own custom fields for Config here +} + +// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables +// TraderViewer. +func NewConfig(blockTimestamp *uint64) *Config { + return &Config{ + Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, + } +} + +// NewDisableConfig returns config for a network upgrade at [blockTimestamp] +// that disables TraderViewer. +func NewDisableConfig(blockTimestamp *uint64) *Config { + return &Config{ + Upgrade: precompileconfig.Upgrade{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} + +// Key returns the key for the TraderViewer precompileconfig. +// This should be the same key as used in the precompile module. +func (*Config) Key() string { return ConfigKey } + +// Verify tries to verify Config and returns an error accordingly. +func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error { + // CUSTOM CODE STARTS HERE + // Add your own custom verify code for Config here + // and return an error accordingly + return nil +} + +// Equal returns true if [s] is a [*Config] and it has been configured identical to [c]. +func (c *Config) Equal(s precompileconfig.Config) bool { + // typecast before comparison + other, ok := (s).(*Config) + if !ok { + return false + } + // CUSTOM CODE STARTS HERE + // modify this boolean accordingly with your custom Config, to check if [other] and the current [c] are equal + // if Config contains only Upgrade you can skip modifying it. + equals := c.Upgrade.Equal(&other.Upgrade) + return equals +} diff --git a/precompile/contracts/traderviewer/config_test.go b/precompile/contracts/traderviewer/config_test.go new file mode 100644 index 0000000000..c2f058cdc4 --- /dev/null +++ b/precompile/contracts/traderviewer/config_test.go @@ -0,0 +1,67 @@ +// Code generated +// This file is a generated precompile config test with the skeleton of test functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package traderviewer + +import ( + "testing" + + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" + "github.com/ava-labs/subnet-evm/precompile/testutils" + "github.com/ava-labs/subnet-evm/utils" + "go.uber.org/mock/gomock" +) + +// TestVerify tests the verification of Config. +func TestVerify(t *testing.T) { + tests := map[string]testutils.ConfigVerifyTest{ + "valid config": { + Config: NewConfig(utils.NewUint64(3)), + ChainConfig: func() precompileconfig.ChainConfig { + config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) + config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes() + return config + }(), + ExpectedError: "", + }, + // CUSTOM CODE STARTS HERE + // Add your own Verify tests here, e.g.: + // "your custom test name": { + // Config: NewConfig(utils.NewUint64(3),), + // ExpectedError: ErrYourCustomError.Error(), + // }, + } + // Run verify tests. + testutils.RunVerifyTests(t, tests) +} + +// TestEqual tests the equality of Config with other precompile configs. +func TestEqual(t *testing.T) { + tests := map[string]testutils.ConfigEqualTest{ + "non-nil config and nil other": { + Config: NewConfig(utils.NewUint64(3)), + Other: nil, + Expected: false, + }, + "different type": { + Config: NewConfig(utils.NewUint64(3)), + Other: precompileconfig.NewMockConfig(gomock.NewController(t)), + Expected: false, + }, + "different timestamp": { + Config: NewConfig(utils.NewUint64(3)), + Other: NewConfig(utils.NewUint64(4)), + Expected: false, + }, + "same config": { + Config: NewConfig(utils.NewUint64(3)), + Other: NewConfig(utils.NewUint64(3)), + Expected: true, + }, + // CUSTOM CODE STARTS HERE + // Add your own Equal tests here + } + // Run equal tests. + testutils.RunEqualTests(t, tests) +} diff --git a/precompile/contracts/traderviewer/contract.abi b/precompile/contracts/traderviewer/contract.abi new file mode 100644 index 0000000000..1df78f423e --- /dev/null +++ b/precompile/contracts/traderviewer/contract.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"enum IClearingHouse.Mode","name":"mode","type":"uint8"}],"name":"getCrossMarginAccountData","outputs":[{"internalType":"uint256","name":"notionalPosition","type":"uint256"},{"internalType":"uint256","name":"requiredMargin","type":"uint256"},{"internalType":"int256","name":"unrealizedPnl","type":"int256"},{"internalType":"int256","name":"pendingFunding","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"bool","name":"includeFundingPayments","type":"bool"},{"internalType":"enum IClearingHouse.Mode","name":"mode","type":"uint8"}],"name":"getNotionalPositionAndMargin","outputs":[{"internalType":"uint256","name":"notionalPosition","type":"uint256"},{"internalType":"int256","name":"margin","type":"int256"},{"internalType":"uint256","name":"requiredMargin","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"}],"name":"getTotalFundingForCrossMarginPositions","outputs":[{"internalType":"int256","name":"totalFunding","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"uint256","name":"ammIndex","type":"uint256"},{"internalType":"enum IClearingHouse.Mode","name":"mode","type":"uint8"}],"name":"getTraderDataForMarket","outputs":[{"internalType":"bool","name":"isIsolated","type":"bool"},{"internalType":"uint256","name":"notionalPosition","type":"uint256"},{"internalType":"int256","name":"unrealizedPnl","type":"int256"},{"internalType":"uint256","name":"requiredMargin","type":"uint256"},{"internalType":"int256","name":"pendingFunding","type":"int256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompile/contracts/traderviewer/contract.go b/precompile/contracts/traderviewer/contract.go new file mode 100644 index 0000000000..9a297445fc --- /dev/null +++ b/precompile/contracts/traderviewer/contract.go @@ -0,0 +1,355 @@ +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package traderviewer + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/vmerrs" + + _ "embed" + + "github.com/ethereum/go-ethereum/common" +) + +const ( + // Gas costs for each function. These are set to 1 by default. + // You should set a gas cost for each function in your contract. + // Generally, you should not set gas costs very low as this may cause your network to be vulnerable to DoS attacks. + // There are some predefined gas costs in contract/utils.go that you can use. + GetCrossMarginAccountDataGasCost uint64 = 1 /* SET A GAS COST HERE */ + GetNotionalPositionAndMarginGasCost uint64 = 1 /* SET A GAS COST HERE */ + GetTotalFundingForCrossMarginPositionsGasCost uint64 = 1 /* SET A GAS COST HERE */ + GetTraderDataForMarketGasCost uint64 = 1 /* SET A GAS COST HERE */ +) + +// CUSTOM CODE STARTS HERE +// Reference imports to suppress errors from unused imports. This code and any unnecessary imports can be removed. +var ( + _ = abi.JSON + _ = errors.New + _ = big.NewInt + _ = vmerrs.ErrOutOfGas + _ = common.Big0 +) + +// Singleton StatefulPrecompiledContract and signatures. +var ( + + // TraderViewerRawABI contains the raw ABI of TraderViewer contract. + //go:embed contract.abi + TraderViewerRawABI string + + TraderViewerABI = contract.ParseABI(TraderViewerRawABI) + + TraderViewerPrecompile = createTraderViewerPrecompile() +) + +type GetCrossMarginAccountDataInput struct { + Trader common.Address + Mode uint8 +} + +type GetCrossMarginAccountDataOutput struct { + NotionalPosition *big.Int + RequiredMargin *big.Int + UnrealizedPnl *big.Int + PendingFunding *big.Int +} + +type GetNotionalPositionAndMarginInput struct { + Trader common.Address + IncludeFundingPayments bool + Mode uint8 +} + +type GetNotionalPositionAndMarginOutput struct { + NotionalPosition *big.Int + Margin *big.Int + RequiredMargin *big.Int +} + +type GetTraderDataForMarketInput struct { + Trader common.Address + AmmIndex *big.Int + Mode uint8 +} + +type GetTraderDataForMarketOutput struct { + IsIsolated bool + NotionalPosition *big.Int + UnrealizedPnl *big.Int + RequiredMargin *big.Int + PendingFunding *big.Int +} + +// UnpackGetCrossMarginAccountDataInput attempts to unpack [input] as GetCrossMarginAccountDataInput +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackGetCrossMarginAccountDataInput(input []byte) (GetCrossMarginAccountDataInput, error) { + inputStruct := GetCrossMarginAccountDataInput{} + // The strict mode in decoding is disabled after Durango. You can re-enable by changing the last argument to true. + err := TraderViewerABI.UnpackInputIntoInterface(&inputStruct, "getCrossMarginAccountData", input, false) + + return inputStruct, err +} + +// PackGetCrossMarginAccountData packs [inputStruct] of type GetCrossMarginAccountDataInput into the appropriate arguments for getCrossMarginAccountData. +func PackGetCrossMarginAccountData(inputStruct GetCrossMarginAccountDataInput) ([]byte, error) { + return TraderViewerABI.Pack("getCrossMarginAccountData", inputStruct.Trader, inputStruct.Mode) +} + +// PackGetCrossMarginAccountDataOutput attempts to pack given [outputStruct] of type GetCrossMarginAccountDataOutput +// to conform the ABI outputs. +func PackGetCrossMarginAccountDataOutput(outputStruct GetCrossMarginAccountDataOutput) ([]byte, error) { + return TraderViewerABI.PackOutput("getCrossMarginAccountData", + outputStruct.NotionalPosition, + outputStruct.RequiredMargin, + outputStruct.UnrealizedPnl, + outputStruct.PendingFunding, + ) +} + +// UnpackGetCrossMarginAccountDataOutput attempts to unpack [output] as GetCrossMarginAccountDataOutput +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackGetCrossMarginAccountDataOutput(output []byte) (GetCrossMarginAccountDataOutput, error) { + outputStruct := GetCrossMarginAccountDataOutput{} + err := TraderViewerABI.UnpackIntoInterface(&outputStruct, "getCrossMarginAccountData", output) + + return outputStruct, err +} + +func getCrossMarginAccountData(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, GetCrossMarginAccountDataGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the GetCrossMarginAccountDataInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackGetCrossMarginAccountDataInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output GetCrossMarginAccountDataOutput // CUSTOM CODE FOR AN OUTPUT + packedOutput, err := PackGetCrossMarginAccountDataOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// UnpackGetNotionalPositionAndMarginInput attempts to unpack [input] as GetNotionalPositionAndMarginInput +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackGetNotionalPositionAndMarginInput(input []byte) (GetNotionalPositionAndMarginInput, error) { + inputStruct := GetNotionalPositionAndMarginInput{} + // The strict mode in decoding is disabled after Durango. You can re-enable by changing the last argument to true. + err := TraderViewerABI.UnpackInputIntoInterface(&inputStruct, "getNotionalPositionAndMargin", input, false) + + return inputStruct, err +} + +// PackGetNotionalPositionAndMargin packs [inputStruct] of type GetNotionalPositionAndMarginInput into the appropriate arguments for getNotionalPositionAndMargin. +func PackGetNotionalPositionAndMargin(inputStruct GetNotionalPositionAndMarginInput) ([]byte, error) { + return TraderViewerABI.Pack("getNotionalPositionAndMargin", inputStruct.Trader, inputStruct.IncludeFundingPayments, inputStruct.Mode) +} + +// PackGetNotionalPositionAndMarginOutput attempts to pack given [outputStruct] of type GetNotionalPositionAndMarginOutput +// to conform the ABI outputs. +func PackGetNotionalPositionAndMarginOutput(outputStruct GetNotionalPositionAndMarginOutput) ([]byte, error) { + return TraderViewerABI.PackOutput("getNotionalPositionAndMargin", + outputStruct.NotionalPosition, + outputStruct.Margin, + outputStruct.RequiredMargin, + ) +} + +// UnpackGetNotionalPositionAndMarginOutput attempts to unpack [output] as GetNotionalPositionAndMarginOutput +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackGetNotionalPositionAndMarginOutput(output []byte) (GetNotionalPositionAndMarginOutput, error) { + outputStruct := GetNotionalPositionAndMarginOutput{} + err := TraderViewerABI.UnpackIntoInterface(&outputStruct, "getNotionalPositionAndMargin", output) + + return outputStruct, err +} + +func getNotionalPositionAndMargin(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, GetNotionalPositionAndMarginGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the GetNotionalPositionAndMarginInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackGetNotionalPositionAndMarginInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output GetNotionalPositionAndMarginOutput // CUSTOM CODE FOR AN OUTPUT + packedOutput, err := PackGetNotionalPositionAndMarginOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// UnpackGetTotalFundingForCrossMarginPositionsInput attempts to unpack [input] into the common.Address type argument +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackGetTotalFundingForCrossMarginPositionsInput(input []byte) (common.Address, error) { + // The strict mode in decoding is disabled after Durango. You can re-enable by changing the last argument to true. + res, err := TraderViewerABI.UnpackInput("getTotalFundingForCrossMarginPositions", input, false) + if err != nil { + return common.Address{}, err + } + unpacked := *abi.ConvertType(res[0], new(common.Address)).(*common.Address) + return unpacked, nil +} + +// PackGetTotalFundingForCrossMarginPositions packs [trader] of type common.Address into the appropriate arguments for getTotalFundingForCrossMarginPositions. +// the packed bytes include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackGetTotalFundingForCrossMarginPositions(trader common.Address) ([]byte, error) { + return TraderViewerABI.Pack("getTotalFundingForCrossMarginPositions", trader) +} + +// PackGetTotalFundingForCrossMarginPositionsOutput attempts to pack given totalFunding of type *big.Int +// to conform the ABI outputs. +func PackGetTotalFundingForCrossMarginPositionsOutput(totalFunding *big.Int) ([]byte, error) { + return TraderViewerABI.PackOutput("getTotalFundingForCrossMarginPositions", totalFunding) +} + +// UnpackGetTotalFundingForCrossMarginPositionsOutput attempts to unpack given [output] into the *big.Int type output +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackGetTotalFundingForCrossMarginPositionsOutput(output []byte) (*big.Int, error) { + res, err := TraderViewerABI.Unpack("getTotalFundingForCrossMarginPositions", output) + if err != nil { + return new(big.Int), err + } + unpacked := *abi.ConvertType(res[0], new(*big.Int)).(**big.Int) + return unpacked, nil +} + +func getTotalFundingForCrossMarginPositions(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, GetTotalFundingForCrossMarginPositionsGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the GetTotalFundingForCrossMarginPositionsInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackGetTotalFundingForCrossMarginPositionsInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + + var output *big.Int // CUSTOM CODE FOR AN OUTPUT + packedOutput, err := PackGetTotalFundingForCrossMarginPositionsOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// UnpackGetTraderDataForMarketInput attempts to unpack [input] as GetTraderDataForMarketInput +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackGetTraderDataForMarketInput(input []byte) (GetTraderDataForMarketInput, error) { + inputStruct := GetTraderDataForMarketInput{} + // The strict mode in decoding is disabled after Durango. You can re-enable by changing the last argument to true. + err := TraderViewerABI.UnpackInputIntoInterface(&inputStruct, "getTraderDataForMarket", input, false) + + return inputStruct, err +} + +// PackGetTraderDataForMarket packs [inputStruct] of type GetTraderDataForMarketInput into the appropriate arguments for getTraderDataForMarket. +func PackGetTraderDataForMarket(inputStruct GetTraderDataForMarketInput) ([]byte, error) { + return TraderViewerABI.Pack("getTraderDataForMarket", inputStruct.Trader, inputStruct.AmmIndex, inputStruct.Mode) +} + +// PackGetTraderDataForMarketOutput attempts to pack given [outputStruct] of type GetTraderDataForMarketOutput +// to conform the ABI outputs. +func PackGetTraderDataForMarketOutput(outputStruct GetTraderDataForMarketOutput) ([]byte, error) { + return TraderViewerABI.PackOutput("getTraderDataForMarket", + outputStruct.IsIsolated, + outputStruct.NotionalPosition, + outputStruct.UnrealizedPnl, + outputStruct.RequiredMargin, + outputStruct.PendingFunding, + ) +} + +// UnpackGetTraderDataForMarketOutput attempts to unpack [output] as GetTraderDataForMarketOutput +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackGetTraderDataForMarketOutput(output []byte) (GetTraderDataForMarketOutput, error) { + outputStruct := GetTraderDataForMarketOutput{} + err := TraderViewerABI.UnpackIntoInterface(&outputStruct, "getTraderDataForMarket", output) + + return outputStruct, err +} + +func getTraderDataForMarket(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, GetTraderDataForMarketGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the GetTraderDataForMarketInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackGetTraderDataForMarketInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output GetTraderDataForMarketOutput // CUSTOM CODE FOR AN OUTPUT + packedOutput, err := PackGetTraderDataForMarketOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// createTraderViewerPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. + +func createTraderViewerPrecompile() contract.StatefulPrecompiledContract { + var functions []*contract.StatefulPrecompileFunction + + abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ + "getCrossMarginAccountData": getCrossMarginAccountData, + "getNotionalPositionAndMargin": getNotionalPositionAndMargin, + "getTotalFundingForCrossMarginPositions": getTotalFundingForCrossMarginPositions, + "getTraderDataForMarket": getTraderDataForMarket, + } + + for name, function := range abiFunctionMap { + method, ok := TraderViewerABI.Methods[name] + if !ok { + panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) + } + functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) + } + // Construct the contract with no fallback function. + statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) + if err != nil { + panic(err) + } + return statefulContract +} diff --git a/precompile/contracts/traderviewer/contract_test.go b/precompile/contracts/traderviewer/contract_test.go new file mode 100644 index 0000000000..c8693e2514 --- /dev/null +++ b/precompile/contracts/traderviewer/contract_test.go @@ -0,0 +1,109 @@ +// Code generated +// This file is a generated precompile contract test with the skeleton of test functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package traderviewer + +import ( + "math/big" + "testing" + + "github.com/ava-labs/subnet-evm/core/state" + "github.com/ava-labs/subnet-evm/precompile/testutils" + "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +var ( + _ = vmerrs.ErrOutOfGas + _ = big.NewInt + _ = common.Big0 + _ = require.New +) + +// These tests are run against the precompile contract directly with +// the given input and expected output. They're just a guide to +// help you write your own tests. These tests are for general cases like +// allowlist, readOnly behaviour, and gas cost. You should write your own +// tests for specific cases. +var ( + tests = map[string]testutils.PrecompileTest{ + "insufficient gas for getCrossMarginAccountData should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // populate test input here + testInput := GetCrossMarginAccountDataInput{} + input, err := PackGetCrossMarginAccountData(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: GetCrossMarginAccountDataGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient gas for getNotionalPositionAndMargin should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // populate test input here + testInput := GetNotionalPositionAndMarginInput{} + input, err := PackGetNotionalPositionAndMargin(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: GetNotionalPositionAndMarginGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient gas for getTotalFundingForCrossMarginPositions should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // set test input to a value here + var testInput common.Address + testInput = common.Address{} + input, err := PackGetTotalFundingForCrossMarginPositions(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: GetTotalFundingForCrossMarginPositionsGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient gas for getTraderDataForMarket should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // populate test input here + testInput := GetTraderDataForMarketInput{} + input, err := PackGetTraderDataForMarket(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: GetTraderDataForMarketGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + } +) + +// TestTraderViewerRun tests the Run function of the precompile contract. +func TestTraderViewerRun(t *testing.T) { + // Run tests. + for name, test := range tests { + t.Run(name, func(t *testing.T) { + test.Run(t, Module, state.NewTestStateDB(t)) + }) + } +} + +func BenchmarkTraderViewer(b *testing.B) { + // Benchmark tests. + for name, test := range tests { + b.Run(name, func(b *testing.B) { + test.Bench(b, Module, state.NewTestStateDB(b)) + }) + } +} diff --git a/precompile/contracts/traderviewer/event.go b/precompile/contracts/traderviewer/event.go new file mode 100644 index 0000000000..cc4780b1b2 --- /dev/null +++ b/precompile/contracts/traderviewer/event.go @@ -0,0 +1,50 @@ +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package traderviewer + +import ( + "math/big" + + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ethereum/go-ethereum/common" +) + +// CUSTOM CODE STARTS HERE +// Reference imports to suppress errors from unused imports. This code and any unnecessary imports can be removed. +var ( + _ = big.NewInt + _ = common.Big0 + _ = contract.LogGas +) + +/* NOTE: Events can only be emitted in state-changing functions. So you cannot use events in read-only (view) functions. +Events are generally emitted at the end of a state-changing function with AddLog method of the StateDB. The AddLog method takes 4 arguments: + 1. Address of the contract that emitted the event. + 2. Topic hashes of the event. + 3. Encoded non-indexed data of the event. + 4. Block number at which the event was emitted. +The first argument is the address of the contract that emitted the event. +Topics can be at most 4 elements, the first topic is the hash of the event signature and the rest are the indexed event arguments. There can be at most 3 indexed arguments. +Topics cannot be fully unpacked into their original values since they're 32-bytes hashes. +The non-indexed arguments are encoded using the ABI encoding scheme. The non-indexed arguments can be unpacked into their original values. +Before packing the event, you need to calculate the gas cost of the event. The gas cost of an event is the base gas cost + the gas cost of the topics + the gas cost of the non-indexed data. +See Get{EvetName}EventGasCost functions for more details. +You can use the following code to emit an event in your state-changing precompile functions (generated packer might be different)): +topics, data, err := PackMyEvent( + topic1, + topic2, + data1, + data2, +) +if err != nil { + return nil, remainingGas, err +} +accessibleState.GetStateDB().AddLog( + ContractAddress, + topics, + data, + accessibleState.GetBlockContext().Number().Uint64(), +) +*/ diff --git a/precompile/contracts/traderviewer/module.go b/precompile/contracts/traderviewer/module.go new file mode 100644 index 0000000000..7494049de8 --- /dev/null +++ b/precompile/contracts/traderviewer/module.go @@ -0,0 +1,63 @@ +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package traderviewer + +import ( + "fmt" + + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" + + "github.com/ethereum/go-ethereum/common" +) + +var _ contract.Configurator = &configurator{} + +// ConfigKey is the key used in json config files to specify this precompile config. +// must be unique across all precompiles. +const ConfigKey = "traderViewerConfig" + +// ContractAddress is the defined address of the precompile contract. +// This should be unique across all precompile contracts. +// See precompile/registry/registry.go for registered precompile contracts and more information. +var ContractAddress = common.HexToAddress("{ASUITABLEHEXADDRESS}") // SET A SUITABLE HEX ADDRESS HERE + +// Module is the precompile module. It is used to register the precompile contract. +var Module = modules.Module{ + ConfigKey: ConfigKey, + Address: ContractAddress, + Contract: TraderViewerPrecompile, + Configurator: &configurator{}, +} + +type configurator struct{} + +func init() { + // Register the precompile module. + // Each precompile contract registers itself through [RegisterModule] function. + if err := modules.RegisterModule(Module); err != nil { + panic(err) + } +} + +// MakeConfig returns a new precompile config instance. +// This is required to Marshal/Unmarshal the precompile config. +func (*configurator) MakeConfig() precompileconfig.Config { + return new(Config) +} + +// Configure configures [state] with the given [cfg] precompileconfig. +// This function is called by the EVM once per precompile contract activation. +// You can use this function to set up your precompile contract's initial state, +// by using the [cfg] config and [state] stateDB. +func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { + // CUSTOM CODE STARTS HERE + if _, ok := cfg.(*Config); !ok { + return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) + } + + return nil +} From 50430761cd4bbf3b62ac85d753826a3a7b30aacc Mon Sep 17 00:00:00 2001 From: Atul Agarwal <21087753+asquare08@users.noreply.github.com> Date: Tue, 5 Mar 2024 01:50:06 +0530 Subject: [PATCH 02/14] add getNotionalPositionAndRequiredMargin --- .../orderbook/hubbleutils/data_structures.go | 12 ++++ .../evm/orderbook/hubbleutils/margin_math.go | 40 +++++++++++++ precompile/contracts/bibliophile/amm.go | 56 +++++++++++++++++++ .../contracts/bibliophile/clearing_house.go | 42 ++++++++++++++ precompile/contracts/bibliophile/client.go | 6 ++ precompile/contracts/traderviewer/contract.go | 5 +- .../traderviewer/notional_position.go | 15 +++++ 7 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 precompile/contracts/traderviewer/notional_position.go diff --git a/plugin/evm/orderbook/hubbleutils/data_structures.go b/plugin/evm/orderbook/hubbleutils/data_structures.go index 0b441b842c..04912b27dc 100644 --- a/plugin/evm/orderbook/hubbleutils/data_structures.go +++ b/plugin/evm/orderbook/hubbleutils/data_structures.go @@ -18,6 +18,18 @@ const ( Min_Allowable_Margin ) +type MarginType = uint8 + +const ( + Cross_Margin MarginType = iota + Isolated_Margin +) + +type AccountPreferences struct { + MarginType MarginType + MarginFraction *big.Int +} + type Collateral struct { Price *big.Int // scaled by 1e6 Weight *big.Int // scaled by 1e6 diff --git a/plugin/evm/orderbook/hubbleutils/margin_math.go b/plugin/evm/orderbook/hubbleutils/margin_math.go index ce440ed6e5..b361be81ea 100644 --- a/plugin/evm/orderbook/hubbleutils/margin_math.go +++ b/plugin/evm/orderbook/hubbleutils/margin_math.go @@ -31,6 +31,7 @@ type UserState struct { Margins []*big.Int PendingFunding *big.Int ReservedMargin *big.Int + AccountPreferences map[Market]*AccountPreferences } func UpgradeVersionV0orV1(blockTimestamp uint64) UpgradeVersion { @@ -118,6 +119,45 @@ func getOptimalPnl(hState *HubbleState, position *Position, margin *big.Int, mar return notionalPosition, unrealizedPnl } +func GetNotionalPositionAndRequiredMargin(hState *HubbleState, userState *UserState) (*big.Int, *big.Int, *big.Int) { + margin := Sub(GetNormalizedMargin(hState.Assets, userState.Margins), userState.PendingFunding) + notionalPosition, requiredMargin, unrealizedPnl := getCrossMarginAccountData(hState, userState) + return notionalPosition, Add(margin, unrealizedPnl), requiredMargin +} + +func getCrossMarginAccountData(hState *HubbleState, userState *UserState) (*big.Int, *big.Int, *big.Int) { + notionalPosition := big.NewInt(0) + unrealizedPnl := big.NewInt(0) + requiredMargin := big.NewInt(0) + + for _, market := range hState.ActiveMarkets { + if userState.AccountPreferences[market].MarginType == Cross_Margin { + _notionalPosition, _unrealizedPnl, _requiredMargin := getTraderPositionDetails(hState, userState.Positions[market], market, userState.AccountPreferences[market].MarginFraction) + notionalPosition.Add(notionalPosition, _notionalPosition) + unrealizedPnl.Add(unrealizedPnl, _unrealizedPnl) + requiredMargin.Add(requiredMargin, _requiredMargin) + } + } + return notionalPosition, requiredMargin, unrealizedPnl +} + +func getTraderPositionDetails(hState *HubbleState, position *Position, market Market, marginFraction *big.Int) (notionalPosition *big.Int, uPnL *big.Int, requiredMargin *big.Int) { + if position == nil || position.Size.Sign() == 0 { + return big.NewInt(0), big.NewInt(0), big.NewInt(0) + } + + // based on oracle price, + notionalPosition, unrealizedPnl, _ := GetPositionMetadata( + hState.OraclePrices[market], + position.OpenNotional, + position.Size, + big.NewInt(0), // margin is not used here + ) + requiredMargin = Div1e6(Mul(notionalPosition, marginFraction)) + + return notionalPosition, unrealizedPnl, requiredMargin +} + func GetPositionMetadata(price *big.Int, openNotional *big.Int, size *big.Int, margin *big.Int) (notionalPosition *big.Int, unrealisedPnl *big.Int, marginFraction *big.Int) { notionalPosition = GetNotionalPosition(price, size) uPnL := new(big.Int) diff --git a/precompile/contracts/bibliophile/amm.go b/precompile/contracts/bibliophile/amm.go index 7b58785731..70f3f773be 100644 --- a/precompile/contracts/bibliophile/amm.go +++ b/precompile/contracts/bibliophile/amm.go @@ -24,6 +24,11 @@ const ( ASKS_SLOT int64 = 22 BIDS_HEAD_SLOT int64 = 23 ASKS_HEAD_SLOT int64 = 24 + TRADE_MARGIN_FRACTION_SLOT int64 = 28 + LIQUIDATION_MARGIN_FRACTION_SLOT int64 = 29 + ISOLATED_TRADE_MARGIN_FRACTION_SLOT int64 = 30 + ISOLATED_LIQUIDATION_MARGIN_FRACTION_SLOT int64 = 31 + ACCOUNT_PREFERENCES_SLOT int64 = 33 ) // AMM State @@ -146,6 +151,57 @@ func getPosition(stateDB contract.StateDB, market common.Address, trader *common } } +func getTradeMarginFraction(stateDB contract.StateDB, market common.Address) *big.Int { + return stateDB.GetState(market, common.BigToHash(big.NewInt(TRADE_MARGIN_FRACTION_SLOT))).Big() +} + +func getLiquidationMarginFraction(stateDB contract.StateDB, market common.Address) *big.Int { + return stateDB.GetState(market, common.BigToHash(big.NewInt(LIQUIDATION_MARGIN_FRACTION_SLOT))).Big() +} + +func getIsolatedTradeMarginFraction(stateDB contract.StateDB, market common.Address) *big.Int { + return stateDB.GetState(market, common.BigToHash(big.NewInt(ISOLATED_TRADE_MARGIN_FRACTION_SLOT))).Big() +} + +func getIsolatedLiquidationMarginFraction(stateDB contract.StateDB, market common.Address) *big.Int { + return stateDB.GetState(market, common.BigToHash(big.NewInt(ISOLATED_LIQUIDATION_MARGIN_FRACTION_SLOT))).Big() +} + +func accountPreferencesSlot(trader *common.Address) *big.Int { + return new(big.Int).SetBytes(crypto.Keccak256(append(common.LeftPadBytes(trader.Bytes(), 32), common.LeftPadBytes(big.NewInt(ACCOUNT_PREFERENCES_SLOT).Bytes(), 32)...))) +} + +func getMarginType(stateDB contract.StateDB, market common.Address, trader *common.Address) uint8 { + return uint8(stateDB.GetState(market, common.BigToHash(accountPreferencesSlot(trader))).Big().Uint64()) +} + +func getTraderMarginFraction(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { + return stateDB.GetState(market, common.BigToHash(new(big.Int).Add(accountPreferencesSlot(trader), big.NewInt(1)))).Big() +} + +func getMarginFractionByMode(stateDB contract.StateDB, market common.Address, trader *common.Address, mode uint8) *big.Int { + if mode == 0 { + if (getMarginType(stateDB, market, trader) == 1) { + return getIsolatedLiquidationMarginFraction(stateDB, market) + } else { + return getLiquidationMarginFraction(stateDB, market) + } + } + // retuns trade margin fraction by default + // @todo check if can be reverted in case of invalid mode + return calcTradeMarginFraction(stateDB, market, trader) +} + +func calcTradeMarginFraction(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { + if (getTraderMarginFraction(stateDB, market, trader).Cmp(big.NewInt(0)) != 0) { + return getTraderMarginFraction(stateDB, market, trader) + } else if (getMarginType(stateDB, market, trader) == 1) { + return getIsolatedTradeMarginFraction(stateDB, market) + } else { + return getTradeMarginFraction(stateDB, market) + } +} + // Utils func getPendingFundingPayment(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { diff --git a/precompile/contracts/bibliophile/clearing_house.go b/precompile/contracts/bibliophile/clearing_house.go index 73d3f1caa5..cffce145f8 100644 --- a/precompile/contracts/bibliophile/clearing_house.go +++ b/precompile/contracts/bibliophile/clearing_house.go @@ -64,6 +64,7 @@ type GetNotionalPositionAndMarginInput struct { type GetNotionalPositionAndMarginOutput struct { NotionalPosition *big.Int Margin *big.Int + RequiredMargin *big.Int } func getNotionalPositionAndMargin(stateDB contract.StateDB, input *GetNotionalPositionAndMarginInput, upgradeVersion hu.UpgradeVersion) GetNotionalPositionAndMarginOutput { @@ -104,6 +105,47 @@ func getNotionalPositionAndMargin(stateDB contract.StateDB, input *GetNotionalPo } } +func getNotionalPositionAndRequiredMargin(stateDB contract.StateDB, input *GetNotionalPositionAndMarginInput, upgradeVersion hu.UpgradeVersion) GetNotionalPositionAndMarginOutput { + markets := GetMarkets(stateDB) + numMarkets := len(markets) + positions := make(map[int]*hu.Position, numMarkets) + underlyingPrices := make(map[int]*big.Int, numMarkets) + accountPreferences := make(map[int]*hu.AccountPreferences, numMarkets) + var activeMarketIds []int + for i, market := range markets { + // @todo can use `market` instead of `GetMarketAddressFromMarketID`? + positions[i] = getPosition(stateDB, GetMarketAddressFromMarketID(int64(i), stateDB), &input.Trader) + underlyingPrices[i] = getUnderlyingPrice(stateDB, market) + activeMarketIds = append(activeMarketIds, i) + accountPreferences[i].MarginType = getMarginType(stateDB, market, &input.Trader) + accountPreferences[i].MarginFraction = getMarginFractionByMode(stateDB, market, &input.Trader, input.Mode) + } + pendingFunding := big.NewInt(0) + if input.IncludeFundingPayments { + // @todo change it to return only cross margin funding + pendingFunding = GetTotalFunding(stateDB, &input.Trader) + } + notionalPosition, margin, requiredMargin := hu.GetNotionalPositionAndRequiredMargin( + &hu.HubbleState{ + Assets: GetCollaterals(stateDB), + OraclePrices: underlyingPrices, + ActiveMarkets: activeMarketIds, + UpgradeVersion: upgradeVersion, + }, + &hu.UserState{ + Positions: positions, + Margins: getMargins(stateDB, input.Trader), + PendingFunding: pendingFunding, + AccountPreferences: accountPreferences, + }, + ) + return GetNotionalPositionAndMarginOutput{ + NotionalPosition: notionalPosition, + Margin: margin, + RequiredMargin: requiredMargin, + } +} + func GetTotalFunding(stateDB contract.StateDB, trader *common.Address) *big.Int { totalFunding := big.NewInt(0) for _, market := range GetMarkets(stateDB) { diff --git a/precompile/contracts/bibliophile/client.go b/precompile/contracts/bibliophile/client.go index c03a219964..7cdcded3c7 100644 --- a/precompile/contracts/bibliophile/client.go +++ b/precompile/contracts/bibliophile/client.go @@ -54,6 +54,7 @@ type BibliophileClient interface { GetTimeStamp() uint64 GetNotionalPositionAndMargin(trader common.Address, includeFundingPayments bool, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int) + GetNotionalPositionAndRequiredMargin(trader common.Address, includeFundingPayments bool, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int, *big.Int) HasReferrer(trader common.Address) bool GetActiveMarketsCount() int64 @@ -212,6 +213,11 @@ func (b *bibliophileClient) GetNotionalPositionAndMargin(trader common.Address, return output.NotionalPosition, output.Margin } +func (b *bibliophileClient) GetNotionalPositionAndRequiredMargin(trader common.Address, includeFundingPayments bool, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int, *big.Int) { + output := getNotionalPositionAndRequiredMargin(b.accessibleState.GetStateDB(), &GetNotionalPositionAndMarginInput{Trader: trader, IncludeFundingPayments: includeFundingPayments, Mode: mode}, upgradeVersion) + return output.NotionalPosition, output.Margin, output.RequiredMargin +} + func (b *bibliophileClient) HasReferrer(trader common.Address) bool { return HasReferrer(b.accessibleState.GetStateDB(), trader) } diff --git a/precompile/contracts/traderviewer/contract.go b/precompile/contracts/traderviewer/contract.go index 9a297445fc..29db710b0f 100644 --- a/precompile/contracts/traderviewer/contract.go +++ b/precompile/contracts/traderviewer/contract.go @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ava-labs/subnet-evm/precompile/contracts/bibliophile" _ "embed" @@ -195,8 +196,8 @@ func getNotionalPositionAndMargin(accessibleState contract.AccessibleState, call } // CUSTOM CODE STARTS HERE - _ = inputStruct // CUSTOM CODE OPERATES ON INPUT - var output GetNotionalPositionAndMarginOutput // CUSTOM CODE FOR AN OUTPUT + bibliophile := bibliophile.NewBibliophileClient(accessibleState) + output := GetNotionalPositionAndMargin(bibliophile, &inputStruct) packedOutput, err := PackGetNotionalPositionAndMarginOutput(output) if err != nil { return nil, remainingGas, err diff --git a/precompile/contracts/traderviewer/notional_position.go b/precompile/contracts/traderviewer/notional_position.go new file mode 100644 index 0000000000..383f82c6d2 --- /dev/null +++ b/precompile/contracts/traderviewer/notional_position.go @@ -0,0 +1,15 @@ +package traderviewer + +import ( + hu "github.com/ava-labs/subnet-evm/plugin/evm/orderbook/hubbleutils" + b "github.com/ava-labs/subnet-evm/precompile/contracts/bibliophile" +) + +func GetNotionalPositionAndMargin(bibliophile b.BibliophileClient, input *GetNotionalPositionAndMarginInput) GetNotionalPositionAndMarginOutput { + notionalPosition, margin, requiredMargin := bibliophile.GetNotionalPositionAndRequiredMargin(input.Trader, input.IncludeFundingPayments, input.Mode, hu.V2) // @todo check if this is the right upgrade version + return GetNotionalPositionAndMarginOutput{ + NotionalPosition: notionalPosition, + Margin: margin, + RequiredMargin: requiredMargin, + } +} From b189ca9742f5e6d9023902b414159af63a8dcec8 Mon Sep 17 00:00:00 2001 From: Atul Agarwal <21087753+asquare08@users.noreply.github.com> Date: Thu, 7 Mar 2024 19:53:06 +0530 Subject: [PATCH 03/14] add functions to traderViewer precompile --- .../evm/orderbook/hubbleutils/margin_math.go | 10 +- precompile/contracts/bibliophile/amm.go | 6 +- .../contracts/bibliophile/clearing_house.go | 93 +++++++++++++++---- precompile/contracts/bibliophile/client.go | 17 ++++ .../contracts/bibliophile/client_mock.go | 65 +++++++++++++ .../contracts/juror/notional_position.go | 4 +- precompile/contracts/traderviewer/contract.go | 13 ++- .../traderviewer/notional_position.go | 27 ++++++ 8 files changed, 203 insertions(+), 32 deletions(-) diff --git a/plugin/evm/orderbook/hubbleutils/margin_math.go b/plugin/evm/orderbook/hubbleutils/margin_math.go index b361be81ea..7b611ef3e2 100644 --- a/plugin/evm/orderbook/hubbleutils/margin_math.go +++ b/plugin/evm/orderbook/hubbleutils/margin_math.go @@ -121,18 +121,18 @@ func getOptimalPnl(hState *HubbleState, position *Position, margin *big.Int, mar func GetNotionalPositionAndRequiredMargin(hState *HubbleState, userState *UserState) (*big.Int, *big.Int, *big.Int) { margin := Sub(GetNormalizedMargin(hState.Assets, userState.Margins), userState.PendingFunding) - notionalPosition, requiredMargin, unrealizedPnl := getCrossMarginAccountData(hState, userState) + notionalPosition, requiredMargin, unrealizedPnl := GetCrossMarginAccountData(hState, userState) return notionalPosition, Add(margin, unrealizedPnl), requiredMargin } -func getCrossMarginAccountData(hState *HubbleState, userState *UserState) (*big.Int, *big.Int, *big.Int) { +func GetCrossMarginAccountData(hState *HubbleState, userState *UserState) (*big.Int, *big.Int, *big.Int) { notionalPosition := big.NewInt(0) unrealizedPnl := big.NewInt(0) requiredMargin := big.NewInt(0) for _, market := range hState.ActiveMarkets { if userState.AccountPreferences[market].MarginType == Cross_Margin { - _notionalPosition, _unrealizedPnl, _requiredMargin := getTraderPositionDetails(hState, userState.Positions[market], market, userState.AccountPreferences[market].MarginFraction) + _notionalPosition, _unrealizedPnl, _requiredMargin := GetTraderPositionDetails(userState.Positions[market], hState.OraclePrices[market], userState.AccountPreferences[market].MarginFraction) notionalPosition.Add(notionalPosition, _notionalPosition) unrealizedPnl.Add(unrealizedPnl, _unrealizedPnl) requiredMargin.Add(requiredMargin, _requiredMargin) @@ -141,14 +141,14 @@ func getCrossMarginAccountData(hState *HubbleState, userState *UserState) (*big. return notionalPosition, requiredMargin, unrealizedPnl } -func getTraderPositionDetails(hState *HubbleState, position *Position, market Market, marginFraction *big.Int) (notionalPosition *big.Int, uPnL *big.Int, requiredMargin *big.Int) { +func GetTraderPositionDetails(position *Position, oraclePrice *big.Int, marginFraction *big.Int) (notionalPosition *big.Int, uPnL *big.Int, requiredMargin *big.Int) { if position == nil || position.Size.Sign() == 0 { return big.NewInt(0), big.NewInt(0), big.NewInt(0) } // based on oracle price, notionalPosition, unrealizedPnl, _ := GetPositionMetadata( - hState.OraclePrices[market], + oraclePrice, position.OpenNotional, position.Size, big.NewInt(0), // margin is not used here diff --git a/precompile/contracts/bibliophile/amm.go b/precompile/contracts/bibliophile/amm.go index 70f3f773be..968869b9e3 100644 --- a/precompile/contracts/bibliophile/amm.go +++ b/precompile/contracts/bibliophile/amm.go @@ -180,8 +180,8 @@ func getTraderMarginFraction(stateDB contract.StateDB, market common.Address, tr } func getMarginFractionByMode(stateDB contract.StateDB, market common.Address, trader *common.Address, mode uint8) *big.Int { - if mode == 0 { - if (getMarginType(stateDB, market, trader) == 1) { + if mode == hu.Maintenance_Margin { + if (getMarginType(stateDB, market, trader) == hu.Isolated_Margin) { return getIsolatedLiquidationMarginFraction(stateDB, market) } else { return getLiquidationMarginFraction(stateDB, market) @@ -195,7 +195,7 @@ func getMarginFractionByMode(stateDB contract.StateDB, market common.Address, tr func calcTradeMarginFraction(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { if (getTraderMarginFraction(stateDB, market, trader).Cmp(big.NewInt(0)) != 0) { return getTraderMarginFraction(stateDB, market, trader) - } else if (getMarginType(stateDB, market, trader) == 1) { + } else if (getMarginType(stateDB, market, trader) == hu.Isolated_Margin) { return getIsolatedTradeMarginFraction(stateDB, market) } else { return getTradeMarginFraction(stateDB, market) diff --git a/precompile/contracts/bibliophile/clearing_house.go b/precompile/contracts/bibliophile/clearing_house.go index cffce145f8..dc4d007c0f 100644 --- a/precompile/contracts/bibliophile/clearing_house.go +++ b/precompile/contracts/bibliophile/clearing_house.go @@ -67,6 +67,14 @@ type GetNotionalPositionAndMarginOutput struct { RequiredMargin *big.Int } +type GetTraderDataForMarketOutput struct { + NotionalPosition *big.Int + RequiredMargin *big.Int + UnrealizedPnl *big.Int + PendingFunding *big.Int + IsIsolated bool +} + func getNotionalPositionAndMargin(stateDB contract.StateDB, input *GetNotionalPositionAndMarginInput, upgradeVersion hu.UpgradeVersion) GetNotionalPositionAndMarginOutput { markets := GetMarkets(stateDB) numMarkets := len(markets) @@ -106,24 +114,10 @@ func getNotionalPositionAndMargin(stateDB contract.StateDB, input *GetNotionalPo } func getNotionalPositionAndRequiredMargin(stateDB contract.StateDB, input *GetNotionalPositionAndMarginInput, upgradeVersion hu.UpgradeVersion) GetNotionalPositionAndMarginOutput { - markets := GetMarkets(stateDB) - numMarkets := len(markets) - positions := make(map[int]*hu.Position, numMarkets) - underlyingPrices := make(map[int]*big.Int, numMarkets) - accountPreferences := make(map[int]*hu.AccountPreferences, numMarkets) - var activeMarketIds []int - for i, market := range markets { - // @todo can use `market` instead of `GetMarketAddressFromMarketID`? - positions[i] = getPosition(stateDB, GetMarketAddressFromMarketID(int64(i), stateDB), &input.Trader) - underlyingPrices[i] = getUnderlyingPrice(stateDB, market) - activeMarketIds = append(activeMarketIds, i) - accountPreferences[i].MarginType = getMarginType(stateDB, market, &input.Trader) - accountPreferences[i].MarginFraction = getMarginFractionByMode(stateDB, market, &input.Trader, input.Mode) - } + positions, underlyingPrices, accountPreferences, activeMarketIds := getMarketsDataFromDB(stateDB, &input.Trader, input.Mode) pendingFunding := big.NewInt(0) if input.IncludeFundingPayments { - // @todo change it to return only cross margin funding - pendingFunding = GetTotalFunding(stateDB, &input.Trader) + pendingFunding = getTotalFundingForCrossMarginPositions(stateDB, &input.Trader) } notionalPosition, margin, requiredMargin := hu.GetNotionalPositionAndRequiredMargin( &hu.HubbleState{ @@ -146,6 +140,73 @@ func getNotionalPositionAndRequiredMargin(stateDB contract.StateDB, input *GetNo } } +func getCrossMarginAccountData(stateDB contract.StateDB, trader *common.Address, mode uint8, upgradeVersion hu.UpgradeVersion) GetTraderDataForMarketOutput { + positions, underlyingPrices, accountPreferences, activeMarketIds := getMarketsDataFromDB(stateDB, trader, mode) + notionalPosition, requiredMargin, unrealizedPnl := hu.GetCrossMarginAccountData( + &hu.HubbleState{ + ActiveMarkets: activeMarketIds, + OraclePrices: underlyingPrices, + UpgradeVersion: upgradeVersion, + }, + &hu.UserState{ + Positions: positions, + AccountPreferences: accountPreferences, + }, + ) + pendingFunding := getTotalFundingForCrossMarginPositions(stateDB, trader) + return GetTraderDataForMarketOutput { + NotionalPosition: notionalPosition, + RequiredMargin: requiredMargin, + UnrealizedPnl: unrealizedPnl, + PendingFunding: pendingFunding, + } +} + +func getMarketsDataFromDB(stateDB contract.StateDB, trader *common.Address, mode uint8) (positions map[int]*hu.Position, underlyingPrices map[int]*big.Int, accountPreferences map[int]*hu.AccountPreferences, activeMarketIds []int) { + markets := GetMarkets(stateDB) + numMarkets := len(markets) + positions = make(map[int]*hu.Position, numMarkets) + underlyingPrices = make(map[int]*big.Int, numMarkets) + accountPreferences = make(map[int]*hu.AccountPreferences, numMarkets) + activeMarketIds = make([]int, numMarkets) + for i, market := range markets { + // @todo can use `market` instead of `GetMarketAddressFromMarketID`? + positions[i] = getPosition(stateDB, GetMarketAddressFromMarketID(int64(i), stateDB), trader) + underlyingPrices[i] = getUnderlyingPrice(stateDB, market) + activeMarketIds[i] = i + accountPreferences[i].MarginType = getMarginType(stateDB, market, trader) + accountPreferences[i].MarginFraction = getMarginFractionByMode(stateDB, market, trader, mode) + } + return positions, underlyingPrices, accountPreferences, activeMarketIds +} + +func getTotalFundingForCrossMarginPositions(stateDB contract.StateDB, trader *common.Address) *big.Int { + totalFunding := big.NewInt(0) + for _, market := range GetMarkets(stateDB) { + if getMarginType(stateDB, market, trader) == hu.Cross_Margin { + totalFunding.Add(totalFunding, getPendingFundingPayment(stateDB, market, trader)) + } + } + return totalFunding +} + +func getTraderDataForMarket(stateDB contract.StateDB, trader *common.Address, marketId int64, mode uint8) GetTraderDataForMarketOutput { + market := GetMarketAddressFromMarketID(marketId, stateDB) + position := getPosition(stateDB, market, trader) + marginFraction := getMarginFractionByMode(stateDB, market, trader, mode) + underlyingPrice := getUnderlyingPrice(stateDB, market) + notionalPosition, unrealizedPnl, requiredMargin := hu.GetTraderPositionDetails(position, underlyingPrice, marginFraction) + pendingFunding := getPendingFundingPayment(stateDB, market, trader) + isIsolated := getMarginType(stateDB, market, trader) == hu.Isolated_Margin + return GetTraderDataForMarketOutput{ + IsIsolated: isIsolated, + NotionalPosition: notionalPosition, + RequiredMargin: requiredMargin, + UnrealizedPnl: unrealizedPnl, + PendingFunding: pendingFunding, + } +} + func GetTotalFunding(stateDB contract.StateDB, trader *common.Address) *big.Int { totalFunding := big.NewInt(0) for _, market := range GetMarkets(stateDB) { diff --git a/precompile/contracts/bibliophile/client.go b/precompile/contracts/bibliophile/client.go index 7cdcded3c7..e39c59a41c 100644 --- a/precompile/contracts/bibliophile/client.go +++ b/precompile/contracts/bibliophile/client.go @@ -55,6 +55,9 @@ type BibliophileClient interface { GetTimeStamp() uint64 GetNotionalPositionAndMargin(trader common.Address, includeFundingPayments bool, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int) GetNotionalPositionAndRequiredMargin(trader common.Address, includeFundingPayments bool, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int, *big.Int) + GetCrossMarginAccountData(trader common.Address, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int, *big.Int, *big.Int) + GetTotalFundingForCrossMarginPositions(trader *common.Address) *big.Int + GetTraderDataForMarket(trader common.Address, marketId int64, mode uint8) (bool, *big.Int, *big.Int, *big.Int, *big.Int) HasReferrer(trader common.Address) bool GetActiveMarketsCount() int64 @@ -218,6 +221,20 @@ func (b *bibliophileClient) GetNotionalPositionAndRequiredMargin(trader common.A return output.NotionalPosition, output.Margin, output.RequiredMargin } +func (b *bibliophileClient) GetCrossMarginAccountData(trader common.Address, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int, *big.Int, *big.Int) { + output := getCrossMarginAccountData(b.accessibleState.GetStateDB(), &trader, mode, upgradeVersion) + return output.NotionalPosition, output.RequiredMargin, output.UnrealizedPnl, output.PendingFunding +} + +func (b *bibliophileClient) GetTotalFundingForCrossMarginPositions(trader *common.Address) *big.Int { + return getTotalFundingForCrossMarginPositions(b.accessibleState.GetStateDB(), trader) +} + +func (b *bibliophileClient) GetTraderDataForMarket(trader common.Address, marketId int64, mode uint8) (bool, *big.Int, *big.Int, *big.Int, *big.Int) { + output := getTraderDataForMarket(b.accessibleState.GetStateDB(), &trader, marketId, mode) + return output.IsIsolated, output.NotionalPosition, output.UnrealizedPnl, output.RequiredMargin, output.PendingFunding +} + func (b *bibliophileClient) HasReferrer(trader common.Address) bool { return HasReferrer(b.accessibleState.GetStateDB(), trader) } diff --git a/precompile/contracts/bibliophile/client_mock.go b/precompile/contracts/bibliophile/client_mock.go index c48907e920..0f9c0dd2fd 100644 --- a/precompile/contracts/bibliophile/client_mock.go +++ b/precompile/contracts/bibliophile/client_mock.go @@ -164,6 +164,23 @@ func (mr *MockBibliophileClientMockRecorder) GetBlockPlaced(orderHash interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockPlaced", reflect.TypeOf((*MockBibliophileClient)(nil).GetBlockPlaced), orderHash) } +// GetCrossMarginAccountData mocks base method. +func (m *MockBibliophileClient) GetCrossMarginAccountData(trader common.Address, mode uint8, upgradeVersion hubbleutils.UpgradeVersion) (*big.Int, *big.Int, *big.Int, *big.Int) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCrossMarginAccountData", trader, mode, upgradeVersion) + ret0, _ := ret[0].(*big.Int) + ret1, _ := ret[1].(*big.Int) + ret2, _ := ret[2].(*big.Int) + ret3, _ := ret[3].(*big.Int) + return ret0, ret1, ret2, ret3 +} + +// GetCrossMarginAccountData indicates an expected call of GetCrossMarginAccountData. +func (mr *MockBibliophileClientMockRecorder) GetCrossMarginAccountData(trader, mode, upgradeVersion interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCrossMarginAccountData", reflect.TypeOf((*MockBibliophileClient)(nil).GetCrossMarginAccountData), trader, mode, upgradeVersion) +} + // GetImpactMarginNotional mocks base method. func (m *MockBibliophileClient) GetImpactMarginNotional(ammAddress common.Address) *big.Int { m.ctrl.T.Helper() @@ -291,6 +308,22 @@ func (mr *MockBibliophileClientMockRecorder) GetNotionalPositionAndMargin(trader return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotionalPositionAndMargin", reflect.TypeOf((*MockBibliophileClient)(nil).GetNotionalPositionAndMargin), trader, includeFundingPayments, mode, upgradeVersion) } +// GetNotionalPositionAndRequiredMargin mocks base method. +func (m *MockBibliophileClient) GetNotionalPositionAndRequiredMargin(trader common.Address, includeFundingPayments bool, mode uint8, upgradeVersion hubbleutils.UpgradeVersion) (*big.Int, *big.Int, *big.Int) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNotionalPositionAndRequiredMargin", trader, includeFundingPayments, mode, upgradeVersion) + ret0, _ := ret[0].(*big.Int) + ret1, _ := ret[1].(*big.Int) + ret2, _ := ret[2].(*big.Int) + return ret0, ret1, ret2 +} + +// GetNotionalPositionAndRequiredMargin indicates an expected call of GetNotionalPositionAndRequiredMargin. +func (mr *MockBibliophileClientMockRecorder) GetNotionalPositionAndRequiredMargin(trader, includeFundingPayments, mode, upgradeVersion interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotionalPositionAndRequiredMargin", reflect.TypeOf((*MockBibliophileClient)(nil).GetNotionalPositionAndRequiredMargin), trader, includeFundingPayments, mode, upgradeVersion) +} + // GetOrderFilledAmount mocks base method. func (m *MockBibliophileClient) GetOrderFilledAmount(orderHash [32]byte) *big.Int { m.ctrl.T.Helper() @@ -431,6 +464,38 @@ func (mr *MockBibliophileClientMockRecorder) GetTimeStamp() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimeStamp", reflect.TypeOf((*MockBibliophileClient)(nil).GetTimeStamp)) } +// GetTotalFundingForCrossMarginPositions mocks base method. +func (m *MockBibliophileClient) GetTotalFundingForCrossMarginPositions(trader *common.Address) *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTotalFundingForCrossMarginPositions", trader) + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// GetTotalFundingForCrossMarginPositions indicates an expected call of GetTotalFundingForCrossMarginPositions. +func (mr *MockBibliophileClientMockRecorder) GetTotalFundingForCrossMarginPositions(trader interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTotalFundingForCrossMarginPositions", reflect.TypeOf((*MockBibliophileClient)(nil).GetTotalFundingForCrossMarginPositions), trader) +} + +// GetTraderDataForMarket mocks base method. +func (m *MockBibliophileClient) GetTraderDataForMarket(trader common.Address, marketId int64, mode uint8) (bool, *big.Int, *big.Int, *big.Int, *big.Int) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTraderDataForMarket", trader, marketId, mode) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(*big.Int) + ret2, _ := ret[2].(*big.Int) + ret3, _ := ret[3].(*big.Int) + ret4, _ := ret[4].(*big.Int) + return ret0, ret1, ret2, ret3, ret4 +} + +// GetTraderDataForMarket indicates an expected call of GetTraderDataForMarket. +func (mr *MockBibliophileClientMockRecorder) GetTraderDataForMarket(trader, marketId, mode interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTraderDataForMarket", reflect.TypeOf((*MockBibliophileClient)(nil).GetTraderDataForMarket), trader, marketId, mode) +} + // GetUpperAndLowerBoundForMarket mocks base method. func (m *MockBibliophileClient) GetUpperAndLowerBoundForMarket(marketId int64) (*big.Int, *big.Int) { m.ctrl.T.Helper() diff --git a/precompile/contracts/juror/notional_position.go b/precompile/contracts/juror/notional_position.go index 57d4ef9057..85396c1907 100644 --- a/precompile/contracts/juror/notional_position.go +++ b/precompile/contracts/juror/notional_position.go @@ -4,7 +4,9 @@ import ( hu "github.com/ava-labs/subnet-evm/plugin/evm/orderbook/hubbleutils" b "github.com/ava-labs/subnet-evm/precompile/contracts/bibliophile" ) - +/** + * Depricated. Use traderviewer.GetNotionalPositionAndMargin instead +*/ func GetNotionalPositionAndMargin(bibliophile b.BibliophileClient, input *GetNotionalPositionAndMarginInput) GetNotionalPositionAndMarginOutput { notionalPosition, margin := bibliophile.GetNotionalPositionAndMargin(input.Trader, input.IncludeFundingPayments, input.Mode, hu.UpgradeVersionV0orV1(bibliophile.GetTimeStamp())) return GetNotionalPositionAndMarginOutput{ diff --git a/precompile/contracts/traderviewer/contract.go b/precompile/contracts/traderviewer/contract.go index 29db710b0f..6244ef7315 100644 --- a/precompile/contracts/traderviewer/contract.go +++ b/precompile/contracts/traderviewer/contract.go @@ -138,8 +138,8 @@ func getCrossMarginAccountData(accessibleState contract.AccessibleState, caller } // CUSTOM CODE STARTS HERE - _ = inputStruct // CUSTOM CODE OPERATES ON INPUT - var output GetCrossMarginAccountDataOutput // CUSTOM CODE FOR AN OUTPUT + bibliophile := bibliophile.NewBibliophileClient(accessibleState) + output := GetCrossMarginAccountData(bibliophile, &inputStruct) packedOutput, err := PackGetCrossMarginAccountDataOutput(output) if err != nil { return nil, remainingGas, err @@ -256,9 +256,8 @@ func getTotalFundingForCrossMarginPositions(accessibleState contract.AccessibleS } // CUSTOM CODE STARTS HERE - _ = inputStruct // CUSTOM CODE OPERATES ON INPUT - - var output *big.Int // CUSTOM CODE FOR AN OUTPUT + bibliophile := bibliophile.NewBibliophileClient(accessibleState) + output := GetTotalFundingForCrossMarginPositions(bibliophile, &inputStruct) packedOutput, err := PackGetTotalFundingForCrossMarginPositionsOutput(output) if err != nil { return nil, remainingGas, err @@ -317,8 +316,8 @@ func getTraderDataForMarket(accessibleState contract.AccessibleState, caller com } // CUSTOM CODE STARTS HERE - _ = inputStruct // CUSTOM CODE OPERATES ON INPUT - var output GetTraderDataForMarketOutput // CUSTOM CODE FOR AN OUTPUT + bibliophile := bibliophile.NewBibliophileClient(accessibleState) + output := GetTraderDataForMarket(bibliophile, &inputStruct) packedOutput, err := PackGetTraderDataForMarketOutput(output) if err != nil { return nil, remainingGas, err diff --git a/precompile/contracts/traderviewer/notional_position.go b/precompile/contracts/traderviewer/notional_position.go index 383f82c6d2..6cc9d00ccb 100644 --- a/precompile/contracts/traderviewer/notional_position.go +++ b/precompile/contracts/traderviewer/notional_position.go @@ -1,8 +1,10 @@ package traderviewer import ( + "math/big" hu "github.com/ava-labs/subnet-evm/plugin/evm/orderbook/hubbleutils" b "github.com/ava-labs/subnet-evm/precompile/contracts/bibliophile" + "github.com/ethereum/go-ethereum/common" ) func GetNotionalPositionAndMargin(bibliophile b.BibliophileClient, input *GetNotionalPositionAndMarginInput) GetNotionalPositionAndMarginOutput { @@ -13,3 +15,28 @@ func GetNotionalPositionAndMargin(bibliophile b.BibliophileClient, input *GetNot RequiredMargin: requiredMargin, } } + +func GetCrossMarginAccountData(bibliophile b.BibliophileClient, input *GetCrossMarginAccountDataInput) GetCrossMarginAccountDataOutput { + notionalPosition, requiredMargin, unrealizedPnl, pendingFunding := bibliophile.GetCrossMarginAccountData(input.Trader, input.Mode, hu.V2) // @todo check if this is the right upgrade version + return GetCrossMarginAccountDataOutput{ + NotionalPosition: notionalPosition, + RequiredMargin: requiredMargin, + UnrealizedPnl: unrealizedPnl, + PendingFunding: pendingFunding, + } +} + +func GetTotalFundingForCrossMarginPositions(bibliophile b.BibliophileClient, trader *common.Address) *big.Int { + return bibliophile.GetTotalFundingForCrossMarginPositions(trader) +} + +func GetTraderDataForMarket(bibliophile b.BibliophileClient, input *GetTraderDataForMarketInput) GetTraderDataForMarketOutput { + isIsolated, notionalPosition, unrealizedPnl, requiredMargin, pendingFunding := bibliophile.GetTraderDataForMarket(input.Trader, input.AmmIndex.Int64(), input.Mode) + return GetTraderDataForMarketOutput{ + IsIsolated: isIsolated, + NotionalPosition: notionalPosition, + UnrealizedPnl: unrealizedPnl, + RequiredMargin: requiredMargin, + PendingFunding: pendingFunding, + } +} From e5c8e09036cd70320c01e4ee2eaf12917a33e509 Mon Sep 17 00:00:00 2001 From: Atul Agarwal <21087753+asquare08@users.noreply.github.com> Date: Thu, 7 Mar 2024 21:57:55 +0530 Subject: [PATCH 04/14] get required margin --- precompile/contracts/bibliophile/amm.go | 20 ++++++++++++++----- precompile/contracts/bibliophile/client.go | 5 +++++ .../contracts/juror/matching_validation.go | 9 ++++----- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/precompile/contracts/bibliophile/amm.go b/precompile/contracts/bibliophile/amm.go index 968869b9e3..9c8feb495c 100644 --- a/precompile/contracts/bibliophile/amm.go +++ b/precompile/contracts/bibliophile/amm.go @@ -175,7 +175,7 @@ func getMarginType(stateDB contract.StateDB, market common.Address, trader *comm return uint8(stateDB.GetState(market, common.BigToHash(accountPreferencesSlot(trader))).Big().Uint64()) } -func getTraderMarginFraction(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { +func traderMarginFraction(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { return stateDB.GetState(market, common.BigToHash(new(big.Int).Add(accountPreferencesSlot(trader), big.NewInt(1)))).Big() } @@ -189,12 +189,12 @@ func getMarginFractionByMode(stateDB contract.StateDB, market common.Address, tr } // retuns trade margin fraction by default // @todo check if can be reverted in case of invalid mode - return calcTradeMarginFraction(stateDB, market, trader) + return getTraderMarginFraction(stateDB, market, trader) } -func calcTradeMarginFraction(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { - if (getTraderMarginFraction(stateDB, market, trader).Cmp(big.NewInt(0)) != 0) { - return getTraderMarginFraction(stateDB, market, trader) +func getTraderMarginFraction(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { + if (traderMarginFraction(stateDB, market, trader).Cmp(big.NewInt(0)) != 0) { + return traderMarginFraction(stateDB, market, trader) } else if (getMarginType(stateDB, market, trader) == hu.Isolated_Margin) { return getIsolatedTradeMarginFraction(stateDB, market) } else { @@ -202,6 +202,16 @@ func calcTradeMarginFraction(stateDB contract.StateDB, market common.Address, tr } } +func getRequiredMargin(stateDB contract.StateDB, baseAsset *big.Int, price *big.Int, marketId int64, trader *common.Address) *big.Int { + quoteAsset := hu.Div1e18(hu.Mul(new(big.Int).Abs(baseAsset), price)) + return getRequiredMarginForQuote(stateDB, GetMarketAddressFromMarketID(marketId, stateDB), trader, quoteAsset) +} + +func getRequiredMarginForQuote(stateDB contract.StateDB, market common.Address, trader *common.Address, quote *big.Int) *big.Int { + marginFraction := getTraderMarginFraction(stateDB, market, trader) + return hu.Div1e6(new(big.Int).Mul(quote, marginFraction)) +} + // Utils func getPendingFundingPayment(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { diff --git a/precompile/contracts/bibliophile/client.go b/precompile/contracts/bibliophile/client.go index e39c59a41c..a69d3adc3d 100644 --- a/precompile/contracts/bibliophile/client.go +++ b/precompile/contracts/bibliophile/client.go @@ -58,6 +58,7 @@ type BibliophileClient interface { GetCrossMarginAccountData(trader common.Address, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int, *big.Int, *big.Int) GetTotalFundingForCrossMarginPositions(trader *common.Address) *big.Int GetTraderDataForMarket(trader common.Address, marketId int64, mode uint8) (bool, *big.Int, *big.Int, *big.Int, *big.Int) + GetRequiredMargin(baseAsset *big.Int, price *big.Int, marketId int64, trader *common.Address) *big.Int HasReferrer(trader common.Address) bool GetActiveMarketsCount() int64 @@ -235,6 +236,10 @@ func (b *bibliophileClient) GetTraderDataForMarket(trader common.Address, market return output.IsIsolated, output.NotionalPosition, output.UnrealizedPnl, output.RequiredMargin, output.PendingFunding } +func (b *bibliophileClient) GetRequiredMargin(baseAsset *big.Int, price *big.Int, marketId int64, trader *common.Address) *big.Int { + return getRequiredMargin(b.accessibleState.GetStateDB(), baseAsset, price, marketId, trader) +} + func (b *bibliophileClient) HasReferrer(trader common.Address) bool { return HasReferrer(b.accessibleState.GetStateDB(), trader) } diff --git a/precompile/contracts/juror/matching_validation.go b/precompile/contracts/juror/matching_validation.go index 6635f4c96d..401648d524 100644 --- a/precompile/contracts/juror/matching_validation.go +++ b/precompile/contracts/juror/matching_validation.go @@ -434,15 +434,14 @@ func reducesPosition(positionSize *big.Int, baseAssetQuantity *big.Int) bool { } func getRequiredMargin(bibliophile b.BibliophileClient, order ILimitOrderBookOrder) *big.Int { - if false { // @todo find apt condition - return getRequiredMarginNew(bibliophile, order) + if false { // @todo find apt condition, maybe by block number? + return getRequiredMarginNew(bibliophile, order.BaseAssetQuantity, order.Price, order.AmmIndex.Int64(), &order.Trader) } return getRequiredMarginLegacy(bibliophile, order) } -func getRequiredMarginNew(bibliophile b.BibliophileClient, order ILimitOrderBookOrder) *big.Int { - // @todo implement - return big.NewInt(0) +func getRequiredMarginNew(bibliophile b.BibliophileClient, baseAsset *big.Int, price *big.Int, marketId int64, trader *common.Address) *big.Int { + return bibliophile.GetRequiredMargin(baseAsset, price, marketId, trader) } func getRequiredMarginLegacy(bibliophile b.BibliophileClient, order ILimitOrderBookOrder) *big.Int { From 971235e0f69cbe88aecf359fd1ef2847c1f16cae Mon Sep 17 00:00:00 2001 From: Atul Agarwal <21087753+asquare08@users.noreply.github.com> Date: Thu, 7 Mar 2024 22:03:02 +0530 Subject: [PATCH 05/14] add todo --- precompile/contracts/juror/contract.go | 1 + 1 file changed, 1 insertion(+) diff --git a/precompile/contracts/juror/contract.go b/precompile/contracts/juror/contract.go index 3957d34c66..9f6bf2ba20 100644 --- a/precompile/contracts/juror/contract.go +++ b/precompile/contracts/juror/contract.go @@ -475,6 +475,7 @@ func createJurorPrecompile() contract.StatefulPrecompiledContract { "validateOrdersAndDetermineFillPrice": validateOrdersAndDetermineFillPrice, "validatePlaceIOCOrder": validatePlaceIOCOrder, "validatePlaceLimitOrder": validatePlaceLimitOrder, + // @todo add getRequiredMargin } for name, function := range abiFunctionMap { From 272c266ec85ab238eb3ee55122808385e8fb732c Mon Sep 17 00:00:00 2001 From: Atul Agarwal <21087753+asquare08@users.noreply.github.com> Date: Mon, 11 Mar 2024 22:56:15 +0530 Subject: [PATCH 06/14] add tests GetNotionalPositionAndRequiredMargin --- .../orderbook/hubbleutils/margin_math_test.go | 56 +++++++++++++++++-- .../contracts/bibliophile/client_mock.go | 14 +++++ .../contracts/traderviewer/contract_test.go | 4 +- precompile/contracts/traderviewer/module.go | 2 +- 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/plugin/evm/orderbook/hubbleutils/margin_math_test.go b/plugin/evm/orderbook/hubbleutils/margin_math_test.go index cbbe9471a9..33e4342232 100644 --- a/plugin/evm/orderbook/hubbleutils/margin_math_test.go +++ b/plugin/evm/orderbook/hubbleutils/margin_math_test.go @@ -40,8 +40,8 @@ var _hState = &HubbleState{ var userState = &UserState{ Positions: map[Market]*Position{ 0: { - Size: big.NewInt(0.582 * 1e18), // 0.0582 - OpenNotional: big.NewInt(875 * 1e6), // 87.5, openPrice = 1503.43 + Size: big.NewInt(0.582 * 1e18), // 0.582 + OpenNotional: big.NewInt(875 * 1e6), // 875, openPrice = 1503.43 }, 1: { Size: Scale(big.NewInt(-101), 18), // -101 @@ -52,8 +52,18 @@ var userState = &UserState{ big.NewInt(30.5 * 1e6), // 30.5 big.NewInt(14 * 1e6), // 14 }, - PendingFunding: big.NewInt(0), - ReservedMargin: big.NewInt(0), + PendingFunding: big.NewInt(-50 * 1e6), // +50 + ReservedMargin: big.NewInt(60 * 1e6), // 60 + AccountPreferences: map[Market]*AccountPreferences{ + 0: { + MarginType: Cross_Margin, + MarginFraction: big.NewInt(0.2 * 1e6), // 0.2 + }, + 1: { + MarginType: Isolated_Margin, + MarginFraction: big.NewInt(0.1 * 1e6), // 0.1 + }, + }, } func TestWeightedAndSpotCollateral(t *testing.T) { @@ -238,3 +248,41 @@ func TestGetTotalNotionalPositionAndUnrealizedPnl(t *testing.T) { assert.Equal(t, expectedNotionalPosition, notionalPosition) assert.Equal(t, expectedUPnL, uPnL) } + +func TestGetNotionalPositionAndRequiredMargin(t *testing.T) { + t.Run("one market in cross and other in isolated mode", func(t *testing.T) { + expectedMargin := GetNormalizedMargin(_hState.Assets, userState.Margins) + fmt.Println(expectedMargin) + notionalPosition, margin, requiredMargin := GetNotionalPositionAndRequiredMargin(_hState, userState) + expectedNotionalPosition := Unscale(Mul(userState.Positions[0].Size, _hState.OraclePrices[0]), 18) + expectedUPnL := Sub(expectedNotionalPosition, userState.Positions[0].OpenNotional) + expectedMargin = Sub(Add(expectedMargin, expectedUPnL), userState.PendingFunding) + expectedRequiredMargin := Unscale(Mul(expectedNotionalPosition, userState.AccountPreferences[0].MarginFraction), 6) + assert.Equal(t, expectedNotionalPosition, notionalPosition) + assert.Equal(t, expectedMargin, margin) + assert.Equal(t, expectedRequiredMargin, requiredMargin) + }) + + t.Run("both markets in cross mode", func(t *testing.T) { + userState.AccountPreferences[1].MarginType = Cross_Margin + notionalPosition, margin, requiredMargin := GetNotionalPositionAndRequiredMargin(_hState, userState) + expectedNotionalPosition := big.NewInt(0) + expectedRequiredMargin := big.NewInt(0) + expectedMargin := GetNormalizedMargin(_hState.Assets, userState.Margins) + for _, market := range _hState.ActiveMarkets { + notional := Abs(Unscale(Mul(userState.Positions[market].Size, _hState.OraclePrices[market]), 18)) + expectedNotionalPosition = Add(expectedNotionalPosition, notional) + multiplier := big.NewInt(1) + if userState.Positions[market].Size.Sign() == -1 { + multiplier = big.NewInt(-1) + } + expectedUPnL := Mul(Sub(notional, userState.Positions[market].OpenNotional), multiplier) + expectedMargin = Add(expectedMargin, expectedUPnL) + expectedRequiredMargin = Add(expectedRequiredMargin, Unscale(Mul(notional, userState.AccountPreferences[market].MarginFraction), 6)) + } + expectedMargin = Sub(expectedMargin, userState.PendingFunding) + assert.Equal(t, expectedNotionalPosition, notionalPosition) + assert.Equal(t, expectedMargin, margin) + assert.Equal(t, expectedRequiredMargin, requiredMargin) + }) +} diff --git a/precompile/contracts/bibliophile/client_mock.go b/precompile/contracts/bibliophile/client_mock.go index 0f9c0dd2fd..7a17614482 100644 --- a/precompile/contracts/bibliophile/client_mock.go +++ b/precompile/contracts/bibliophile/client_mock.go @@ -380,6 +380,20 @@ func (mr *MockBibliophileClientMockRecorder) GetReduceOnlyAmount(trader, ammInde return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReduceOnlyAmount", reflect.TypeOf((*MockBibliophileClient)(nil).GetReduceOnlyAmount), trader, ammIndex) } +// GetRequiredMargin mocks base method. +func (m *MockBibliophileClient) GetRequiredMargin(baseAsset, price *big.Int, marketId int64, trader *common.Address) *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRequiredMargin", baseAsset, price, marketId, trader) + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// GetRequiredMargin indicates an expected call of GetRequiredMargin. +func (mr *MockBibliophileClientMockRecorder) GetRequiredMargin(baseAsset, price, marketId, trader interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRequiredMargin", reflect.TypeOf((*MockBibliophileClient)(nil).GetRequiredMargin), baseAsset, price, marketId, trader) +} + // GetShortOpenOrdersAmount mocks base method. func (m *MockBibliophileClient) GetShortOpenOrdersAmount(trader common.Address, ammIndex *big.Int) *big.Int { m.ctrl.T.Helper() diff --git a/precompile/contracts/traderviewer/contract_test.go b/precompile/contracts/traderviewer/contract_test.go index c8693e2514..00c03954ee 100644 --- a/precompile/contracts/traderviewer/contract_test.go +++ b/precompile/contracts/traderviewer/contract_test.go @@ -77,7 +77,9 @@ var ( InputFn: func(t testing.TB) []byte { // CUSTOM CODE STARTS HERE // populate test input here - testInput := GetTraderDataForMarketInput{} + testInput := GetTraderDataForMarketInput{ + AmmIndex: big.NewInt(0), + } input, err := PackGetTraderDataForMarket(testInput) require.NoError(t, err) return input diff --git a/precompile/contracts/traderviewer/module.go b/precompile/contracts/traderviewer/module.go index 7494049de8..c6f2273298 100644 --- a/precompile/contracts/traderviewer/module.go +++ b/precompile/contracts/traderviewer/module.go @@ -23,7 +23,7 @@ const ConfigKey = "traderViewerConfig" // ContractAddress is the defined address of the precompile contract. // This should be unique across all precompile contracts. // See precompile/registry/registry.go for registered precompile contracts and more information. -var ContractAddress = common.HexToAddress("{ASUITABLEHEXADDRESS}") // SET A SUITABLE HEX ADDRESS HERE +var ContractAddress = common.HexToAddress("0x03000000000000000000000000000000000000a3") // SET A SUITABLE HEX ADDRESS HERE // Module is the precompile module. It is used to register the precompile contract. var Module = modules.Module{ From c0e2869169fcf2ec4d5ede08e2c92b7056f20208 Mon Sep 17 00:00:00 2001 From: Atul Agarwal <21087753+asquare08@users.noreply.github.com> Date: Tue, 12 Mar 2024 01:19:56 +0530 Subject: [PATCH 07/14] opt --- precompile/contracts/bibliophile/amm.go | 4 ++-- .../traderviewer/{notional_position.go => viewer.go} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename precompile/contracts/traderviewer/{notional_position.go => viewer.go} (100%) diff --git a/precompile/contracts/bibliophile/amm.go b/precompile/contracts/bibliophile/amm.go index 9c8feb495c..32107b263c 100644 --- a/precompile/contracts/bibliophile/amm.go +++ b/precompile/contracts/bibliophile/amm.go @@ -203,13 +203,13 @@ func getTraderMarginFraction(stateDB contract.StateDB, market common.Address, tr } func getRequiredMargin(stateDB contract.StateDB, baseAsset *big.Int, price *big.Int, marketId int64, trader *common.Address) *big.Int { - quoteAsset := hu.Div1e18(hu.Mul(new(big.Int).Abs(baseAsset), price)) + quoteAsset := hu.Div1e18(hu.Mul(hu.Abs(baseAsset), price)) return getRequiredMarginForQuote(stateDB, GetMarketAddressFromMarketID(marketId, stateDB), trader, quoteAsset) } func getRequiredMarginForQuote(stateDB contract.StateDB, market common.Address, trader *common.Address, quote *big.Int) *big.Int { marginFraction := getTraderMarginFraction(stateDB, market, trader) - return hu.Div1e6(new(big.Int).Mul(quote, marginFraction)) + return hu.Div1e6(hu.Mul(quote, marginFraction)) } // Utils diff --git a/precompile/contracts/traderviewer/notional_position.go b/precompile/contracts/traderviewer/viewer.go similarity index 100% rename from precompile/contracts/traderviewer/notional_position.go rename to precompile/contracts/traderviewer/viewer.go From 85df6994eaa3da919b171e39db6b815e42ba0aa9 Mon Sep 17 00:00:00 2001 From: Atul Agarwal <21087753+asquare08@users.noreply.github.com> Date: Wed, 13 Mar 2024 17:42:24 +0530 Subject: [PATCH 08/14] add position cap check in juror --- precompile/contracts/bibliophile/amm.go | 12 +++++++++++ precompile/contracts/bibliophile/client.go | 10 +++++++++ .../contracts/bibliophile/limit_order_book.go | 6 ++++++ precompile/contracts/juror/ioc_orders.go | 11 +++++++++- precompile/contracts/juror/limit_orders.go | 21 +++++++++++++++++++ .../contracts/juror/matching_validation.go | 10 ++++++++- precompile/contracts/juror/module.go | 3 ++- 7 files changed, 70 insertions(+), 3 deletions(-) diff --git a/precompile/contracts/bibliophile/amm.go b/precompile/contracts/bibliophile/amm.go index 32107b263c..0c4441b63f 100644 --- a/precompile/contracts/bibliophile/amm.go +++ b/precompile/contracts/bibliophile/amm.go @@ -29,6 +29,7 @@ const ( ISOLATED_TRADE_MARGIN_FRACTION_SLOT int64 = 30 ISOLATED_LIQUIDATION_MARGIN_FRACTION_SLOT int64 = 31 ACCOUNT_PREFERENCES_SLOT int64 = 33 + MAX_POSITION_CAP_SLOT int64 = 34 ) // AMM State @@ -212,6 +213,17 @@ func getRequiredMarginForQuote(stateDB contract.StateDB, market common.Address, return hu.Div1e6(hu.Mul(quote, marginFraction)) } +func getMaxPositionCap(stateDB contract.StateDB, market common.Address) *big.Int { + return stateDB.GetState(market, common.BigToHash(big.NewInt(MAX_POSITION_CAP_SLOT))).Big() +} + +func getPositionCap(stateDB contract.StateDB, market int64, trader *common.Address) *big.Int { + marketAddress := GetMarketAddressFromMarketID(market, stateDB) + maxPositionCap := getMaxPositionCap(stateDB, marketAddress) + traderMarginFraction := getTraderMarginFraction(stateDB, marketAddress, trader) + return hu.Div1e6(hu.Mul(maxPositionCap, traderMarginFraction)) +} + // Utils func getPendingFundingPayment(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { diff --git a/precompile/contracts/bibliophile/client.go b/precompile/contracts/bibliophile/client.go index a69d3adc3d..d5ba8ca85b 100644 --- a/precompile/contracts/bibliophile/client.go +++ b/precompile/contracts/bibliophile/client.go @@ -27,6 +27,7 @@ type BibliophileClient interface { GetBlockPlaced(orderHash [32]byte) *big.Int GetOrderFilledAmount(orderHash [32]byte) *big.Int GetOrderStatus(orderHash [32]byte) int64 + GetPrecompileVersion(precompileAddress common.Address) *big.Int // IOC Order IOC_GetBlockPlaced(orderHash [32]byte) *big.Int @@ -51,6 +52,7 @@ type BibliophileClient interface { GetPriceMultiplier(market common.Address) *big.Int GetUpperAndLowerBoundForMarket(marketId int64) (*big.Int, *big.Int) GetAcceptableBoundsForLiquidation(marketId int64) (*big.Int, *big.Int) + GetPositionCap(marketId int64, trader common.Address) *big.Int GetTimeStamp() uint64 GetNotionalPositionAndMargin(trader common.Address, includeFundingPayments bool, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int) @@ -243,3 +245,11 @@ func (b *bibliophileClient) GetRequiredMargin(baseAsset *big.Int, price *big.Int func (b *bibliophileClient) HasReferrer(trader common.Address) bool { return HasReferrer(b.accessibleState.GetStateDB(), trader) } + +func (b *bibliophileClient) GetPositionCap(marketId int64, trader common.Address) *big.Int { + return getPositionCap(b.accessibleState.GetStateDB(), marketId, &trader) +} + +func (b *bibliophileClient) GetPrecompileVersion(precompileAddress common.Address) *big.Int { + return getPrecompileVersion(b.accessibleState.GetStateDB(), precompileAddress) +} diff --git a/precompile/contracts/bibliophile/limit_order_book.go b/precompile/contracts/bibliophile/limit_order_book.go index 52984b6bf5..be446096c1 100644 --- a/precompile/contracts/bibliophile/limit_order_book.go +++ b/precompile/contracts/bibliophile/limit_order_book.go @@ -14,6 +14,7 @@ const ( REDUCE_ONLY_AMOUNT_SLOT int64 = 2 LONG_OPEN_ORDERS_SLOT int64 = 4 SHORT_OPEN_ORDERS_SLOT int64 = 5 + PRECOMPILE_VERSION_SLOT int64 = 8 ) func getOrderFilledAmount(stateDB contract.StateDB, orderHash [32]byte) *big.Int { @@ -53,3 +54,8 @@ func getBlockPlaced(stateDB contract.StateDB, orderHash [32]byte) *big.Int { orderInfo := orderInfoMappingStorageSlot(orderHash) return new(big.Int).SetBytes(stateDB.GetState(common.HexToAddress(LIMIT_ORDERBOOK_GENESIS_ADDRESS), common.BigToHash(orderInfo)).Bytes()) } + +func getPrecompileVersion(stateDB contract.StateDB, precompileAddress common.Address) *big.Int { + slot := new(big.Int).SetBytes(crypto.Keccak256(append(common.LeftPadBytes(precompileAddress.Bytes(), 32), common.LeftPadBytes(big.NewInt(PRECOMPILE_VERSION_SLOT).Bytes(), 32)...))) + return stateDB.GetState(common.HexToAddress(LIMIT_ORDERBOOK_GENESIS_ADDRESS), common.BigToHash(slot)).Big() +} diff --git a/precompile/contracts/juror/ioc_orders.go b/precompile/contracts/juror/ioc_orders.go index 9958960665..5aa83eba54 100644 --- a/precompile/contracts/juror/ioc_orders.go +++ b/precompile/contracts/juror/ioc_orders.go @@ -67,8 +67,8 @@ func ValidatePlaceIOCorder(bibliophile b.BibliophileClient, inputStruct *Validat } // this check is sort of redundant because either ways user can circumvent this by placing several reduceOnly order in a single tx/block + posSize := bibliophile.GetSize(ammAddress, &trader) if order.ReduceOnly { - posSize := bibliophile.GetSize(ammAddress, &trader) // a reduce only order should reduce position if !reducesPosition(posSize, order.BaseAssetQuantity) { response.Err = ErrReduceOnlyBaseAssetQuantityInvalid.Error() @@ -91,6 +91,15 @@ func ValidatePlaceIOCorder(bibliophile b.BibliophileClient, inputStruct *Validat response.Err = ErrPricePrecision.Error() return } + + if bibliophile.GetPrecompileVersion(common.HexToAddress(SelfAddress)).Cmp(big.NewInt(1)) >= 0 { + posCap := bibliophile.GetPositionCap(order.AmmIndex.Int64(), trader) + if hu.Abs(hu.Add(posSize, order.BaseAssetQuantity)).Cmp(posCap) == 1 { + response.Err = ErrOverPositionCap.Error() + return + } + } + return response } diff --git a/precompile/contracts/juror/limit_orders.go b/precompile/contracts/juror/limit_orders.go index 1e82d55fa9..1840f44e8a 100644 --- a/precompile/contracts/juror/limit_orders.go +++ b/precompile/contracts/juror/limit_orders.go @@ -7,6 +7,7 @@ import ( hu "github.com/ava-labs/subnet-evm/plugin/evm/orderbook/hubbleutils" b "github.com/ava-labs/subnet-evm/precompile/contracts/bibliophile" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" ) func ValidatePlaceLimitOrder(bibliophile b.BibliophileClient, inputStruct *ValidatePlaceLimitOrderInput) (response ValidatePlaceLimitOrderOutput) { @@ -62,6 +63,13 @@ func ValidatePlaceLimitOrder(bibliophile b.BibliophileClient, inputStruct *Valid return } + if bibliophile.GetPrecompileVersion(common.HexToAddress(SelfAddress)).Cmp(big.NewInt(1)) >= 0 { + if isOverPositionCap(bibliophile, trader, order.AmmIndex, order.BaseAssetQuantity) { + response.Err = ErrOverPositionCap.Error() + return + } + } + var orderSide Side = Side(Long) if order.BaseAssetQuantity.Sign() == -1 { orderSide = Side(Short) @@ -181,3 +189,16 @@ func ILimitOrderBookOrderToLimitOrder(o *ILimitOrderBookOrder) *ob.LimitOrder { func GetLimitOrderHashFromContractStruct(o *ILimitOrderBookOrder) (common.Hash, error) { return ILimitOrderBookOrderToLimitOrder(o).Hash() } + +func isOverPositionCap(bibliophile b.BibliophileClient, trader common.Address, ammIndex *big.Int, baseAssetQuantity *big.Int) bool { + posSize := bibliophile.GetSize(bibliophile.GetMarketAddressFromMarketID(ammIndex.Int64()), &trader) + posCap := bibliophile.GetPositionCap(ammIndex.Int64(), trader) + longOpenOrdersAmount := bibliophile.GetLongOpenOrdersAmount(trader, ammIndex) + if baseAssetQuantity.Sign() == 1 { + posSize = math.BigMax(posSize, big.NewInt(0)) + return baseAssetQuantity.Cmp(hu.Sub(posCap, hu.Add(posSize, longOpenOrdersAmount))) == 1 + } + shortOpenOrdersAmount := bibliophile.GetShortOpenOrdersAmount(trader, ammIndex) + posSize = math.BigMax(hu.Neg(posSize), big.NewInt(0)) + return hu.Neg(baseAssetQuantity).Cmp(hu.Sub(posCap, hu.Add(posSize, shortOpenOrdersAmount))) == 1 +} diff --git a/precompile/contracts/juror/matching_validation.go b/precompile/contracts/juror/matching_validation.go index 401648d524..d7322c8c8d 100644 --- a/precompile/contracts/juror/matching_validation.go +++ b/precompile/contracts/juror/matching_validation.go @@ -71,6 +71,7 @@ var ( ErrOpenReduceOnlyOrders = errors.New("open reduce only orders") ErrNoTradingAuthority = errors.New("no trading authority") ErrNoReferrer = errors.New("no referrer") + ErrOverPositionCap = errors.New("position size over max cap") ) type BadElement uint8 @@ -379,6 +380,7 @@ func validateLimitOrderLike(bibliophile b.BibliophileClient, order *hu.BaseOrder } market := bibliophile.GetMarketAddressFromMarketID(order.AmmIndex.Int64()) + posSize := bibliophile.GetSize(market, &order.Trader) if side == Long { if order.BaseAssetQuantity.Sign() <= 0 { return ErrNotLongOrder @@ -390,7 +392,6 @@ func validateLimitOrderLike(bibliophile b.BibliophileClient, order *hu.BaseOrder return ErrOverFill } if order.ReduceOnly { - posSize := bibliophile.GetSize(market, &order.Trader) // posSize should be closed to continue to be Short // this also returns err if posSize >= 0, which should not happen because we are executing a long reduceOnly order on this account if new(big.Int).Add(posSize, fillAmount).Sign() > 0 { @@ -418,6 +419,13 @@ func validateLimitOrderLike(bibliophile b.BibliophileClient, order *hu.BaseOrder } else { return errors.New("invalid side") } + + if bibliophile.GetPrecompileVersion(common.HexToAddress(SelfAddress)).Cmp(big.NewInt(1)) >= 0 { + posCap := bibliophile.GetPositionCap(order.AmmIndex.Int64(), order.Trader) + if hu.Abs(hu.Add(posSize, order.BaseAssetQuantity)).Cmp(posCap) == 1 { + return ErrOverPositionCap + } + } return nil } diff --git a/precompile/contracts/juror/module.go b/precompile/contracts/juror/module.go index d65d2bbf7c..03d4d2276b 100644 --- a/precompile/contracts/juror/module.go +++ b/precompile/contracts/juror/module.go @@ -19,11 +19,12 @@ var _ contract.Configurator = &configurator{} // ConfigKey is the key used in json config files to specify this precompile precompileconfig. // must be unique across all precompiles. const ConfigKey = "jurorConfig" +const SelfAddress = "0x03000000000000000000000000000000000000a0" // ContractAddress is the defined address of the precompile contract. // This should be unique across all precompile contracts. // See precompile/registry/registry.go for registered precompile contracts and more information. -var ContractAddress = common.HexToAddress("0x03000000000000000000000000000000000000a0") // SET A SUITABLE HEX ADDRESS HERE +var ContractAddress = common.HexToAddress(SelfAddress) // SET A SUITABLE HEX ADDRESS HERE // Module is the precompile module. It is used to register the precompile contract. var Module = modules.Module{ From 761e62ad5bd48b2522614d23a203d3419cdea7f7 Mon Sep 17 00:00:00 2001 From: Atul Agarwal <21087753+asquare08@users.noreply.github.com> Date: Thu, 14 Mar 2024 18:18:48 +0530 Subject: [PATCH 09/14] fix getAvailableMargin, add ValidateCancelLimitOrderV2 --- .../hubble-v2/interfaces/ITraderViewer.sol | 5 + .../evm/orderbook/hubbleutils/margin_math.go | 1 + .../contracts/bibliophile/clearing_house.go | 3 +- precompile/contracts/bibliophile/client.go | 6 +- .../contracts/bibliophile/margin_account.go | 13 ++ .../contracts/juror/matching_validation.go | 6 +- .../contracts/traderviewer/contract.abi | 2 +- precompile/contracts/traderviewer/contract.go | 158 +++++++++++++++++- .../contracts/traderviewer/contract_test.go | 28 ++++ .../contracts/traderviewer/limit_orders.go | 89 ++++++++++ precompile/contracts/traderviewer/viewer.go | 2 +- 11 files changed, 302 insertions(+), 11 deletions(-) create mode 100644 precompile/contracts/traderviewer/limit_orders.go diff --git a/contracts/contracts/hubble-v2/interfaces/ITraderViewer.sol b/contracts/contracts/hubble-v2/interfaces/ITraderViewer.sol index a78c103be2..1b6e8384f7 100644 --- a/contracts/contracts/hubble-v2/interfaces/ITraderViewer.sol +++ b/contracts/contracts/hubble-v2/interfaces/ITraderViewer.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.0; import { IClearingHouse } from "./IClearingHouse.sol"; +import { ILimitOrderBook } from "./IJuror.sol"; +import { IOrderHandler } from "./IOrderHandler.sol"; interface ITraderViewer { function getNotionalPositionAndMargin(address trader, bool includeFundingPayments, IClearingHouse.Mode mode) external view returns(uint256 notionalPosition, int256 margin, uint256 requiredMargin); @@ -18,4 +20,7 @@ interface ITraderViewer { function getTotalFundingForCrossMarginPositions(address trader) external view returns(int256 totalFunding); + function validateCancelLimitOrderV2(ILimitOrderBook.Order memory order, address sender, bool assertLowMargin, bool assertOverPositionCap) external view returns (string memory err, bytes32 orderHash, IOrderHandler.CancelOrderRes memory res); + + function getRequiredMargin(int256 baseAssetQuantity, uint256 price, uint ammIndex, address trader) external view returns(uint256 requiredMargin); } diff --git a/plugin/evm/orderbook/hubbleutils/margin_math.go b/plugin/evm/orderbook/hubbleutils/margin_math.go index 7b611ef3e2..6551e379e2 100644 --- a/plugin/evm/orderbook/hubbleutils/margin_math.go +++ b/plugin/evm/orderbook/hubbleutils/margin_math.go @@ -41,6 +41,7 @@ func UpgradeVersionV0orV1(blockTimestamp uint64) UpgradeVersion { return V0 } +// @todo update to newer version wherever it is used in the evm func GetAvailableMargin(hState *HubbleState, userState *UserState) *big.Int { notionalPosition, margin := GetNotionalPositionAndMargin(hState, userState, Min_Allowable_Margin) return GetAvailableMargin_(notionalPosition, margin, userState.ReservedMargin, hState.MinAllowableMargin) diff --git a/precompile/contracts/bibliophile/clearing_house.go b/precompile/contracts/bibliophile/clearing_house.go index dc4d007c0f..8a78b2f9b7 100644 --- a/precompile/contracts/bibliophile/clearing_house.go +++ b/precompile/contracts/bibliophile/clearing_house.go @@ -113,7 +113,7 @@ func getNotionalPositionAndMargin(stateDB contract.StateDB, input *GetNotionalPo } } -func getNotionalPositionAndRequiredMargin(stateDB contract.StateDB, input *GetNotionalPositionAndMarginInput, upgradeVersion hu.UpgradeVersion) GetNotionalPositionAndMarginOutput { +func getNotionalPositionAndRequiredMargin(stateDB contract.StateDB, input *GetNotionalPositionAndMarginInput) GetNotionalPositionAndMarginOutput { positions, underlyingPrices, accountPreferences, activeMarketIds := getMarketsDataFromDB(stateDB, &input.Trader, input.Mode) pendingFunding := big.NewInt(0) if input.IncludeFundingPayments { @@ -124,7 +124,6 @@ func getNotionalPositionAndRequiredMargin(stateDB contract.StateDB, input *GetNo Assets: GetCollaterals(stateDB), OraclePrices: underlyingPrices, ActiveMarkets: activeMarketIds, - UpgradeVersion: upgradeVersion, }, &hu.UserState{ Positions: positions, diff --git a/precompile/contracts/bibliophile/client.go b/precompile/contracts/bibliophile/client.go index d5ba8ca85b..ae2b38bab0 100644 --- a/precompile/contracts/bibliophile/client.go +++ b/precompile/contracts/bibliophile/client.go @@ -56,7 +56,7 @@ type BibliophileClient interface { GetTimeStamp() uint64 GetNotionalPositionAndMargin(trader common.Address, includeFundingPayments bool, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int) - GetNotionalPositionAndRequiredMargin(trader common.Address, includeFundingPayments bool, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int, *big.Int) + GetNotionalPositionAndRequiredMargin(trader common.Address, includeFundingPayments bool, mode uint8) (*big.Int, *big.Int, *big.Int) GetCrossMarginAccountData(trader common.Address, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int, *big.Int, *big.Int) GetTotalFundingForCrossMarginPositions(trader *common.Address) *big.Int GetTraderDataForMarket(trader common.Address, marketId int64, mode uint8) (bool, *big.Int, *big.Int, *big.Int, *big.Int) @@ -219,8 +219,8 @@ func (b *bibliophileClient) GetNotionalPositionAndMargin(trader common.Address, return output.NotionalPosition, output.Margin } -func (b *bibliophileClient) GetNotionalPositionAndRequiredMargin(trader common.Address, includeFundingPayments bool, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int, *big.Int) { - output := getNotionalPositionAndRequiredMargin(b.accessibleState.GetStateDB(), &GetNotionalPositionAndMarginInput{Trader: trader, IncludeFundingPayments: includeFundingPayments, Mode: mode}, upgradeVersion) +func (b *bibliophileClient) GetNotionalPositionAndRequiredMargin(trader common.Address, includeFundingPayments bool, mode uint8) (*big.Int, *big.Int, *big.Int) { + output := getNotionalPositionAndRequiredMargin(b.accessibleState.GetStateDB(), &GetNotionalPositionAndMarginInput{Trader: trader, IncludeFundingPayments: includeFundingPayments, Mode: mode}) return output.NotionalPosition, output.Margin, output.RequiredMargin } diff --git a/precompile/contracts/bibliophile/margin_account.go b/precompile/contracts/bibliophile/margin_account.go index 9784662d03..e74cb1a513 100644 --- a/precompile/contracts/bibliophile/margin_account.go +++ b/precompile/contracts/bibliophile/margin_account.go @@ -12,6 +12,7 @@ import ( const ( MARGIN_ACCOUNT_GENESIS_ADDRESS = "0x03000000000000000000000000000000000000b1" + JUROR_ADDRESS = "0x03000000000000000000000000000000000000a0" ORACLE_SLOT int64 = 4 SUPPORTED_COLLATERAL_SLOT int64 = 8 MARGIN_MAPPING_SLOT int64 = 10 @@ -45,10 +46,22 @@ func getReservedMargin(stateDB contract.StateDB, trader common.Address) *big.Int } func GetAvailableMargin(stateDB contract.StateDB, trader common.Address, upgradeVersion hu.UpgradeVersion) *big.Int { + if getPrecompileVersion(stateDB, common.HexToAddress(JUROR_ADDRESS)).Cmp(big.NewInt(0)) == 0 { + return GetAvailableMarginLegacy(stateDB, trader, upgradeVersion) + } + return GetAvailableMarginV2(stateDB, trader) +} + +func GetAvailableMarginLegacy(stateDB contract.StateDB, trader common.Address, upgradeVersion hu.UpgradeVersion) *big.Int { output := getNotionalPositionAndMargin(stateDB, &GetNotionalPositionAndMarginInput{Trader: trader, IncludeFundingPayments: true, Mode: uint8(1)}, upgradeVersion) // Min_Allowable_Margin return hu.GetAvailableMargin_(output.NotionalPosition, output.Margin, getReservedMargin(stateDB, trader), GetMinAllowableMargin(stateDB)) } +func GetAvailableMarginV2(stateDB contract.StateDB, trader common.Address) *big.Int { + output := getNotionalPositionAndRequiredMargin(stateDB, &GetNotionalPositionAndMarginInput{Trader: trader, IncludeFundingPayments: true, Mode: uint8(1)}) + return hu.Sub(output.Margin, hu.Add(output.RequiredMargin, getReservedMargin(stateDB, trader))) +} + func getOracleAddress(stateDB contract.StateDB) common.Address { return common.BytesToAddress(stateDB.GetState(common.HexToAddress(MARGIN_ACCOUNT_GENESIS_ADDRESS), common.BigToHash(big.NewInt(ORACLE_SLOT))).Bytes()) } diff --git a/precompile/contracts/juror/matching_validation.go b/precompile/contracts/juror/matching_validation.go index d7322c8c8d..e6d1005227 100644 --- a/precompile/contracts/juror/matching_validation.go +++ b/precompile/contracts/juror/matching_validation.go @@ -442,10 +442,10 @@ func reducesPosition(positionSize *big.Int, baseAssetQuantity *big.Int) bool { } func getRequiredMargin(bibliophile b.BibliophileClient, order ILimitOrderBookOrder) *big.Int { - if false { // @todo find apt condition, maybe by block number? - return getRequiredMarginNew(bibliophile, order.BaseAssetQuantity, order.Price, order.AmmIndex.Int64(), &order.Trader) + if bibliophile.GetPrecompileVersion(common.HexToAddress(SelfAddress)).Cmp(big.NewInt(0)) == 0 { + return getRequiredMarginLegacy(bibliophile, order) } - return getRequiredMarginLegacy(bibliophile, order) + return getRequiredMarginNew(bibliophile, order.BaseAssetQuantity, order.Price, order.AmmIndex.Int64(), &order.Trader) } func getRequiredMarginNew(bibliophile b.BibliophileClient, baseAsset *big.Int, price *big.Int, marketId int64, trader *common.Address) *big.Int { diff --git a/precompile/contracts/traderviewer/contract.abi b/precompile/contracts/traderviewer/contract.abi index 1df78f423e..e441255f06 100644 --- a/precompile/contracts/traderviewer/contract.abi +++ b/precompile/contracts/traderviewer/contract.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"enum IClearingHouse.Mode","name":"mode","type":"uint8"}],"name":"getCrossMarginAccountData","outputs":[{"internalType":"uint256","name":"notionalPosition","type":"uint256"},{"internalType":"uint256","name":"requiredMargin","type":"uint256"},{"internalType":"int256","name":"unrealizedPnl","type":"int256"},{"internalType":"int256","name":"pendingFunding","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"bool","name":"includeFundingPayments","type":"bool"},{"internalType":"enum IClearingHouse.Mode","name":"mode","type":"uint8"}],"name":"getNotionalPositionAndMargin","outputs":[{"internalType":"uint256","name":"notionalPosition","type":"uint256"},{"internalType":"int256","name":"margin","type":"int256"},{"internalType":"uint256","name":"requiredMargin","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"}],"name":"getTotalFundingForCrossMarginPositions","outputs":[{"internalType":"int256","name":"totalFunding","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"uint256","name":"ammIndex","type":"uint256"},{"internalType":"enum IClearingHouse.Mode","name":"mode","type":"uint8"}],"name":"getTraderDataForMarket","outputs":[{"internalType":"bool","name":"isIsolated","type":"bool"},{"internalType":"uint256","name":"notionalPosition","type":"uint256"},{"internalType":"int256","name":"unrealizedPnl","type":"int256"},{"internalType":"uint256","name":"requiredMargin","type":"uint256"},{"internalType":"int256","name":"pendingFunding","type":"int256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"enum IClearingHouse.Mode","name":"mode","type":"uint8"}],"name":"getCrossMarginAccountData","outputs":[{"internalType":"uint256","name":"notionalPosition","type":"uint256"},{"internalType":"uint256","name":"requiredMargin","type":"uint256"},{"internalType":"int256","name":"unrealizedPnl","type":"int256"},{"internalType":"int256","name":"pendingFunding","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"bool","name":"includeFundingPayments","type":"bool"},{"internalType":"enum IClearingHouse.Mode","name":"mode","type":"uint8"}],"name":"getNotionalPositionAndMargin","outputs":[{"internalType":"uint256","name":"notionalPosition","type":"uint256"},{"internalType":"int256","name":"margin","type":"int256"},{"internalType":"uint256","name":"requiredMargin","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"baseAssetQuantity","type":"int256"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"ammIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"}],"name":"getRequiredMargin","outputs":[{"internalType":"uint256","name":"requiredMargin","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"}],"name":"getTotalFundingForCrossMarginPositions","outputs":[{"internalType":"int256","name":"totalFunding","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"uint256","name":"ammIndex","type":"uint256"},{"internalType":"enum IClearingHouse.Mode","name":"mode","type":"uint8"}],"name":"getTraderDataForMarket","outputs":[{"internalType":"bool","name":"isIsolated","type":"bool"},{"internalType":"uint256","name":"notionalPosition","type":"uint256"},{"internalType":"int256","name":"unrealizedPnl","type":"int256"},{"internalType":"uint256","name":"requiredMargin","type":"uint256"},{"internalType":"int256","name":"pendingFunding","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"ammIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"},{"internalType":"int256","name":"baseAssetQuantity","type":"int256"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"bool","name":"reduceOnly","type":"bool"},{"internalType":"bool","name":"postOnly","type":"bool"}],"internalType":"struct ILimitOrderBook.Order","name":"order","type":"tuple"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bool","name":"assertLowMargin","type":"bool"},{"internalType":"bool","name":"assertOverPositionCap","type":"bool"}],"name":"validateCancelLimitOrderV2","outputs":[{"internalType":"string","name":"err","type":"string"},{"internalType":"bytes32","name":"orderHash","type":"bytes32"},{"components":[{"internalType":"int256","name":"unfilledAmount","type":"int256"},{"internalType":"address","name":"amm","type":"address"}],"internalType":"struct IOrderHandler.CancelOrderRes","name":"res","type":"tuple"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompile/contracts/traderviewer/contract.go b/precompile/contracts/traderviewer/contract.go index 6244ef7315..259c0e88bd 100644 --- a/precompile/contracts/traderviewer/contract.go +++ b/precompile/contracts/traderviewer/contract.go @@ -12,11 +12,11 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ava-labs/subnet-evm/precompile/contracts/bibliophile" _ "embed" "github.com/ethereum/go-ethereum/common" + "github.com/ava-labs/subnet-evm/precompile/contracts/bibliophile" ) const ( @@ -26,8 +26,10 @@ const ( // There are some predefined gas costs in contract/utils.go that you can use. GetCrossMarginAccountDataGasCost uint64 = 1 /* SET A GAS COST HERE */ GetNotionalPositionAndMarginGasCost uint64 = 1 /* SET A GAS COST HERE */ + GetRequiredMarginGasCost uint64 = 1 /* SET A GAS COST HERE */ GetTotalFundingForCrossMarginPositionsGasCost uint64 = 1 /* SET A GAS COST HERE */ GetTraderDataForMarketGasCost uint64 = 1 /* SET A GAS COST HERE */ + ValidateCancelLimitOrderV2GasCost uint64 = 1 /* SET A GAS COST HERE */ ) // CUSTOM CODE STARTS HERE @@ -52,6 +54,23 @@ var ( TraderViewerPrecompile = createTraderViewerPrecompile() ) +// ILimitOrderBookOrder is an auto generated low-level Go binding around an user-defined struct. +type ILimitOrderBookOrder struct { + AmmIndex *big.Int + Trader common.Address + BaseAssetQuantity *big.Int + Price *big.Int + Salt *big.Int + ReduceOnly bool + PostOnly bool +} + +// IOrderHandlerCancelOrderRes is an auto generated low-level Go binding around an user-defined struct. +type IOrderHandlerCancelOrderRes struct { + UnfilledAmount *big.Int + Amm common.Address +} + type GetCrossMarginAccountDataInput struct { Trader common.Address Mode uint8 @@ -76,6 +95,13 @@ type GetNotionalPositionAndMarginOutput struct { RequiredMargin *big.Int } +type GetRequiredMarginInput struct { + BaseAssetQuantity *big.Int + Price *big.Int + AmmIndex *big.Int + Trader common.Address +} + type GetTraderDataForMarketInput struct { Trader common.Address AmmIndex *big.Int @@ -90,6 +116,19 @@ type GetTraderDataForMarketOutput struct { PendingFunding *big.Int } +type ValidateCancelLimitOrderV2Input struct { + Order ILimitOrderBookOrder + Sender common.Address + AssertLowMargin bool + AssertOverPositionCap bool +} + +type ValidateCancelLimitOrderV2Output struct { + Err string + OrderHash [32]byte + Res IOrderHandlerCancelOrderRes +} + // UnpackGetCrossMarginAccountDataInput attempts to unpack [input] as GetCrossMarginAccountDataInput // assumes that [input] does not include selector (omits first 4 func signature bytes) func UnpackGetCrossMarginAccountDataInput(input []byte) (GetCrossMarginAccountDataInput, error) { @@ -207,6 +246,63 @@ func getNotionalPositionAndMargin(accessibleState contract.AccessibleState, call return packedOutput, remainingGas, nil } +// UnpackGetRequiredMarginInput attempts to unpack [input] as GetRequiredMarginInput +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackGetRequiredMarginInput(input []byte) (GetRequiredMarginInput, error) { + inputStruct := GetRequiredMarginInput{} + // The strict mode in decoding is disabled after Durango. You can re-enable by changing the last argument to true. + err := TraderViewerABI.UnpackInputIntoInterface(&inputStruct, "getRequiredMargin", input, false) + + return inputStruct, err +} + +// PackGetRequiredMargin packs [inputStruct] of type GetRequiredMarginInput into the appropriate arguments for getRequiredMargin. +func PackGetRequiredMargin(inputStruct GetRequiredMarginInput) ([]byte, error) { + return TraderViewerABI.Pack("getRequiredMargin", inputStruct.BaseAssetQuantity, inputStruct.Price, inputStruct.AmmIndex, inputStruct.Trader) +} + +// PackGetRequiredMarginOutput attempts to pack given requiredMargin of type *big.Int +// to conform the ABI outputs. +func PackGetRequiredMarginOutput(requiredMargin *big.Int) ([]byte, error) { + return TraderViewerABI.PackOutput("getRequiredMargin", requiredMargin) +} + +// UnpackGetRequiredMarginOutput attempts to unpack given [output] into the *big.Int type output +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackGetRequiredMarginOutput(output []byte) (*big.Int, error) { + res, err := TraderViewerABI.Unpack("getRequiredMargin", output) + if err != nil { + return new(big.Int), err + } + unpacked := *abi.ConvertType(res[0], new(*big.Int)).(**big.Int) + return unpacked, nil +} + +func getRequiredMargin(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, GetRequiredMarginGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the GetRequiredMarginInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackGetRequiredMarginInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + + var output *big.Int // CUSTOM CODE FOR AN OUTPUT + packedOutput, err := PackGetRequiredMarginOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + // UnpackGetTotalFundingForCrossMarginPositionsInput attempts to unpack [input] into the common.Address type argument // assumes that [input] does not include selector (omits first 4 func signature bytes) func UnpackGetTotalFundingForCrossMarginPositionsInput(input []byte) (common.Address, error) { @@ -327,6 +423,64 @@ func getTraderDataForMarket(accessibleState contract.AccessibleState, caller com return packedOutput, remainingGas, nil } +// UnpackValidateCancelLimitOrderV2Input attempts to unpack [input] as ValidateCancelLimitOrderV2Input +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackValidateCancelLimitOrderV2Input(input []byte) (ValidateCancelLimitOrderV2Input, error) { + inputStruct := ValidateCancelLimitOrderV2Input{} + // The strict mode in decoding is disabled after Durango. You can re-enable by changing the last argument to true. + err := TraderViewerABI.UnpackInputIntoInterface(&inputStruct, "validateCancelLimitOrderV2", input, false) + + return inputStruct, err +} + +// PackValidateCancelLimitOrderV2 packs [inputStruct] of type ValidateCancelLimitOrderV2Input into the appropriate arguments for validateCancelLimitOrderV2. +func PackValidateCancelLimitOrderV2(inputStruct ValidateCancelLimitOrderV2Input) ([]byte, error) { + return TraderViewerABI.Pack("validateCancelLimitOrderV2", inputStruct.Order, inputStruct.Sender, inputStruct.AssertLowMargin, inputStruct.AssertOverPositionCap) +} + +// PackValidateCancelLimitOrderV2Output attempts to pack given [outputStruct] of type ValidateCancelLimitOrderV2Output +// to conform the ABI outputs. +func PackValidateCancelLimitOrderV2Output(outputStruct ValidateCancelLimitOrderV2Output) ([]byte, error) { + return TraderViewerABI.PackOutput("validateCancelLimitOrderV2", + outputStruct.Err, + outputStruct.OrderHash, + outputStruct.Res, + ) +} + +// UnpackValidateCancelLimitOrderV2Output attempts to unpack [output] as ValidateCancelLimitOrderV2Output +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackValidateCancelLimitOrderV2Output(output []byte) (ValidateCancelLimitOrderV2Output, error) { + outputStruct := ValidateCancelLimitOrderV2Output{} + err := TraderViewerABI.UnpackIntoInterface(&outputStruct, "validateCancelLimitOrderV2", output) + + return outputStruct, err +} + +func validateCancelLimitOrderV2(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, ValidateCancelLimitOrderV2GasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the ValidateCancelLimitOrderV2Input. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackValidateCancelLimitOrderV2Input(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output ValidateCancelLimitOrderV2Output // CUSTOM CODE FOR AN OUTPUT + packedOutput, err := PackValidateCancelLimitOrderV2Output(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + // createTraderViewerPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. func createTraderViewerPrecompile() contract.StatefulPrecompiledContract { @@ -335,8 +489,10 @@ func createTraderViewerPrecompile() contract.StatefulPrecompiledContract { abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ "getCrossMarginAccountData": getCrossMarginAccountData, "getNotionalPositionAndMargin": getNotionalPositionAndMargin, + "getRequiredMargin": getRequiredMargin, "getTotalFundingForCrossMarginPositions": getTotalFundingForCrossMarginPositions, "getTraderDataForMarket": getTraderDataForMarket, + "validateCancelLimitOrderV2": validateCancelLimitOrderV2, } for name, function := range abiFunctionMap { diff --git a/precompile/contracts/traderviewer/contract_test.go b/precompile/contracts/traderviewer/contract_test.go index 00c03954ee..5cfa8d4fb0 100644 --- a/precompile/contracts/traderviewer/contract_test.go +++ b/precompile/contracts/traderviewer/contract_test.go @@ -57,6 +57,20 @@ var ( ReadOnly: false, ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, + "insufficient gas for getRequiredMargin should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // populate test input here + testInput := GetRequiredMarginInput{} + input, err := PackGetRequiredMargin(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: GetRequiredMarginGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, "insufficient gas for getTotalFundingForCrossMarginPositions should fail": { Caller: common.Address{1}, InputFn: func(t testing.TB) []byte { @@ -88,6 +102,20 @@ var ( ReadOnly: false, ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, + "insufficient gas for validateCancelLimitOrderV2 should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // populate test input here + testInput := ValidateCancelLimitOrderV2Input{} + input, err := PackValidateCancelLimitOrderV2(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: ValidateCancelLimitOrderV2GasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, } ) diff --git a/precompile/contracts/traderviewer/limit_orders.go b/precompile/contracts/traderviewer/limit_orders.go new file mode 100644 index 0000000000..584e2304f9 --- /dev/null +++ b/precompile/contracts/traderviewer/limit_orders.go @@ -0,0 +1,89 @@ +package traderviewer + +import ( + "errors" + "math/big" + + ob "github.com/ava-labs/subnet-evm/plugin/evm/orderbook" + hu "github.com/ava-labs/subnet-evm/plugin/evm/orderbook/hubbleutils" + b "github.com/ava-labs/subnet-evm/precompile/contracts/bibliophile" + juror "github.com/ava-labs/subnet-evm/precompile/contracts/juror" + "github.com/ethereum/go-ethereum/common" +) + +var ( + ErrUnauthorizedCancellation = errors.New("unauthorized cancellation") + ErrNotOverPositionCap = errors.New("not over position cap") +) + +func ValidateCancelLimitOrderV2(bibliophile b.BibliophileClient, inputStruct *ValidateCancelLimitOrderV2Input) (response ValidateCancelLimitOrderV2Output) { + order := inputStruct.Order + sender := inputStruct.Sender + assertLowMargin := inputStruct.AssertLowMargin + assertOverPositionCap := inputStruct.AssertOverPositionCap + + response.Res.UnfilledAmount = big.NewInt(0) + + trader := order.Trader + if (!assertLowMargin && trader != sender && !bibliophile.IsTradingAuthority(trader, sender)) || + (assertLowMargin && !bibliophile.IsValidator(sender)) || + (assertOverPositionCap && !bibliophile.IsValidator(sender)) { + response.Err = ErrUnauthorizedCancellation.Error() + return + } + orderHash, err := GetLimitOrderHashFromContractStruct(&order) + response.OrderHash = orderHash + if err != nil { + response.Err = err.Error() + return + } + switch status := juror.OrderStatus(bibliophile.GetOrderStatus(orderHash)); status { + case juror.Invalid: + response.Err = "Invalid" + return + case juror.Filled: + response.Err = "Filled" + return + case juror.Cancelled: + response.Err = "Cancelled" + return + default: + } + response.Res.UnfilledAmount = big.NewInt(0).Sub(order.BaseAssetQuantity, bibliophile.GetOrderFilledAmount(orderHash)) + if assertLowMargin && bibliophile.GetAvailableMargin(trader, hu.UpgradeVersionV0orV1(bibliophile.GetTimeStamp())).Sign() != -1 { + response.Err = "Not Low Margin" + return + } else if assertOverPositionCap { + ammAddress := bibliophile.GetMarketAddressFromMarketID(order.AmmIndex.Int64()) + posSize := bibliophile.GetSize(ammAddress, &trader) + if hu.Abs(hu.Add(posSize, response.Res.UnfilledAmount)).Cmp(bibliophile.GetPositionCap(order.AmmIndex.Int64(), trader)) <= 0 { + response.Err = ErrNotOverPositionCap.Error() + return + } + } + response.Res.Amm = bibliophile.GetMarketAddressFromMarketID(order.AmmIndex.Int64()) + + return response +} + +func ILimitOrderBookOrderToLimitOrder(o *ILimitOrderBookOrder) *ob.LimitOrder { + return &ob.LimitOrder{ + BaseOrder: hu.BaseOrder{ + AmmIndex: o.AmmIndex, + Trader: o.Trader, + BaseAssetQuantity: o.BaseAssetQuantity, + Price: o.Price, + Salt: o.Salt, + ReduceOnly: o.ReduceOnly, + }, + PostOnly: o.PostOnly, + } +} + +func GetLimitOrderHashFromContractStruct(o *ILimitOrderBookOrder) (common.Hash, error) { + return ILimitOrderBookOrderToLimitOrder(o).Hash() +} + +func GetRequiredMargin(bibliophile b.BibliophileClient, baseAsset *big.Int, price *big.Int, marketId int64, trader *common.Address) *big.Int { + return bibliophile.GetRequiredMargin(baseAsset, price, marketId, trader) +} diff --git a/precompile/contracts/traderviewer/viewer.go b/precompile/contracts/traderviewer/viewer.go index 6cc9d00ccb..7c264bc0d5 100644 --- a/precompile/contracts/traderviewer/viewer.go +++ b/precompile/contracts/traderviewer/viewer.go @@ -8,7 +8,7 @@ import ( ) func GetNotionalPositionAndMargin(bibliophile b.BibliophileClient, input *GetNotionalPositionAndMarginInput) GetNotionalPositionAndMarginOutput { - notionalPosition, margin, requiredMargin := bibliophile.GetNotionalPositionAndRequiredMargin(input.Trader, input.IncludeFundingPayments, input.Mode, hu.V2) // @todo check if this is the right upgrade version + notionalPosition, margin, requiredMargin := bibliophile.GetNotionalPositionAndRequiredMargin(input.Trader, input.IncludeFundingPayments, input.Mode) return GetNotionalPositionAndMarginOutput{ NotionalPosition: notionalPosition, Margin: margin, From 243fe54e69f793dda5c26c9e1eac14575180e7bd Mon Sep 17 00:00:00 2001 From: Atul Agarwal <21087753+asquare08@users.noreply.github.com> Date: Mon, 18 Mar 2024 00:58:39 +0530 Subject: [PATCH 10/14] add test for ValidateCancelLimitOrderV2 --- .../contracts/bibliophile/client_mock.go | 36 +- precompile/contracts/juror/ioc_orders_test.go | 4 + .../contracts/juror/limit_orders_test.go | 1 + .../contracts/juror/matching_validation.go | 1 - .../juror/matching_validation_test.go | 48 ++- precompile/contracts/traderviewer/contract.go | 3 +- .../contracts/traderviewer/contract_test.go | 20 +- .../contracts/traderviewer/limit_orders.go | 6 +- .../traderviewer/limit_orders_test.go | 385 ++++++++++++++++++ 9 files changed, 485 insertions(+), 19 deletions(-) create mode 100644 precompile/contracts/traderviewer/limit_orders_test.go diff --git a/precompile/contracts/bibliophile/client_mock.go b/precompile/contracts/bibliophile/client_mock.go index 7a17614482..2a5f0d0007 100644 --- a/precompile/contracts/bibliophile/client_mock.go +++ b/precompile/contracts/bibliophile/client_mock.go @@ -309,9 +309,9 @@ func (mr *MockBibliophileClientMockRecorder) GetNotionalPositionAndMargin(trader } // GetNotionalPositionAndRequiredMargin mocks base method. -func (m *MockBibliophileClient) GetNotionalPositionAndRequiredMargin(trader common.Address, includeFundingPayments bool, mode uint8, upgradeVersion hubbleutils.UpgradeVersion) (*big.Int, *big.Int, *big.Int) { +func (m *MockBibliophileClient) GetNotionalPositionAndRequiredMargin(trader common.Address, includeFundingPayments bool, mode uint8) (*big.Int, *big.Int, *big.Int) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetNotionalPositionAndRequiredMargin", trader, includeFundingPayments, mode, upgradeVersion) + ret := m.ctrl.Call(m, "GetNotionalPositionAndRequiredMargin", trader, includeFundingPayments, mode) ret0, _ := ret[0].(*big.Int) ret1, _ := ret[1].(*big.Int) ret2, _ := ret[2].(*big.Int) @@ -319,9 +319,9 @@ func (m *MockBibliophileClient) GetNotionalPositionAndRequiredMargin(trader comm } // GetNotionalPositionAndRequiredMargin indicates an expected call of GetNotionalPositionAndRequiredMargin. -func (mr *MockBibliophileClientMockRecorder) GetNotionalPositionAndRequiredMargin(trader, includeFundingPayments, mode, upgradeVersion interface{}) *gomock.Call { +func (mr *MockBibliophileClientMockRecorder) GetNotionalPositionAndRequiredMargin(trader, includeFundingPayments, mode interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotionalPositionAndRequiredMargin", reflect.TypeOf((*MockBibliophileClient)(nil).GetNotionalPositionAndRequiredMargin), trader, includeFundingPayments, mode, upgradeVersion) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotionalPositionAndRequiredMargin", reflect.TypeOf((*MockBibliophileClient)(nil).GetNotionalPositionAndRequiredMargin), trader, includeFundingPayments, mode) } // GetOrderFilledAmount mocks base method. @@ -352,6 +352,34 @@ func (mr *MockBibliophileClientMockRecorder) GetOrderStatus(orderHash interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOrderStatus", reflect.TypeOf((*MockBibliophileClient)(nil).GetOrderStatus), orderHash) } +// GetPositionCap mocks base method. +func (m *MockBibliophileClient) GetPositionCap(marketId int64, trader common.Address) *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPositionCap", marketId, trader) + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// GetPositionCap indicates an expected call of GetPositionCap. +func (mr *MockBibliophileClientMockRecorder) GetPositionCap(marketId, trader interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPositionCap", reflect.TypeOf((*MockBibliophileClient)(nil).GetPositionCap), marketId, trader) +} + +// GetPrecompileVersion mocks base method. +func (m *MockBibliophileClient) GetPrecompileVersion(precompileAddress common.Address) *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPrecompileVersion", precompileAddress) + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// GetPrecompileVersion indicates an expected call of GetPrecompileVersion. +func (mr *MockBibliophileClientMockRecorder) GetPrecompileVersion(precompileAddress interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrecompileVersion", reflect.TypeOf((*MockBibliophileClient)(nil).GetPrecompileVersion), precompileAddress) +} + // GetPriceMultiplier mocks base method. func (m *MockBibliophileClient) GetPriceMultiplier(market common.Address) *big.Int { m.ctrl.T.Helper() diff --git a/precompile/contracts/juror/ioc_orders_test.go b/precompile/contracts/juror/ioc_orders_test.go index c73f0f5e6a..a772e1c39d 100644 --- a/precompile/contracts/juror/ioc_orders_test.go +++ b/precompile/contracts/juror/ioc_orders_test.go @@ -408,6 +408,7 @@ func TestValidatePlaceIOCOrder(t *testing.T) { mockBibliophile.EXPECT().GetSize(common.Address{101}, &trader).Return(big.NewInt(-15)) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, order.AmmIndex).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetPriceMultiplier(common.Address{101}).Return(big.NewInt(10)) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) testValidatePlaceIOCOrderTestCase(t, mockBibliophile, ValidatePlaceIOCOrderTestCase{ Order: order, @@ -489,6 +490,8 @@ func TestValidateExecuteIOCOrder(t *testing.T) { mockBibliophile.EXPECT().IOC_GetOrderFilledAmount(hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().IOC_GetOrderStatus(hash).Return(int64(1)) mockBibliophile.EXPECT().IOC_GetBlockPlaced(hash).Return(big.NewInt(21)) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) + mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(big.NewInt(0)) m, err := validateExecuteIOCOrder(mockBibliophile, &order, Long, big.NewInt(10)) assert.Nil(t, err) @@ -525,6 +528,7 @@ func TestValidateExecuteIOCOrder(t *testing.T) { mockBibliophile.EXPECT().IOC_GetOrderFilledAmount(hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().IOC_GetOrderStatus(hash).Return(int64(1)) mockBibliophile.EXPECT().IOC_GetBlockPlaced(hash).Return(big.NewInt(21)) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) _, err := validateExecuteIOCOrder(mockBibliophile, &order, Long, big.NewInt(10)) assert.Nil(t, err) diff --git a/precompile/contracts/juror/limit_orders_test.go b/precompile/contracts/juror/limit_orders_test.go index b4ed7e2d67..2fcbfe512c 100644 --- a/precompile/contracts/juror/limit_orders_test.go +++ b/precompile/contracts/juror/limit_orders_test.go @@ -248,6 +248,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Invalid)).Times(1) mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).AnyTimes() output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, ErrReduceOnlyBaseAssetQuantityInvalid.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) diff --git a/precompile/contracts/juror/matching_validation.go b/precompile/contracts/juror/matching_validation.go index e6d1005227..d12dbbf40e 100644 --- a/precompile/contracts/juror/matching_validation.go +++ b/precompile/contracts/juror/matching_validation.go @@ -409,7 +409,6 @@ func validateLimitOrderLike(bibliophile b.BibliophileClient, order *hu.BaseOrder return ErrOverFill } if order.ReduceOnly { - posSize := bibliophile.GetSize(market, &order.Trader) // posSize should continue to be Long // this also returns is posSize <= 0, which should not happen because we are executing a short reduceOnly order on this account if new(big.Int).Add(posSize, fillAmount).Sign() < 0 { diff --git a/precompile/contracts/juror/matching_validation_test.go b/precompile/contracts/juror/matching_validation_test.go index 3abd025bd0..b0794fd936 100644 --- a/precompile/contracts/juror/matching_validation_test.go +++ b/precompile/contracts/juror/matching_validation_test.go @@ -53,6 +53,7 @@ func TestValidateLimitOrderLike(t *testing.T) { t.Run("base asset quantity <= 0", func(t *testing.T) { badOrder := *order badOrder.BaseAssetQuantity = big.NewInt(-23) + mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(2) err := validateLimitOrderLike(mockBibliophile, &badOrder, filledAmount, Placed, Long, fillAmount) assert.EqualError(t, err, ErrNotLongOrder.Error()) @@ -65,6 +66,7 @@ func TestValidateLimitOrderLike(t *testing.T) { t.Run("ErrOverFill", func(t *testing.T) { fillAmount := big.NewInt(6) + mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Long, fillAmount) assert.EqualError(t, err, ErrOverFill.Error()) }) @@ -72,6 +74,7 @@ func TestValidateLimitOrderLike(t *testing.T) { t.Run("negative fillAmount", func(t *testing.T) { fillAmount := big.NewInt(-6) + mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Long, fillAmount) assert.EqualError(t, err, ErrInvalidFillAmount.Error()) }) @@ -94,12 +97,15 @@ func TestValidateLimitOrderLike(t *testing.T) { start := new(big.Int).Neg(fillAmount).Int64() for i := start; i > start-5; i-- { mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(i)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) err := validateLimitOrderLike(mockBibliophile, &badOrder, filledAmount, Placed, Long, fillAmount) assert.Nil(t, err) } }) t.Run("all conditions met", func(t *testing.T) { + mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Long, fillAmount) assert.Nil(t, err) }) @@ -131,6 +137,7 @@ func TestValidateLimitOrderLike(t *testing.T) { t.Run("base asset quantity >= 0", func(t *testing.T) { badOrder := *order badOrder.BaseAssetQuantity = big.NewInt(23) + mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(2) err := validateLimitOrderLike(mockBibliophile, &badOrder, filledAmount, Placed, Short, fillAmount) assert.EqualError(t, err, ErrNotShortOrder.Error()) @@ -142,6 +149,7 @@ func TestValidateLimitOrderLike(t *testing.T) { t.Run("positive fillAmount", func(t *testing.T) { fillAmount := big.NewInt(6) + mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Short, fillAmount) assert.EqualError(t, err, ErrInvalidFillAmount.Error()) @@ -149,6 +157,7 @@ func TestValidateLimitOrderLike(t *testing.T) { t.Run("ErrOverFill", func(t *testing.T) { fillAmount := big.NewInt(-6) + mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Short, fillAmount) assert.EqualError(t, err, ErrOverFill.Error()) @@ -172,12 +181,15 @@ func TestValidateLimitOrderLike(t *testing.T) { start := new(big.Int).Abs(fillAmount).Int64() for i := start; i < start+5; i++ { mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(i)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) err := validateLimitOrderLike(mockBibliophile, &badOrder, filledAmount, Placed, Short, fillAmount) assert.Nil(t, err) } }) t.Run("all conditions met", func(t *testing.T) { + mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Short, fillAmount) assert.Nil(t, err) }) @@ -194,6 +206,7 @@ func TestValidateLimitOrderLike(t *testing.T) { } filledAmount := big.NewInt(0) fillAmount := big.NewInt(5) + mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Side(4), fillAmount) // assuming 4 is an invalid Side value assert.EqualError(t, err, "invalid side") @@ -231,6 +244,8 @@ func TestValidateExecuteLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(1)).Times(1) // placed mockBibliophile.EXPECT().GetBlockPlaced(orderHash).Return(blockPlaced).Times(1) // placed mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order.AmmIndex.Int64()).Return(marketAddress).Times(1) // placed + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) + mockBibliophile.EXPECT().GetSize(marketAddress, &trader).Return(big.NewInt(0)).Times(1) m, err := validateExecuteLimitOrder(mockBibliophile, order, Long, fillAmount) assert.Nil(t, err) @@ -749,13 +764,18 @@ func TestValidateOrdersAndDetermineFillPrice(t *testing.T) { mockBibliophile := b.NewMockBibliophileClient(ctrl) mockBibliophile.EXPECT().GetOrderFilledAmount(order0Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order0Hash).Return(int64(1)) // placed - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(common.Address{101}) + marketAddress := common.Address{101} + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(marketAddress) mockBibliophile.EXPECT().GetBlockPlaced(order0Hash).Return(big.NewInt(10)) + mockBibliophile.EXPECT().GetSize(marketAddress, &trader).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(order1Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order1Hash).Return(int64(1)) // placed - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(common.Address{102}) + marketAddress = common.Address{102} + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(marketAddress) mockBibliophile.EXPECT().GetBlockPlaced(order1Hash).Return(big.NewInt(12)) + mockBibliophile.EXPECT().GetSize(marketAddress, &trader).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) testCase := ValidateOrdersAndDetermineFillPriceTestCase{ Order0: order0, Order1: order1, @@ -797,13 +817,16 @@ func TestValidateOrdersAndDetermineFillPrice(t *testing.T) { mockBibliophile := b.NewMockBibliophileClient(ctrl) mockBibliophile.EXPECT().GetOrderFilledAmount(order0Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order0Hash).Return(int64(1)) // placed - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(common.Address{101}) + marketAddress := common.Address{101} + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(marketAddress) mockBibliophile.EXPECT().GetBlockPlaced(order0Hash).Return(big.NewInt(10)) mockBibliophile.EXPECT().GetOrderFilledAmount(order1Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order1Hash).Return(int64(1)) // placed - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(common.Address{101}) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(marketAddress) mockBibliophile.EXPECT().GetBlockPlaced(order1Hash).Return(big.NewInt(12)) + mockBibliophile.EXPECT().GetSize(marketAddress, &trader).Return(big.NewInt(0)).Times(2) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) testCase := ValidateOrdersAndDetermineFillPriceTestCase{ Order0: order0, Order1: order1, @@ -845,13 +868,16 @@ func TestValidateOrdersAndDetermineFillPrice(t *testing.T) { mockBibliophile := b.NewMockBibliophileClient(ctrl) mockBibliophile.EXPECT().GetOrderFilledAmount(order0Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order0Hash).Return(int64(1)) // placed - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(common.Address{101}) + marketAddress := common.Address{101} + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(marketAddress) mockBibliophile.EXPECT().GetBlockPlaced(order0Hash).Return(big.NewInt(10)) mockBibliophile.EXPECT().GetOrderFilledAmount(order1Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order1Hash).Return(int64(1)) // placed - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(common.Address{101}) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(marketAddress) mockBibliophile.EXPECT().GetBlockPlaced(order1Hash).Return(big.NewInt(12)) + mockBibliophile.EXPECT().GetSize(marketAddress, &trader).Return(big.NewInt(0)).Times(2) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) mockBibliophile.EXPECT().GetMinSizeRequirement(order1.AmmIndex.Int64()).Return(big.NewInt(5)) @@ -896,16 +922,19 @@ func TestValidateOrdersAndDetermineFillPrice(t *testing.T) { mockBibliophile := b.NewMockBibliophileClient(ctrl) mockBibliophile.EXPECT().GetOrderFilledAmount(order0Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order0Hash).Return(int64(1)) // placed - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(common.Address{101}) + marketAddress := common.Address{101} + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(marketAddress) mockBibliophile.EXPECT().GetBlockPlaced(order0Hash).Return(big.NewInt(10)) mockBibliophile.EXPECT().GetOrderFilledAmount(order1Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order1Hash).Return(int64(1)) // placed - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(common.Address{101}) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(marketAddress) mockBibliophile.EXPECT().GetBlockPlaced(order1Hash).Return(big.NewInt(12)) mockBibliophile.EXPECT().GetMinSizeRequirement(order1.AmmIndex.Int64()).Return(big.NewInt(1)) mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(order1.AmmIndex.Int64()).Return(big.NewInt(110), big.NewInt(90)) + mockBibliophile.EXPECT().GetSize(marketAddress, &trader).Return(big.NewInt(0)).Times(2) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) testCase := ValidateOrdersAndDetermineFillPriceTestCase{ Order0: order0, @@ -1020,6 +1049,7 @@ func TestValidateLiquidationOrderAndDetermineFillPrice(t *testing.T) { mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order.AmmIndex.Int64()).Return(common.Address{101}) mockBibliophile.EXPECT().GetBlockPlaced(orderHash).Return(big.NewInt(10)) mockBibliophile.EXPECT().GetSize(common.Address{101}, &trader).Return(big.NewInt(10)) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetMinSizeRequirement(order.AmmIndex.Int64()).Return(big.NewInt(5)) @@ -1056,6 +1086,7 @@ func TestValidateLiquidationOrderAndDetermineFillPrice(t *testing.T) { mockBibliophile.EXPECT().GetMinSizeRequirement(order.AmmIndex.Int64()).Return(big.NewInt(1)) mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(order.AmmIndex.Int64()).Return(big.NewInt(110), big.NewInt(90)) mockBibliophile.EXPECT().GetAcceptableBoundsForLiquidation(order.AmmIndex.Int64()).Return(big.NewInt(110), big.NewInt(90)) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) testCase := ValidateLiquidationOrderAndDetermineFillPriceTestCase{ Order: order, @@ -1124,6 +1155,7 @@ func TestGetRequiredMargin(t *testing.T) { mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(gomock.Any()).Return(big.NewInt(100), big.NewInt(10)).AnyTimes() mockBibliophile.EXPECT().GetMinAllowableMargin().Return(big.NewInt(1000)).AnyTimes() mockBibliophile.EXPECT().GetTakerFee().Return(big.NewInt(5)).AnyTimes() + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) // create a mock order order := ILimitOrderBookOrder{ diff --git a/precompile/contracts/traderviewer/contract.go b/precompile/contracts/traderviewer/contract.go index 259c0e88bd..955aeb3cd1 100644 --- a/precompile/contracts/traderviewer/contract.go +++ b/precompile/contracts/traderviewer/contract.go @@ -293,7 +293,8 @@ func getRequiredMargin(accessibleState contract.AccessibleState, caller common.A // CUSTOM CODE STARTS HERE _ = inputStruct // CUSTOM CODE OPERATES ON INPUT - var output *big.Int // CUSTOM CODE FOR AN OUTPUT + bibliophile := bibliophile.NewBibliophileClient(accessibleState) + var output = GetRequiredMargin(bibliophile, &inputStruct) packedOutput, err := PackGetRequiredMarginOutput(output) if err != nil { return nil, remainingGas, err diff --git a/precompile/contracts/traderviewer/contract_test.go b/precompile/contracts/traderviewer/contract_test.go index 5cfa8d4fb0..8d8c36a7ba 100644 --- a/precompile/contracts/traderviewer/contract_test.go +++ b/precompile/contracts/traderviewer/contract_test.go @@ -62,7 +62,12 @@ var ( InputFn: func(t testing.TB) []byte { // CUSTOM CODE STARTS HERE // populate test input here - testInput := GetRequiredMarginInput{} + testInput := GetRequiredMarginInput{ + Trader: common.Address{1}, + BaseAssetQuantity: big.NewInt(0), + Price: big.NewInt(0), + AmmIndex: big.NewInt(0), + } input, err := PackGetRequiredMargin(testInput) require.NoError(t, err) return input @@ -107,7 +112,18 @@ var ( InputFn: func(t testing.TB) []byte { // CUSTOM CODE STARTS HERE // populate test input here - testInput := ValidateCancelLimitOrderV2Input{} + testInput := ValidateCancelLimitOrderV2Input{ + Order: ILimitOrderBookOrder{ + AmmIndex: big.NewInt(0), + Trader: common.Address{1}, + BaseAssetQuantity: big.NewInt(0), + Price: big.NewInt(0), + Salt: big.NewInt(0), + ReduceOnly: false, + PostOnly: false, + }, + Sender: common.Address{1}, + } input, err := PackValidateCancelLimitOrderV2(testInput) require.NoError(t, err) return input diff --git a/precompile/contracts/traderviewer/limit_orders.go b/precompile/contracts/traderviewer/limit_orders.go index 584e2304f9..d77f806d9e 100644 --- a/precompile/contracts/traderviewer/limit_orders.go +++ b/precompile/contracts/traderviewer/limit_orders.go @@ -49,7 +49,7 @@ func ValidateCancelLimitOrderV2(bibliophile b.BibliophileClient, inputStruct *Va return default: } - response.Res.UnfilledAmount = big.NewInt(0).Sub(order.BaseAssetQuantity, bibliophile.GetOrderFilledAmount(orderHash)) + response.Res.UnfilledAmount = hu.Sub(order.BaseAssetQuantity, bibliophile.GetOrderFilledAmount(orderHash)) if assertLowMargin && bibliophile.GetAvailableMargin(trader, hu.UpgradeVersionV0orV1(bibliophile.GetTimeStamp())).Sign() != -1 { response.Err = "Not Low Margin" return @@ -84,6 +84,6 @@ func GetLimitOrderHashFromContractStruct(o *ILimitOrderBookOrder) (common.Hash, return ILimitOrderBookOrderToLimitOrder(o).Hash() } -func GetRequiredMargin(bibliophile b.BibliophileClient, baseAsset *big.Int, price *big.Int, marketId int64, trader *common.Address) *big.Int { - return bibliophile.GetRequiredMargin(baseAsset, price, marketId, trader) +func GetRequiredMargin(bibliophile b.BibliophileClient, inputStruct *GetRequiredMarginInput) *big.Int { + return bibliophile.GetRequiredMargin(inputStruct.BaseAssetQuantity, inputStruct.Price, inputStruct.AmmIndex.Int64(), &inputStruct.Trader) } diff --git a/precompile/contracts/traderviewer/limit_orders_test.go b/precompile/contracts/traderviewer/limit_orders_test.go new file mode 100644 index 0000000000..4f72698477 --- /dev/null +++ b/precompile/contracts/traderviewer/limit_orders_test.go @@ -0,0 +1,385 @@ +package traderviewer + +import ( + "math/big" + + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + + hu "github.com/ava-labs/subnet-evm/plugin/evm/orderbook/hubbleutils" + b "github.com/ava-labs/subnet-evm/precompile/contracts/bibliophile" + juror "github.com/ava-labs/subnet-evm/precompile/contracts/juror" + gomock "github.com/golang/mock/gomock" +) + +func TestValidateCancelLimitOrderV2(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockBibliophile := b.NewMockBibliophileClient(ctrl) + ammIndex := big.NewInt(0) + longBaseAssetQuantity := big.NewInt(5000000000000000000) + shortBaseAssetQuantity := big.NewInt(-5000000000000000000) + price := big.NewInt(100000000) + salt := big.NewInt(121) + reduceOnly := false + postOnly := false + trader := common.HexToAddress("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC") + ammAddress := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8") + assertLowMargin := false + assertOverPositionCap := false + + t.Run("when sender is not the trader and is not trading authority, it returns error", func(t *testing.T) { + sender := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C9") + t.Run("it returns error for a long order", func(t *testing.T) { + order := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) + input := getValidateCancelLimitOrderV2Input(order, sender, assertLowMargin, assertOverPositionCap) + mockBibliophile.EXPECT().IsTradingAuthority(order.Trader, sender).Return(false).Times(1) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, ErrUnauthorizedCancellation.Error(), output.Err) + }) + t.Run("it returns error for a short order", func(t *testing.T) { + order := getOrder(ammIndex, trader, shortBaseAssetQuantity, price, salt, reduceOnly, postOnly) + input := getValidateCancelLimitOrderV2Input(order, sender, assertLowMargin, assertOverPositionCap) + mockBibliophile.EXPECT().IsTradingAuthority(order.Trader, sender).Return(false).Times(1) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, ErrUnauthorizedCancellation.Error(), output.Err) + }) + }) + t.Run("when either sender is trader or a trading authority", func(t *testing.T) { + t.Run("When order status is not placed", func(t *testing.T) { + t.Run("when order status was never placed", func(t *testing.T) { + t.Run("it returns error for a longOrder", func(t *testing.T) { + longOrder := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(longOrder) + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Invalid)).Times(1) + input := getValidateCancelLimitOrderV2Input(longOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "Invalid", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, big.NewInt(0), output.Res.UnfilledAmount) + }) + t.Run("it returns error for a shortOrder", func(t *testing.T) { + shortOrder := getOrder(ammIndex, trader, shortBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(shortOrder) + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Invalid)).Times(1) + input := getValidateCancelLimitOrderV2Input(shortOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "Invalid", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, big.NewInt(0), output.Res.UnfilledAmount) + }) + }) + t.Run("when order status is cancelled", func(t *testing.T) { + t.Run("it returns error for a longOrder", func(t *testing.T) { + longOrder := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(longOrder) + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Cancelled)).Times(1) + input := getValidateCancelLimitOrderV2Input(longOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "Cancelled", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, big.NewInt(0), output.Res.UnfilledAmount) + }) + t.Run("it returns error for a shortOrder", func(t *testing.T) { + shortOrder := getOrder(ammIndex, trader, shortBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(shortOrder) + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Cancelled)).Times(1) + input := getValidateCancelLimitOrderV2Input(shortOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "Cancelled", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, big.NewInt(0), output.Res.UnfilledAmount) + }) + }) + t.Run("when order status is filled", func(t *testing.T) { + t.Run("it returns error for a longOrder", func(t *testing.T) { + longOrder := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(longOrder) + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Filled)).Times(1) + input := getValidateCancelLimitOrderV2Input(longOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "Filled", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, big.NewInt(0), output.Res.UnfilledAmount) + }) + t.Run("it returns error for a shortOrder", func(t *testing.T) { + shortOrder := getOrder(ammIndex, trader, shortBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(shortOrder) + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Filled)).Times(1) + input := getValidateCancelLimitOrderV2Input(shortOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "Filled", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, big.NewInt(0), output.Res.UnfilledAmount) + }) + }) + }) + t.Run("When order status is placed", func(t *testing.T) { + t.Run("when assertLowMargin is true", func(t *testing.T) { + assertLowMargin := true + t.Run("when availableMargin >= zero", func(t *testing.T) { + t.Run("when availableMargin == 0 ", func(t *testing.T) { + t.Run("it returns error for a longOrder", func(t *testing.T) { + longOrder := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(longOrder) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V1).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().IsValidator(longOrder.Trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) + input := getValidateCancelLimitOrderV2Input(longOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "Not Low Margin", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, longOrder.BaseAssetQuantity, output.Res.UnfilledAmount) + }) + t.Run("it returns error for a shortOrder", func(t *testing.T) { + shortOrder := getOrder(ammIndex, trader, shortBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(shortOrder) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V1).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().IsValidator(shortOrder.Trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) + input := getValidateCancelLimitOrderV2Input(shortOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + + assert.Equal(t, "Not Low Margin", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, shortOrder.BaseAssetQuantity, output.Res.UnfilledAmount) + }) + }) + t.Run("when availableMargin > 0 ", func(t *testing.T) { + newMargin := hu.Mul(price, longBaseAssetQuantity) + t.Run("it returns error for a longOrder", func(t *testing.T) { + longOrder := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(longOrder) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V1).Return(newMargin).Times(1) + mockBibliophile.EXPECT().IsValidator(longOrder.Trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) + input := getValidateCancelLimitOrderV2Input(longOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "Not Low Margin", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, longOrder.BaseAssetQuantity, output.Res.UnfilledAmount) + }) + t.Run("it returns error for a shortOrder", func(t *testing.T) { + shortOrder := getOrder(ammIndex, trader, shortBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(shortOrder) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V1).Return(newMargin).Times(1) + mockBibliophile.EXPECT().IsValidator(shortOrder.Trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) + input := getValidateCancelLimitOrderV2Input(shortOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "Not Low Margin", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, shortOrder.BaseAssetQuantity, output.Res.UnfilledAmount) + }) + }) + }) + t.Run("when availableMargin < zero", func(t *testing.T) { + t.Run("for an unfilled Order", func(t *testing.T) { + t.Run("for a longOrder it returns err = nil, with ammAddress and unfilled amount of cancelled Order", func(t *testing.T) { + longOrder := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(longOrder) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V1).Return(big.NewInt(-1)).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().IsValidator(longOrder.Trader).Return(true).Times(1) + + input := getValidateCancelLimitOrderV2Input(longOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + assert.Equal(t, longOrder.BaseAssetQuantity, output.Res.UnfilledAmount) + }) + t.Run("for a shortOrder it returns err = nil, with ammAddress and unfilled amount of cancelled Order", func(t *testing.T) { + shortOrder := getOrder(ammIndex, trader, shortBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(shortOrder) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V1).Return(big.NewInt(-1)).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().IsValidator(shortOrder.Trader).Return(true).Times(1) + + input := getValidateCancelLimitOrderV2Input(shortOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + assert.Equal(t, shortOrder.BaseAssetQuantity, output.Res.UnfilledAmount) + }) + }) + t.Run("for a partially filled Order", func(t *testing.T) { + t.Run("for a longOrder it returns err = nil, with ammAddress and unfilled amount of cancelled Order", func(t *testing.T) { + longOrder := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(longOrder) + filledAmount := hu.Div(longOrder.BaseAssetQuantity, big.NewInt(2)) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V1).Return(big.NewInt(-1)).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(filledAmount).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().IsValidator(longOrder.Trader).Return(true).Times(1) + + input := getValidateCancelLimitOrderV2Input(longOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + expectedUnfilleAmount := hu.Sub(longOrder.BaseAssetQuantity, filledAmount) + assert.Equal(t, expectedUnfilleAmount, output.Res.UnfilledAmount) + }) + t.Run("for a shortOrder it returns err = nil, with ammAddress and unfilled amount of cancelled Order", func(t *testing.T) { + shortOrder := getOrder(ammIndex, trader, shortBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(shortOrder) + filledAmount := hu.Div(shortOrder.BaseAssetQuantity, big.NewInt(2)) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V1).Return(big.NewInt(-1)).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(filledAmount).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().IsValidator(shortOrder.Trader).Return(true).Times(1) + + input := getValidateCancelLimitOrderV2Input(shortOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + expectedUnfilleAmount := hu.Sub(shortOrder.BaseAssetQuantity, filledAmount) + assert.Equal(t, expectedUnfilleAmount, output.Res.UnfilledAmount) + }) + }) + }) + }) + t.Run("when assertLowMargin is false", func(t *testing.T) { + assertLowMargin := false + t.Run("for an unfilled Order", func(t *testing.T) { + t.Run("for a longOrder it returns err = nil, with ammAddress and unfilled amount of cancelled Order", func(t *testing.T) { + longOrder := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(longOrder) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + + input := getValidateCancelLimitOrderV2Input(longOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + assert.Equal(t, longOrder.BaseAssetQuantity, output.Res.UnfilledAmount) + }) + t.Run("for a shortOrder it returns err = nil, with ammAddress and unfilled amount of cancelled Order", func(t *testing.T) { + shortOrder := getOrder(ammIndex, trader, shortBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(shortOrder) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + + input := getValidateCancelLimitOrderV2Input(shortOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + assert.Equal(t, shortOrder.BaseAssetQuantity, output.Res.UnfilledAmount) + }) + }) + t.Run("for a partially filled Order", func(t *testing.T) { + t.Run("for a longOrder it returns err = nil, with ammAddress and unfilled amount of cancelled Order", func(t *testing.T) { + longOrder := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(longOrder) + filledAmount := hu.Div(longOrder.BaseAssetQuantity, big.NewInt(2)) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(filledAmount).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + + input := getValidateCancelLimitOrderV2Input(longOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + expectedUnfilleAmount := hu.Sub(longOrder.BaseAssetQuantity, filledAmount) + assert.Equal(t, expectedUnfilleAmount, output.Res.UnfilledAmount) + }) + t.Run("for a shortOrder it returns err = nil, with ammAddress and unfilled amount of cancelled Order", func(t *testing.T) { + shortOrder := getOrder(ammIndex, trader, shortBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(shortOrder) + filledAmount := hu.Div(shortOrder.BaseAssetQuantity, big.NewInt(2)) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(filledAmount).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + + input := getValidateCancelLimitOrderV2Input(shortOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, "", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + expectedUnfilleAmount := hu.Sub(shortOrder.BaseAssetQuantity, filledAmount) + assert.Equal(t, expectedUnfilleAmount, output.Res.UnfilledAmount) + }) + }) + }) + }) + }) +} + +func getValidateCancelLimitOrderV2Input(order ILimitOrderBookOrder, sender common.Address, assertLowMargin bool, assertOverPositionCap bool) ValidateCancelLimitOrderV2Input { + return ValidateCancelLimitOrderV2Input{ + Order: order, + Sender: sender, + AssertLowMargin: assertLowMargin, + AssertOverPositionCap: assertOverPositionCap, + } +} + +func getOrder(ammIndex *big.Int, trader common.Address, baseAssetQuantity *big.Int, price *big.Int, salt *big.Int, reduceOnly bool, postOnly bool) ILimitOrderBookOrder { + return ILimitOrderBookOrder{ + AmmIndex: ammIndex, + BaseAssetQuantity: baseAssetQuantity, + Trader: trader, + Price: price, + Salt: salt, + ReduceOnly: reduceOnly, + PostOnly: postOnly, + } +} + +func getOrderHash(order ILimitOrderBookOrder) common.Hash { + orderHash, err := GetLimitOrderHashFromContractStruct(&order) + if err != nil { + panic("error in getting order hash") + } + return orderHash +} From bdc5027108c44bd020e1f902dd570e2e6371e5f0 Mon Sep 17 00:00:00 2001 From: Atul Agarwal <21087753+asquare08@users.noreply.github.com> Date: Mon, 18 Mar 2024 20:30:14 +0530 Subject: [PATCH 11/14] add overPositonCap test in traderViewer --- precompile/contracts/bibliophile/amm.go | 13 ++-- precompile/contracts/juror/ioc_orders_test.go | 3 - .../contracts/juror/matching_validation.go | 10 +-- .../juror/matching_validation_test.go | 47 ++---------- precompile/contracts/jurorv2/module.go | 3 +- .../contracts/traderviewer/limit_orders.go | 5 +- .../traderviewer/limit_orders_test.go | 73 +++++++++++++++++-- 7 files changed, 89 insertions(+), 65 deletions(-) diff --git a/precompile/contracts/bibliophile/amm.go b/precompile/contracts/bibliophile/amm.go index 0c4441b63f..5a1addab08 100644 --- a/precompile/contracts/bibliophile/amm.go +++ b/precompile/contracts/bibliophile/amm.go @@ -180,6 +180,10 @@ func traderMarginFraction(stateDB contract.StateDB, market common.Address, trade return stateDB.GetState(market, common.BigToHash(new(big.Int).Add(accountPreferencesSlot(trader), big.NewInt(1)))).Big() } +func getMaxPositionCap(stateDB contract.StateDB, market common.Address) *big.Int { + return stateDB.GetState(market, common.BigToHash(big.NewInt(MAX_POSITION_CAP_SLOT))).Big() +} + func getMarginFractionByMode(stateDB contract.StateDB, market common.Address, trader *common.Address, mode uint8) *big.Int { if mode == hu.Maintenance_Margin { if (getMarginType(stateDB, market, trader) == hu.Isolated_Margin) { @@ -194,8 +198,9 @@ func getMarginFractionByMode(stateDB contract.StateDB, market common.Address, tr } func getTraderMarginFraction(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { - if (traderMarginFraction(stateDB, market, trader).Cmp(big.NewInt(0)) != 0) { - return traderMarginFraction(stateDB, market, trader) + traderMarginFraction_ := traderMarginFraction(stateDB, market, trader) + if (traderMarginFraction_.Cmp(big.NewInt(0)) != 0) { + return traderMarginFraction_ } else if (getMarginType(stateDB, market, trader) == hu.Isolated_Margin) { return getIsolatedTradeMarginFraction(stateDB, market) } else { @@ -213,10 +218,6 @@ func getRequiredMarginForQuote(stateDB contract.StateDB, market common.Address, return hu.Div1e6(hu.Mul(quote, marginFraction)) } -func getMaxPositionCap(stateDB contract.StateDB, market common.Address) *big.Int { - return stateDB.GetState(market, common.BigToHash(big.NewInt(MAX_POSITION_CAP_SLOT))).Big() -} - func getPositionCap(stateDB contract.StateDB, market int64, trader *common.Address) *big.Int { marketAddress := GetMarketAddressFromMarketID(market, stateDB) maxPositionCap := getMaxPositionCap(stateDB, marketAddress) diff --git a/precompile/contracts/juror/ioc_orders_test.go b/precompile/contracts/juror/ioc_orders_test.go index a772e1c39d..b939eb79ff 100644 --- a/precompile/contracts/juror/ioc_orders_test.go +++ b/precompile/contracts/juror/ioc_orders_test.go @@ -490,8 +490,6 @@ func TestValidateExecuteIOCOrder(t *testing.T) { mockBibliophile.EXPECT().IOC_GetOrderFilledAmount(hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().IOC_GetOrderStatus(hash).Return(int64(1)) mockBibliophile.EXPECT().IOC_GetBlockPlaced(hash).Return(big.NewInt(21)) - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) - mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(big.NewInt(0)) m, err := validateExecuteIOCOrder(mockBibliophile, &order, Long, big.NewInt(10)) assert.Nil(t, err) @@ -528,7 +526,6 @@ func TestValidateExecuteIOCOrder(t *testing.T) { mockBibliophile.EXPECT().IOC_GetOrderFilledAmount(hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().IOC_GetOrderStatus(hash).Return(int64(1)) mockBibliophile.EXPECT().IOC_GetBlockPlaced(hash).Return(big.NewInt(21)) - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) _, err := validateExecuteIOCOrder(mockBibliophile, &order, Long, big.NewInt(10)) assert.Nil(t, err) diff --git a/precompile/contracts/juror/matching_validation.go b/precompile/contracts/juror/matching_validation.go index d12dbbf40e..93caf8b206 100644 --- a/precompile/contracts/juror/matching_validation.go +++ b/precompile/contracts/juror/matching_validation.go @@ -380,7 +380,6 @@ func validateLimitOrderLike(bibliophile b.BibliophileClient, order *hu.BaseOrder } market := bibliophile.GetMarketAddressFromMarketID(order.AmmIndex.Int64()) - posSize := bibliophile.GetSize(market, &order.Trader) if side == Long { if order.BaseAssetQuantity.Sign() <= 0 { return ErrNotLongOrder @@ -392,6 +391,7 @@ func validateLimitOrderLike(bibliophile b.BibliophileClient, order *hu.BaseOrder return ErrOverFill } if order.ReduceOnly { + posSize := bibliophile.GetSize(market, &order.Trader) // posSize should be closed to continue to be Short // this also returns err if posSize >= 0, which should not happen because we are executing a long reduceOnly order on this account if new(big.Int).Add(posSize, fillAmount).Sign() > 0 { @@ -409,6 +409,7 @@ func validateLimitOrderLike(bibliophile b.BibliophileClient, order *hu.BaseOrder return ErrOverFill } if order.ReduceOnly { + posSize := bibliophile.GetSize(market, &order.Trader) // posSize should continue to be Long // this also returns is posSize <= 0, which should not happen because we are executing a short reduceOnly order on this account if new(big.Int).Add(posSize, fillAmount).Sign() < 0 { @@ -418,13 +419,6 @@ func validateLimitOrderLike(bibliophile b.BibliophileClient, order *hu.BaseOrder } else { return errors.New("invalid side") } - - if bibliophile.GetPrecompileVersion(common.HexToAddress(SelfAddress)).Cmp(big.NewInt(1)) >= 0 { - posCap := bibliophile.GetPositionCap(order.AmmIndex.Int64(), order.Trader) - if hu.Abs(hu.Add(posSize, order.BaseAssetQuantity)).Cmp(posCap) == 1 { - return ErrOverPositionCap - } - } return nil } diff --git a/precompile/contracts/juror/matching_validation_test.go b/precompile/contracts/juror/matching_validation_test.go index b0794fd936..f013150bd6 100644 --- a/precompile/contracts/juror/matching_validation_test.go +++ b/precompile/contracts/juror/matching_validation_test.go @@ -53,7 +53,6 @@ func TestValidateLimitOrderLike(t *testing.T) { t.Run("base asset quantity <= 0", func(t *testing.T) { badOrder := *order badOrder.BaseAssetQuantity = big.NewInt(-23) - mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(2) err := validateLimitOrderLike(mockBibliophile, &badOrder, filledAmount, Placed, Long, fillAmount) assert.EqualError(t, err, ErrNotLongOrder.Error()) @@ -66,7 +65,6 @@ func TestValidateLimitOrderLike(t *testing.T) { t.Run("ErrOverFill", func(t *testing.T) { fillAmount := big.NewInt(6) - mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Long, fillAmount) assert.EqualError(t, err, ErrOverFill.Error()) }) @@ -74,7 +72,6 @@ func TestValidateLimitOrderLike(t *testing.T) { t.Run("negative fillAmount", func(t *testing.T) { fillAmount := big.NewInt(-6) - mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Long, fillAmount) assert.EqualError(t, err, ErrInvalidFillAmount.Error()) }) @@ -97,15 +94,12 @@ func TestValidateLimitOrderLike(t *testing.T) { start := new(big.Int).Neg(fillAmount).Int64() for i := start; i > start-5; i-- { mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(i)).Times(1) - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) err := validateLimitOrderLike(mockBibliophile, &badOrder, filledAmount, Placed, Long, fillAmount) assert.Nil(t, err) } }) t.Run("all conditions met", func(t *testing.T) { - mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Long, fillAmount) assert.Nil(t, err) }) @@ -137,7 +131,6 @@ func TestValidateLimitOrderLike(t *testing.T) { t.Run("base asset quantity >= 0", func(t *testing.T) { badOrder := *order badOrder.BaseAssetQuantity = big.NewInt(23) - mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(2) err := validateLimitOrderLike(mockBibliophile, &badOrder, filledAmount, Placed, Short, fillAmount) assert.EqualError(t, err, ErrNotShortOrder.Error()) @@ -149,7 +142,6 @@ func TestValidateLimitOrderLike(t *testing.T) { t.Run("positive fillAmount", func(t *testing.T) { fillAmount := big.NewInt(6) - mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Short, fillAmount) assert.EqualError(t, err, ErrInvalidFillAmount.Error()) @@ -157,7 +149,6 @@ func TestValidateLimitOrderLike(t *testing.T) { t.Run("ErrOverFill", func(t *testing.T) { fillAmount := big.NewInt(-6) - mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Short, fillAmount) assert.EqualError(t, err, ErrOverFill.Error()) @@ -181,15 +172,12 @@ func TestValidateLimitOrderLike(t *testing.T) { start := new(big.Int).Abs(fillAmount).Int64() for i := start; i < start+5; i++ { mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(i)).Times(1) - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) err := validateLimitOrderLike(mockBibliophile, &badOrder, filledAmount, Placed, Short, fillAmount) assert.Nil(t, err) } }) t.Run("all conditions met", func(t *testing.T) { - mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Short, fillAmount) assert.Nil(t, err) }) @@ -206,7 +194,6 @@ func TestValidateLimitOrderLike(t *testing.T) { } filledAmount := big.NewInt(0) fillAmount := big.NewInt(5) - mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Side(4), fillAmount) // assuming 4 is an invalid Side value assert.EqualError(t, err, "invalid side") @@ -244,8 +231,6 @@ func TestValidateExecuteLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(1)).Times(1) // placed mockBibliophile.EXPECT().GetBlockPlaced(orderHash).Return(blockPlaced).Times(1) // placed mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order.AmmIndex.Int64()).Return(marketAddress).Times(1) // placed - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)) - mockBibliophile.EXPECT().GetSize(marketAddress, &trader).Return(big.NewInt(0)).Times(1) m, err := validateExecuteLimitOrder(mockBibliophile, order, Long, fillAmount) assert.Nil(t, err) @@ -764,18 +749,13 @@ func TestValidateOrdersAndDetermineFillPrice(t *testing.T) { mockBibliophile := b.NewMockBibliophileClient(ctrl) mockBibliophile.EXPECT().GetOrderFilledAmount(order0Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order0Hash).Return(int64(1)) // placed - marketAddress := common.Address{101} - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(marketAddress) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(common.Address{101}) mockBibliophile.EXPECT().GetBlockPlaced(order0Hash).Return(big.NewInt(10)) - mockBibliophile.EXPECT().GetSize(marketAddress, &trader).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(order1Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order1Hash).Return(int64(1)) // placed - marketAddress = common.Address{102} - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(marketAddress) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(common.Address{102}) mockBibliophile.EXPECT().GetBlockPlaced(order1Hash).Return(big.NewInt(12)) - mockBibliophile.EXPECT().GetSize(marketAddress, &trader).Return(big.NewInt(0)).Times(1) - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) testCase := ValidateOrdersAndDetermineFillPriceTestCase{ Order0: order0, Order1: order1, @@ -817,16 +797,13 @@ func TestValidateOrdersAndDetermineFillPrice(t *testing.T) { mockBibliophile := b.NewMockBibliophileClient(ctrl) mockBibliophile.EXPECT().GetOrderFilledAmount(order0Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order0Hash).Return(int64(1)) // placed - marketAddress := common.Address{101} - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(marketAddress) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(common.Address{101}) mockBibliophile.EXPECT().GetBlockPlaced(order0Hash).Return(big.NewInt(10)) mockBibliophile.EXPECT().GetOrderFilledAmount(order1Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order1Hash).Return(int64(1)) // placed - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(marketAddress) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(common.Address{101}) mockBibliophile.EXPECT().GetBlockPlaced(order1Hash).Return(big.NewInt(12)) - mockBibliophile.EXPECT().GetSize(marketAddress, &trader).Return(big.NewInt(0)).Times(2) - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) testCase := ValidateOrdersAndDetermineFillPriceTestCase{ Order0: order0, Order1: order1, @@ -868,16 +845,13 @@ func TestValidateOrdersAndDetermineFillPrice(t *testing.T) { mockBibliophile := b.NewMockBibliophileClient(ctrl) mockBibliophile.EXPECT().GetOrderFilledAmount(order0Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order0Hash).Return(int64(1)) // placed - marketAddress := common.Address{101} - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(marketAddress) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(common.Address{101}) mockBibliophile.EXPECT().GetBlockPlaced(order0Hash).Return(big.NewInt(10)) mockBibliophile.EXPECT().GetOrderFilledAmount(order1Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order1Hash).Return(int64(1)) // placed - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(marketAddress) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(common.Address{101}) mockBibliophile.EXPECT().GetBlockPlaced(order1Hash).Return(big.NewInt(12)) - mockBibliophile.EXPECT().GetSize(marketAddress, &trader).Return(big.NewInt(0)).Times(2) - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) mockBibliophile.EXPECT().GetMinSizeRequirement(order1.AmmIndex.Int64()).Return(big.NewInt(5)) @@ -922,19 +896,16 @@ func TestValidateOrdersAndDetermineFillPrice(t *testing.T) { mockBibliophile := b.NewMockBibliophileClient(ctrl) mockBibliophile.EXPECT().GetOrderFilledAmount(order0Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order0Hash).Return(int64(1)) // placed - marketAddress := common.Address{101} - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(marketAddress) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order0.AmmIndex.Int64()).Return(common.Address{101}) mockBibliophile.EXPECT().GetBlockPlaced(order0Hash).Return(big.NewInt(10)) mockBibliophile.EXPECT().GetOrderFilledAmount(order1Hash).Return(big.NewInt(0)) mockBibliophile.EXPECT().GetOrderStatus(order1Hash).Return(int64(1)) // placed - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(marketAddress) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(common.Address{101}) mockBibliophile.EXPECT().GetBlockPlaced(order1Hash).Return(big.NewInt(12)) mockBibliophile.EXPECT().GetMinSizeRequirement(order1.AmmIndex.Int64()).Return(big.NewInt(1)) mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(order1.AmmIndex.Int64()).Return(big.NewInt(110), big.NewInt(90)) - mockBibliophile.EXPECT().GetSize(marketAddress, &trader).Return(big.NewInt(0)).Times(2) - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) testCase := ValidateOrdersAndDetermineFillPriceTestCase{ Order0: order0, @@ -1049,7 +1020,6 @@ func TestValidateLiquidationOrderAndDetermineFillPrice(t *testing.T) { mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order.AmmIndex.Int64()).Return(common.Address{101}) mockBibliophile.EXPECT().GetBlockPlaced(orderHash).Return(big.NewInt(10)) mockBibliophile.EXPECT().GetSize(common.Address{101}, &trader).Return(big.NewInt(10)) - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetMinSizeRequirement(order.AmmIndex.Int64()).Return(big.NewInt(5)) @@ -1086,7 +1056,6 @@ func TestValidateLiquidationOrderAndDetermineFillPrice(t *testing.T) { mockBibliophile.EXPECT().GetMinSizeRequirement(order.AmmIndex.Int64()).Return(big.NewInt(1)) mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(order.AmmIndex.Int64()).Return(big.NewInt(110), big.NewInt(90)) mockBibliophile.EXPECT().GetAcceptableBoundsForLiquidation(order.AmmIndex.Int64()).Return(big.NewInt(110), big.NewInt(90)) - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) testCase := ValidateLiquidationOrderAndDetermineFillPriceTestCase{ Order: order, diff --git a/precompile/contracts/jurorv2/module.go b/precompile/contracts/jurorv2/module.go index 59fce051d3..b2d86790ef 100644 --- a/precompile/contracts/jurorv2/module.go +++ b/precompile/contracts/jurorv2/module.go @@ -19,11 +19,12 @@ var _ contract.Configurator = &configurator{} // ConfigKey is the key used in json config files to specify this precompile precompileconfig. // must be unique across all precompiles. const ConfigKey = "jurorV2Config" +const SelfAddress = "0x03000000000000000000000000000000000000a2" // ContractAddress is the defined address of the precompile contract. // This should be unique across all precompile contracts. // See precompile/registry/registry.go for registered precompile contracts and more information. -var ContractAddress = common.HexToAddress("0x03000000000000000000000000000000000000a2") // SET A SUITABLE HEX ADDRESS HERE +var ContractAddress = common.HexToAddress(SelfAddress) // SET A SUITABLE HEX ADDRESS HERE // Module is the precompile module. It is used to register the precompile contract. var Module = modules.Module{ diff --git a/precompile/contracts/traderviewer/limit_orders.go b/precompile/contracts/traderviewer/limit_orders.go index d77f806d9e..3efecee12d 100644 --- a/precompile/contracts/traderviewer/limit_orders.go +++ b/precompile/contracts/traderviewer/limit_orders.go @@ -50,18 +50,17 @@ func ValidateCancelLimitOrderV2(bibliophile b.BibliophileClient, inputStruct *Va default: } response.Res.UnfilledAmount = hu.Sub(order.BaseAssetQuantity, bibliophile.GetOrderFilledAmount(orderHash)) + response.Res.Amm = bibliophile.GetMarketAddressFromMarketID(order.AmmIndex.Int64()) if assertLowMargin && bibliophile.GetAvailableMargin(trader, hu.UpgradeVersionV0orV1(bibliophile.GetTimeStamp())).Sign() != -1 { response.Err = "Not Low Margin" return } else if assertOverPositionCap { - ammAddress := bibliophile.GetMarketAddressFromMarketID(order.AmmIndex.Int64()) - posSize := bibliophile.GetSize(ammAddress, &trader) + posSize := bibliophile.GetSize(response.Res.Amm, &trader) if hu.Abs(hu.Add(posSize, response.Res.UnfilledAmount)).Cmp(bibliophile.GetPositionCap(order.AmmIndex.Int64(), trader)) <= 0 { response.Err = ErrNotOverPositionCap.Error() return } } - response.Res.Amm = bibliophile.GetMarketAddressFromMarketID(order.AmmIndex.Int64()) return response } diff --git a/precompile/contracts/traderviewer/limit_orders_test.go b/precompile/contracts/traderviewer/limit_orders_test.go index 4f72698477..dca480b1f7 100644 --- a/precompile/contracts/traderviewer/limit_orders_test.go +++ b/precompile/contracts/traderviewer/limit_orders_test.go @@ -136,11 +136,12 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V1).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().IsValidator(longOrder.Trader).Return(true).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) input := getValidateCancelLimitOrderV2Input(longOrder, trader, assertLowMargin, assertOverPositionCap) output := ValidateCancelLimitOrderV2(mockBibliophile, &input) assert.Equal(t, "Not Low Margin", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) - assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, ammAddress, output.Res.Amm) assert.Equal(t, longOrder.BaseAssetQuantity, output.Res.UnfilledAmount) }) t.Run("it returns error for a shortOrder", func(t *testing.T) { @@ -152,12 +153,13 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V1).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().IsValidator(shortOrder.Trader).Return(true).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) input := getValidateCancelLimitOrderV2Input(shortOrder, trader, assertLowMargin, assertOverPositionCap) output := ValidateCancelLimitOrderV2(mockBibliophile, &input) assert.Equal(t, "Not Low Margin", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) - assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, ammAddress, output.Res.Amm) assert.Equal(t, shortOrder.BaseAssetQuantity, output.Res.UnfilledAmount) }) }) @@ -172,11 +174,12 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V1).Return(newMargin).Times(1) mockBibliophile.EXPECT().IsValidator(longOrder.Trader).Return(true).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) input := getValidateCancelLimitOrderV2Input(longOrder, trader, assertLowMargin, assertOverPositionCap) output := ValidateCancelLimitOrderV2(mockBibliophile, &input) assert.Equal(t, "Not Low Margin", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) - assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, ammAddress, output.Res.Amm) assert.Equal(t, longOrder.BaseAssetQuantity, output.Res.UnfilledAmount) }) t.Run("it returns error for a shortOrder", func(t *testing.T) { @@ -188,11 +191,12 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V1).Return(newMargin).Times(1) mockBibliophile.EXPECT().IsValidator(shortOrder.Trader).Return(true).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) input := getValidateCancelLimitOrderV2Input(shortOrder, trader, assertLowMargin, assertOverPositionCap) output := ValidateCancelLimitOrderV2(mockBibliophile, &input) assert.Equal(t, "Not Low Margin", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) - assert.Equal(t, common.Address{}, output.Res.Amm) + assert.Equal(t, ammAddress, output.Res.Amm) assert.Equal(t, shortOrder.BaseAssetQuantity, output.Res.UnfilledAmount) }) }) @@ -280,8 +284,12 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { }) }) }) - t.Run("when assertLowMargin is false", func(t *testing.T) { + t.Run("when assertLowMargin is false, position cap not reached", func(t *testing.T) { assertLowMargin := false + assertOverPositionCap := true // also asserts isOverPositionCap + mockBibliophile.EXPECT().IsValidator(trader).Return(true).Times(4) + mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(big.NewInt(0)).Times(4) + mockBibliophile.EXPECT().GetPositionCap(ammIndex.Int64(), trader).Return(big.NewInt(1e18)).Times(4) t.Run("for an unfilled Order", func(t *testing.T) { t.Run("for a longOrder it returns err = nil, with ammAddress and unfilled amount of cancelled Order", func(t *testing.T) { longOrder := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) @@ -351,6 +359,61 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { }) }) }) + t.Run("when assertOverPositionCap is true", func(t *testing.T) { + assertOverPositionCap := true + maxPositionCap := hu.Mul(longBaseAssetQuantity, big.NewInt(2)) // 10 + t.Run("when sender is not a validator", func(t *testing.T) { + sender := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C9") + order := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) + input := getValidateCancelLimitOrderV2Input(order, sender, assertLowMargin, assertOverPositionCap) + mockBibliophile.EXPECT().IsTradingAuthority(order.Trader, sender).Return(true).Times(1) + mockBibliophile.EXPECT().IsValidator(sender).Return(false).Times(1) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, ErrUnauthorizedCancellation.Error(), output.Err) + }) + t.Run("when sender is a validator", func(t *testing.T) { + t.Run("when positionCap is not reached", func(t *testing.T) { + t.Run("return error for a longOrder", func(t *testing.T) { + longOrder := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(longOrder) + unfilledAmount := hu.Div(longOrder.BaseAssetQuantity, big.NewInt(2)) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().IsValidator(longOrder.Trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(hu.Sub(longOrder.BaseAssetQuantity, unfilledAmount)).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPositionCap(longOrder.AmmIndex.Int64(), trader).Return(maxPositionCap).Times(1) + + input := getValidateCancelLimitOrderV2Input(longOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, ErrNotOverPositionCap.Error(), output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + assert.Equal(t, unfilledAmount, output.Res.UnfilledAmount) + }) + t.Run("return error for a shortOrder", func(t *testing.T) { + shortOrder := getOrder(ammIndex, trader, shortBaseAssetQuantity, price, salt, reduceOnly, postOnly) + orderHash := getOrderHash(shortOrder) + unfilledAmount := hu.Div(shortOrder.BaseAssetQuantity, big.NewInt(2)) + + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) + mockBibliophile.EXPECT().IsValidator(shortOrder.Trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(hu.Sub(shortOrder.BaseAssetQuantity, unfilledAmount)).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPositionCap(shortOrder.AmmIndex.Int64(), trader).Return(maxPositionCap).Times(1) + + input := getValidateCancelLimitOrderV2Input(shortOrder, trader, assertLowMargin, assertOverPositionCap) + output := ValidateCancelLimitOrderV2(mockBibliophile, &input) + assert.Equal(t, ErrNotOverPositionCap.Error(), output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.OrderHash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + assert.Equal(t, unfilledAmount, output.Res.UnfilledAmount) + }) + }) + }) + }) }) }) } From 64f4ea2e57c46449bfa1fdec29e7041a7396a65d Mon Sep 17 00:00:00 2001 From: Atul Agarwal <21087753+asquare08@users.noreply.github.com> Date: Tue, 19 Mar 2024 00:15:36 +0530 Subject: [PATCH 12/14] add position cap tests for juror and jurorv2 --- precompile/contracts/juror/ioc_orders_test.go | 35 +++ precompile/contracts/juror/limit_orders.go | 5 +- .../contracts/juror/limit_orders_test.go | 202 +++++++++++++++++- .../contracts/juror/matching_validation.go | 2 +- .../contracts/jurorv2/matching_validation.go | 9 + .../jurorv2/matching_validation_test.go | 21 ++ 6 files changed, 269 insertions(+), 5 deletions(-) diff --git a/precompile/contracts/juror/ioc_orders_test.go b/precompile/contracts/juror/ioc_orders_test.go index b939eb79ff..32ecb61361 100644 --- a/precompile/contracts/juror/ioc_orders_test.go +++ b/precompile/contracts/juror/ioc_orders_test.go @@ -417,6 +417,41 @@ func TestValidatePlaceIOCOrder(t *testing.T) { }) }) }) + + t.Run("position cap reached", func(t *testing.T) { + mockBibliophile := b.NewMockBibliophileClient(ctrl) + mockBibliophile.EXPECT().IsTradingAuthority(trader, common.Address{1}).Return(true) + mockBibliophile.EXPECT().GetTimeStamp().Return(uint64(1000)) + mockBibliophile.EXPECT().IOC_GetExpirationCap().Return(big.NewInt(5)) + mockBibliophile.EXPECT().GetMinSizeRequirement(int64(0)).Return(big.NewInt(5)) + + order := IImmediateOrCancelOrdersOrder{ + OrderType: 1, + ExpireAt: big.NewInt(1004), + AmmIndex: big.NewInt(0), + Trader: trader, + BaseAssetQuantity: big.NewInt(10), + Price: big.NewInt(100), + Salt: big.NewInt(13), + ReduceOnly: true, + } + hash, _ := IImmediateOrCancelOrdersOrderToIOCOrder(&order).Hash() + + mockBibliophile.EXPECT().IOC_GetOrderStatus(hash).Return(int64(0)) + mockBibliophile.EXPECT().HasReferrer(trader).Return(true) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(int64(0)).Return(common.Address{101}) + mockBibliophile.EXPECT().GetSize(common.Address{101}, &trader).Return(big.NewInt(-15)) + mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, order.AmmIndex).Return(big.NewInt(0)) + mockBibliophile.EXPECT().GetPriceMultiplier(common.Address{101}).Return(big.NewInt(10)) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetPositionCap(int64(0), order.Trader).Return(order.BaseAssetQuantity).Times(1) + + testValidatePlaceIOCOrderTestCase(t, mockBibliophile, ValidatePlaceIOCOrderTestCase{ + Order: order, + Sender: common.Address{1}, + Error: nil, + }) + }) } func TestValidateExecuteIOCOrder(t *testing.T) { diff --git a/precompile/contracts/juror/limit_orders.go b/precompile/contracts/juror/limit_orders.go index 1840f44e8a..bcadec4332 100644 --- a/precompile/contracts/juror/limit_orders.go +++ b/precompile/contracts/juror/limit_orders.go @@ -64,7 +64,7 @@ func ValidatePlaceLimitOrder(bibliophile b.BibliophileClient, inputStruct *Valid } if bibliophile.GetPrecompileVersion(common.HexToAddress(SelfAddress)).Cmp(big.NewInt(1)) >= 0 { - if isOverPositionCap(bibliophile, trader, order.AmmIndex, order.BaseAssetQuantity) { + if isOverPositionCap(bibliophile, trader, order.AmmIndex, order.BaseAssetQuantity, posSize) { response.Err = ErrOverPositionCap.Error() return } @@ -190,8 +190,7 @@ func GetLimitOrderHashFromContractStruct(o *ILimitOrderBookOrder) (common.Hash, return ILimitOrderBookOrderToLimitOrder(o).Hash() } -func isOverPositionCap(bibliophile b.BibliophileClient, trader common.Address, ammIndex *big.Int, baseAssetQuantity *big.Int) bool { - posSize := bibliophile.GetSize(bibliophile.GetMarketAddressFromMarketID(ammIndex.Int64()), &trader) +func isOverPositionCap(bibliophile b.BibliophileClient, trader common.Address, ammIndex *big.Int, baseAssetQuantity *big.Int, posSize *big.Int) bool { posCap := bibliophile.GetPositionCap(ammIndex.Int64(), trader) longOpenOrdersAmount := bibliophile.GetLongOpenOrdersAmount(trader, ammIndex) if baseAssetQuantity.Sign() == 1 { diff --git a/precompile/contracts/juror/limit_orders_test.go b/precompile/contracts/juror/limit_orders_test.go index 2fcbfe512c..8e93613a15 100644 --- a/precompile/contracts/juror/limit_orders_test.go +++ b/precompile/contracts/juror/limit_orders_test.go @@ -226,6 +226,50 @@ func TestValidatePlaceLimitOrder(t *testing.T) { }) }) }) + t.Run("when isOverPositionCap returns true", func(t *testing.T) { + t.Run("it returns error for a longOrder", func(t *testing.T) { + longOrder := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, reduceOnly, postOnly) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetMinSizeRequirement(longOrder.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) + orderHash, err := GetLimitOrderHashFromContractStruct(&longOrder) + if err != nil { + panic("error in getting longOrder hash") + } + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Invalid)).Times(1) + mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(hu.Div(longOrder.BaseAssetQuantity, big.NewInt(2))).Times(1) + mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, longOrder.AmmIndex).Return(hu.Div(longOrder.BaseAssetQuantity, big.NewInt(2))).Times(1) + mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetPositionCap(longOrder.AmmIndex.Int64(), trader).Return(hu.Sub(hu.Mul(longOrder.BaseAssetQuantity, big.NewInt(2)), big.NewInt(1))).Times(1) + + output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) + assert.Equal(t, ErrOverPositionCap.Error(), output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + }) + + t.Run("it returns error for a shortOrder", func(t *testing.T) { + shortOrder := getOrder(ammIndex, trader, shortBaseAssetQuantity, price, salt, reduceOnly, postOnly) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetMinSizeRequirement(shortOrder.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) + orderHash, err := GetLimitOrderHashFromContractStruct(&shortOrder) + if err != nil { + panic("error in getting shortOrder hash") + } + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Invalid)).Times(1) + mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(hu.Div(shortOrder.BaseAssetQuantity, big.NewInt(2))).Times(1) + mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(hu.Div(shortOrder.BaseAssetQuantity, big.NewInt(2))).Times(1) + mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetPositionCap(shortOrder.AmmIndex.Int64(), trader).Return(hu.Sub(hu.Mul(shortOrder.BaseAssetQuantity, big.NewInt(2)), big.NewInt(1))).Times(1) + + output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) + assert.Equal(t, ErrOverPositionCap.Error(), output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + }) + }) }) }) }) @@ -248,7 +292,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Invalid)).Times(1) mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) - mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).AnyTimes() + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, ErrReduceOnlyBaseAssetQuantityInvalid.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -268,6 +312,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Invalid)).Times(1) mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, ErrReduceOnlyBaseAssetQuantityInvalid.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -289,6 +334,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Invalid)).Times(1) mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, ErrReduceOnlyBaseAssetQuantityInvalid.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -308,6 +354,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Invalid)).Times(1) mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, ErrReduceOnlyBaseAssetQuantityInvalid.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -336,6 +383,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, longOrder.AmmIndex).Return(longOpenOrdersAmount).Times(1) mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, ErrOpenOrders.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -361,6 +409,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(shortOpenOrdersAmount).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, ErrOpenOrders.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -388,6 +437,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, longOrder.AmmIndex).Return(reduceOnlyAmount).Times(1) mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, ErrNetReduceOnlyAmountExceeded.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -411,6 +461,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, shortOrder.AmmIndex).Return(reduceOnlyAmount).Times(1) mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, ErrNetReduceOnlyAmountExceeded.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -439,6 +490,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -464,6 +516,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -496,6 +549,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, ErrCrossingMarket.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -519,6 +573,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, ErrCrossingMarket.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -546,6 +601,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, ErrCrossingMarket.Error(), output.Err) @@ -569,6 +625,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, ErrCrossingMarket.Error(), output.Err) @@ -601,6 +658,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -628,6 +686,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -656,6 +715,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Invalid)).Times(1) mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, longOrder.AmmIndex).Return(reduceOnlyAmount).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, ErrOpenReduceOnlyOrders.Error(), output.Err) @@ -677,6 +737,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Invalid)).Times(1) mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, shortOrder.AmmIndex).Return(reduceOnlyAmount).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, ErrOpenReduceOnlyOrders.Error(), output.Err) @@ -713,6 +774,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { availableMargin := big.NewInt(0) mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, ErrInsufficientMargin.Error(), output.Err) @@ -738,6 +800,35 @@ func TestValidatePlaceLimitOrder(t *testing.T) { availableMargin := hu.Sub(requiredMargin, big.NewInt(1)) mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) + + output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) + assert.Equal(t, ErrInsufficientMargin.Error(), output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + assert.Equal(t, big.NewInt(0), output.Res.ReserveAmount) + }) + + t.Run("when available margin is one less than requiredMargin and precompileVersion = 1", func(t *testing.T) { + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetMinSizeRequirement(longOrder.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) + orderHash, err := GetLimitOrderHashFromContractStruct(&longOrder) + if err != nil { + panic("error in getting longOrder hash") + } + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Invalid)).Times(1) + mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) + mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, longOrder.AmmIndex).Return(reduceOnlyAmount).Times(1) + + quoteAsset := big.NewInt(0).Abs(hu.Div(hu.Mul(longOrder.BaseAssetQuantity, upperBound), big.NewInt(1e18))) + requiredMargin := hu.Div(quoteAsset, big.NewInt(10)) // 10x leverage + mockBibliophile.EXPECT().GetRequiredMargin(longOrder.BaseAssetQuantity, longOrder.Price, longOrder.AmmIndex.Int64(), &trader).Return(requiredMargin).Times(1) + availableMargin := hu.Sub(requiredMargin, big.NewInt(1)) + mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(1)).Times(2) + mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPositionCap(longOrder.AmmIndex.Int64(), trader).Return(hu.Mul(longOrder.BaseAssetQuantity, big.NewInt(2))).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, ErrInsufficientMargin.Error(), output.Err) @@ -771,6 +862,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { availableMargin := big.NewInt(0) mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, ErrInsufficientMargin.Error(), output.Err) @@ -778,6 +870,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { assert.Equal(t, ammAddress, output.Res.Amm) assert.Equal(t, big.NewInt(0), output.Res.ReserveAmount) }) + t.Run("when available margin is one less than requiredMargin", func(t *testing.T) { mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) mockBibliophile.EXPECT().GetMinSizeRequirement(shortOrder.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) @@ -797,6 +890,35 @@ func TestValidatePlaceLimitOrder(t *testing.T) { availableMargin := hu.Sub(requiredMargin, big.NewInt(1)) mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) + + output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) + assert.Equal(t, ErrInsufficientMargin.Error(), output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + assert.Equal(t, big.NewInt(0), output.Res.ReserveAmount) + }) + t.Run("when available margin is one less than requiredMargin and precompileVersion = 1", func(t *testing.T) { + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetMinSizeRequirement(shortOrder.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) + orderHash, err := GetLimitOrderHashFromContractStruct(&shortOrder) + if err != nil { + panic("error in getting shortOrder hash") + } + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Invalid)).Times(1) + mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) + mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, shortOrder.AmmIndex).Return(reduceOnlyAmount).Times(1) + + quoteAsset := big.NewInt(0).Abs(hu.Div(hu.Mul(shortOrder.BaseAssetQuantity, upperBound), big.NewInt(1e18))) + requiredMargin := hu.Div(quoteAsset, big.NewInt(10)) // 10x leverage + mockBibliophile.EXPECT().GetRequiredMargin(shortOrder.BaseAssetQuantity, shortOrder.Price, shortOrder.AmmIndex.Int64(), &trader).Return(requiredMargin).Times(1) + availableMargin := hu.Sub(requiredMargin, big.NewInt(1)) + mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(1)).Times(2) + mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPositionCap(shortOrder.AmmIndex.Int64(), trader).Return(hu.Sub(hu.Mul(shortOrder.BaseAssetQuantity, big.NewInt(-2)), big.NewInt(1))).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, ErrInsufficientMargin.Error(), output.Err) @@ -836,6 +958,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -867,6 +990,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -909,6 +1033,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, ErrCrossingMarket.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -941,6 +1066,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, ErrCrossingMarket.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -976,6 +1102,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, ErrCrossingMarket.Error(), output.Err) @@ -1008,6 +1135,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, ErrCrossingMarket.Error(), output.Err) @@ -1045,6 +1173,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -1077,6 +1206,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, "", output.Err) @@ -1086,6 +1216,76 @@ func TestValidatePlaceLimitOrder(t *testing.T) { }) }) }) + + t.Run("when trader has available margin for order and precompileVersion = 1", func(t *testing.T) { + asksHead := big.NewInt(0).Add(price, big.NewInt(1)) + bidsHead := big.NewInt(0).Sub(price, big.NewInt(1)) + reduceOnlyAmount := big.NewInt(0) + positionSize := big.NewInt(0) + t.Run("for a longOrder it returns no error and 0 as reserveAmount", func(t *testing.T) { + longOrder := getOrder(ammIndex, trader, longBaseAssetQuantity, price, salt, false, true) + quoteAsset := big.NewInt(0).Abs(hu.Div(hu.Mul(longOrder.BaseAssetQuantity, longOrder.Price), big.NewInt(1e18))) + requiredMargin := hu.Div(quoteAsset, big.NewInt(10)) // 10x leverage + availableMargin := hu.Add(requiredMargin, big.NewInt(1)) + + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetMinSizeRequirement(longOrder.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) + orderHash, err := GetLimitOrderHashFromContractStruct(&longOrder) + if err != nil { + panic("error in getting longOrder hash") + } + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Invalid)).Times(1) + mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) + mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, longOrder.AmmIndex).Return(reduceOnlyAmount).Times(1) + mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) + mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) + mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(1)).Times(2) + mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPositionCap(longOrder.AmmIndex.Int64(), trader).Return(hu.Mul(longOrder.BaseAssetQuantity, big.NewInt(2))).Times(1) + mockBibliophile.EXPECT().GetRequiredMargin(longOrder.BaseAssetQuantity, longOrder.Price, longOrder.AmmIndex.Int64(), &trader).Return(requiredMargin).Times(1) + output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) + assert.Equal(t, "", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + assert.Equal(t, requiredMargin, output.Res.ReserveAmount) + }) + t.Run("for a shortOrder it returns no error and 0 as reserveAmount", func(t *testing.T) { + shortOrder := getOrder(ammIndex, trader, shortBaseAssetQuantity, price, salt, false, true) + quoteAsset := big.NewInt(0).Abs(hu.Div(hu.Mul(shortOrder.BaseAssetQuantity, shortOrder.Price), big.NewInt(1e18))) + requiredMargin := hu.Div(quoteAsset, big.NewInt(10)) // 10x leverage + availableMargin := hu.Add(requiredMargin, big.NewInt(1)) + + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetMinSizeRequirement(shortOrder.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) + orderHash, err := GetLimitOrderHashFromContractStruct(&shortOrder) + if err != nil { + panic("error in getting shortOrder hash") + } + mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Invalid)).Times(1) + mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) + mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, shortOrder.AmmIndex).Return(reduceOnlyAmount).Times(1) + mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) + mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) + mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(1)).Times(2) + mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetPositionCap(shortOrder.AmmIndex.Int64(), trader).Return(hu.Mul(shortOrder.BaseAssetQuantity, big.NewInt(-2))).Times(1) + mockBibliophile.EXPECT().GetRequiredMargin(shortOrder.BaseAssetQuantity, shortOrder.Price, shortOrder.AmmIndex.Int64(), &trader).Return(requiredMargin).Times(1) + output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) + assert.Equal(t, "", output.Err) + assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) + assert.Equal(t, ammAddress, output.Res.Amm) + assert.Equal(t, requiredMargin, output.Res.ReserveAmount) + }) + }) }) }) }) diff --git a/precompile/contracts/juror/matching_validation.go b/precompile/contracts/juror/matching_validation.go index 93caf8b206..4df229c423 100644 --- a/precompile/contracts/juror/matching_validation.go +++ b/precompile/contracts/juror/matching_validation.go @@ -71,7 +71,7 @@ var ( ErrOpenReduceOnlyOrders = errors.New("open reduce only orders") ErrNoTradingAuthority = errors.New("no trading authority") ErrNoReferrer = errors.New("no referrer") - ErrOverPositionCap = errors.New("position size over max cap") + ErrOverPositionCap = errors.New("position size over max cap") ) type BadElement uint8 diff --git a/precompile/contracts/jurorv2/matching_validation.go b/precompile/contracts/jurorv2/matching_validation.go index 63154ed8c0..9611b6416f 100644 --- a/precompile/contracts/jurorv2/matching_validation.go +++ b/precompile/contracts/jurorv2/matching_validation.go @@ -75,6 +75,7 @@ var ( ErrOpenReduceOnlyOrders = errors.New("open reduce only orders") ErrNoTradingAuthority = errors.New("no trading authority") ErrNoReferrer = errors.New("no referrer") + ErrOverPositionCap = errors.New("position size over max cap") ) type BadElement uint8 @@ -493,6 +494,14 @@ func validateLimitOrderLike(bibliophile b.BibliophileClient, order *hu.BaseOrder } else { return errors.New("invalid side") } + + if bibliophile.GetPrecompileVersion(common.HexToAddress(SelfAddress)).Cmp(big.NewInt(1)) >= 0 { + posSize := bibliophile.GetSize(market, &order.Trader) + posCap := bibliophile.GetPositionCap(order.AmmIndex.Int64(), order.Trader) + if hu.Abs(hu.Add(posSize, order.BaseAssetQuantity)).Cmp(posCap) == 1 { + return ErrOverPositionCap + } + } return nil } diff --git a/precompile/contracts/jurorv2/matching_validation_test.go b/precompile/contracts/jurorv2/matching_validation_test.go index 41c98844da..982452b33a 100644 --- a/precompile/contracts/jurorv2/matching_validation_test.go +++ b/precompile/contracts/jurorv2/matching_validation_test.go @@ -96,12 +96,22 @@ func TestValidateLimitOrderLike(t *testing.T) { start := new(big.Int).Neg(fillAmount).Int64() for i := start; i > start-5; i-- { mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(i)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, &badOrder, filledAmount, Placed, Long, fillAmount) assert.Nil(t, err) } }) + t.Run("position cap reached", func(t *testing.T) { + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetSize(common.Address{}, &order.Trader).Return(big.NewInt(1)).Times(1) + mockBibliophile.EXPECT().GetPositionCap(int64(0), order.Trader).Return(order.BaseAssetQuantity).Times(1) + err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Long, fillAmount) + assert.EqualError(t, err, ErrOverPositionCap.Error()) + }) + t.Run("all conditions met", func(t *testing.T) { + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Long, fillAmount) assert.Nil(t, err) }) @@ -174,12 +184,14 @@ func TestValidateLimitOrderLike(t *testing.T) { start := new(big.Int).Abs(fillAmount).Int64() for i := start; i < start+5; i++ { mockBibliophile.EXPECT().GetSize(gomock.Any(), gomock.Any()).Return(big.NewInt(i)).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, &badOrder, filledAmount, Placed, Short, fillAmount) assert.Nil(t, err) } }) t.Run("all conditions met", func(t *testing.T) { + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Short, fillAmount) assert.Nil(t, err) }) @@ -292,6 +304,7 @@ func testValidateExecuteSignedOrder(t *testing.T, mockBibliophile *b.MockBibliop mockBibliophile.EXPECT().GetSignedOrderStatus(h).Return(int64(0)).Times(1) // Invalid mockBibliophile.EXPECT().GetSignedOrderFilledAmount(h).Return(filledAmount).Times(1) mockBibliophile.EXPECT().HasReferrer(order.Trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) m, err := validateOrder(mockBibliophile, ob.Signed, encodedOrder, side, fillAmount) assert.Nil(t, err) @@ -340,6 +353,7 @@ func TestValidateExecuteLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(1)).Times(1) // placed mockBibliophile.EXPECT().GetBlockPlaced(orderHash).Return(blockPlaced).Times(1) // placed mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order.AmmIndex.Int64()).Return(marketAddress).Times(1) // placed + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) m, err := validateOrder(mockBibliophile, ob.Limit, encodedOrder, Long, fillAmount) assert.Nil(t, err) @@ -403,6 +417,7 @@ func TestValidateExecuteIOCOrder(t *testing.T) { mockBibliophile.EXPECT().IOC_GetBlockPlaced(orderHash).Return(blockPlaced).Times(1) // placed mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order.AmmIndex.Int64()).Return(marketAddress).Times(1) // placed mockBibliophile.EXPECT().GetTimeStamp().Return(uint64(60)).Times(1) // placed + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) m, err := validateOrder(mockBibliophile, ob.IOC, encodedOrder, Long, fillAmount) assert.Nil(t, err) @@ -1034,6 +1049,7 @@ func TestValidateOrdersAndDetermineFillPrice(t *testing.T) { mockBibliophile.EXPECT().GetOrderStatus(order1Hash).Return(int64(1)) // placed mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(common.Address{102}) mockBibliophile.EXPECT().GetBlockPlaced(order1Hash).Return(big.NewInt(12)) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) testCase := ValidateOrdersAndDetermineFillPriceTestCase{ Order0: order0, Order1: order1, @@ -1082,6 +1098,7 @@ func TestValidateOrdersAndDetermineFillPrice(t *testing.T) { mockBibliophile.EXPECT().GetOrderStatus(order1Hash).Return(int64(1)) // placed mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order1.AmmIndex.Int64()).Return(common.Address{101}) mockBibliophile.EXPECT().GetBlockPlaced(order1Hash).Return(big.NewInt(12)) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) testCase := ValidateOrdersAndDetermineFillPriceTestCase{ Order0: order0, Order1: order1, @@ -1132,6 +1149,7 @@ func TestValidateOrdersAndDetermineFillPrice(t *testing.T) { mockBibliophile.EXPECT().GetBlockPlaced(order1Hash).Return(big.NewInt(12)) mockBibliophile.EXPECT().GetMinSizeRequirement(order1.AmmIndex.Int64()).Return(big.NewInt(5)) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) testCase := ValidateOrdersAndDetermineFillPriceTestCase{ Order0: order0, @@ -1184,6 +1202,7 @@ func TestValidateOrdersAndDetermineFillPrice(t *testing.T) { mockBibliophile.EXPECT().GetMinSizeRequirement(order1.AmmIndex.Int64()).Return(big.NewInt(1)) mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(order1.AmmIndex.Int64()).Return(big.NewInt(110), big.NewInt(90)) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(2) testCase := ValidateOrdersAndDetermineFillPriceTestCase{ Order0: order0, @@ -1300,6 +1319,7 @@ func TestValidateLiquidationOrderAndDetermineFillPrice(t *testing.T) { mockBibliophile.EXPECT().GetSize(common.Address{101}, &trader).Return(big.NewInt(10)) mockBibliophile.EXPECT().GetMinSizeRequirement(order.AmmIndex.Int64()).Return(big.NewInt(5)) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) testCase := ValidateLiquidationOrderAndDetermineFillPriceTestCase{ Order: order, @@ -1334,6 +1354,7 @@ func TestValidateLiquidationOrderAndDetermineFillPrice(t *testing.T) { mockBibliophile.EXPECT().GetMinSizeRequirement(order.AmmIndex.Int64()).Return(big.NewInt(1)) mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(order.AmmIndex.Int64()).Return(big.NewInt(110), big.NewInt(90)) mockBibliophile.EXPECT().GetAcceptableBoundsForLiquidation(order.AmmIndex.Int64()).Return(big.NewInt(110), big.NewInt(90)) + mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(0)).Times(1) testCase := ValidateLiquidationOrderAndDetermineFillPriceTestCase{ Order: order, From 3e524b79e766304a4e2957917f7770f8da52859c Mon Sep 17 00:00:00 2001 From: Atul Agarwal <21087753+asquare08@users.noreply.github.com> Date: Fri, 22 Mar 2024 02:19:26 +0530 Subject: [PATCH 13/14] resolve comments --- .../orderbook/hubbleutils/data_structures.go | 8 +-- .../evm/orderbook/hubbleutils/margin_math.go | 14 ++--- .../orderbook/hubbleutils/margin_math_test.go | 6 +- precompile/contracts/bibliophile/amm.go | 60 ++++++++----------- .../contracts/bibliophile/clearing_house.go | 39 ++++-------- precompile/contracts/bibliophile/client.go | 12 ++-- .../contracts/bibliophile/client_mock.go | 36 +++++------ precompile/contracts/juror/contract.go | 1 - .../contracts/juror/limit_orders_test.go | 24 ++++---- .../contracts/juror/matching_validation.go | 5 +- .../contracts/juror/notional_position.go | 2 +- .../contracts/jurorv2/matching_validation.go | 2 +- .../jurorv2/matching_validation_test.go | 2 +- .../contracts/traderviewer/limit_orders.go | 10 ++-- .../traderviewer/limit_orders_test.go | 24 +++----- precompile/contracts/traderviewer/viewer.go | 3 +- 16 files changed, 108 insertions(+), 140 deletions(-) diff --git a/plugin/evm/orderbook/hubbleutils/data_structures.go b/plugin/evm/orderbook/hubbleutils/data_structures.go index 04912b27dc..bed91c516b 100644 --- a/plugin/evm/orderbook/hubbleutils/data_structures.go +++ b/plugin/evm/orderbook/hubbleutils/data_structures.go @@ -18,15 +18,13 @@ const ( Min_Allowable_Margin ) -type MarginType = uint8 - const ( - Cross_Margin MarginType = iota - Isolated_Margin + Cross MarginMode = iota + Isolated ) type AccountPreferences struct { - MarginType MarginType + MarginMode MarginMode MarginFraction *big.Int } diff --git a/plugin/evm/orderbook/hubbleutils/margin_math.go b/plugin/evm/orderbook/hubbleutils/margin_math.go index 6551e379e2..747be36010 100644 --- a/plugin/evm/orderbook/hubbleutils/margin_math.go +++ b/plugin/evm/orderbook/hubbleutils/margin_math.go @@ -26,11 +26,11 @@ type HubbleState struct { } type UserState struct { - Positions map[Market]*Position - ReduceOnlyAmounts []*big.Int - Margins []*big.Int - PendingFunding *big.Int - ReservedMargin *big.Int + Positions map[Market]*Position + ReduceOnlyAmounts []*big.Int + Margins []*big.Int + PendingFunding *big.Int + ReservedMargin *big.Int AccountPreferences map[Market]*AccountPreferences } @@ -132,7 +132,7 @@ func GetCrossMarginAccountData(hState *HubbleState, userState *UserState) (*big. requiredMargin := big.NewInt(0) for _, market := range hState.ActiveMarkets { - if userState.AccountPreferences[market].MarginType == Cross_Margin { + if userState.AccountPreferences[market].MarginMode == Cross { _notionalPosition, _unrealizedPnl, _requiredMargin := GetTraderPositionDetails(userState.Positions[market], hState.OraclePrices[market], userState.AccountPreferences[market].MarginFraction) notionalPosition.Add(notionalPosition, _notionalPosition) unrealizedPnl.Add(unrealizedPnl, _unrealizedPnl) @@ -148,7 +148,7 @@ func GetTraderPositionDetails(position *Position, oraclePrice *big.Int, marginFr } // based on oracle price, - notionalPosition, unrealizedPnl, _ := GetPositionMetadata( + notionalPosition, unrealizedPnl, _ := GetPositionMetadata( oraclePrice, position.OpenNotional, position.Size, diff --git a/plugin/evm/orderbook/hubbleutils/margin_math_test.go b/plugin/evm/orderbook/hubbleutils/margin_math_test.go index 33e4342232..d8cffd4275 100644 --- a/plugin/evm/orderbook/hubbleutils/margin_math_test.go +++ b/plugin/evm/orderbook/hubbleutils/margin_math_test.go @@ -56,11 +56,11 @@ var userState = &UserState{ ReservedMargin: big.NewInt(60 * 1e6), // 60 AccountPreferences: map[Market]*AccountPreferences{ 0: { - MarginType: Cross_Margin, + MarginMode: Cross, MarginFraction: big.NewInt(0.2 * 1e6), // 0.2 }, 1: { - MarginType: Isolated_Margin, + MarginMode: Isolated, MarginFraction: big.NewInt(0.1 * 1e6), // 0.1 }, }, @@ -264,7 +264,7 @@ func TestGetNotionalPositionAndRequiredMargin(t *testing.T) { }) t.Run("both markets in cross mode", func(t *testing.T) { - userState.AccountPreferences[1].MarginType = Cross_Margin + userState.AccountPreferences[1].MarginMode = Cross notionalPosition, margin, requiredMargin := GetNotionalPositionAndRequiredMargin(_hState, userState) expectedNotionalPosition := big.NewInt(0) expectedRequiredMargin := big.NewInt(0) diff --git a/precompile/contracts/bibliophile/amm.go b/precompile/contracts/bibliophile/amm.go index 5a1addab08..c06555b40f 100644 --- a/precompile/contracts/bibliophile/amm.go +++ b/precompile/contracts/bibliophile/amm.go @@ -10,26 +10,26 @@ import ( ) const ( - VAR_POSITIONS_SLOT int64 = 1 - VAR_CUMULATIVE_PREMIUM_FRACTION int64 = 2 - MAX_ORACLE_SPREAD_RATIO_SLOT int64 = 3 - MAX_LIQUIDATION_RATIO_SLOT int64 = 4 - MIN_SIZE_REQUIREMENT_SLOT int64 = 5 - UNDERLYING_ASSET_SLOT int64 = 6 - MAX_LIQUIDATION_PRICE_SPREAD int64 = 11 - MULTIPLIER_SLOT int64 = 12 - IMPACT_MARGIN_NOTIONAL_SLOT int64 = 19 - LAST_TRADE_PRICE_SLOT int64 = 20 - BIDS_SLOT int64 = 21 - ASKS_SLOT int64 = 22 - BIDS_HEAD_SLOT int64 = 23 - ASKS_HEAD_SLOT int64 = 24 - TRADE_MARGIN_FRACTION_SLOT int64 = 28 - LIQUIDATION_MARGIN_FRACTION_SLOT int64 = 29 - ISOLATED_TRADE_MARGIN_FRACTION_SLOT int64 = 30 + VAR_POSITIONS_SLOT int64 = 1 + VAR_CUMULATIVE_PREMIUM_FRACTION int64 = 2 + MAX_ORACLE_SPREAD_RATIO_SLOT int64 = 3 + MAX_LIQUIDATION_RATIO_SLOT int64 = 4 + MIN_SIZE_REQUIREMENT_SLOT int64 = 5 + UNDERLYING_ASSET_SLOT int64 = 6 + MAX_LIQUIDATION_PRICE_SPREAD int64 = 11 + MULTIPLIER_SLOT int64 = 12 + IMPACT_MARGIN_NOTIONAL_SLOT int64 = 19 + LAST_TRADE_PRICE_SLOT int64 = 20 + BIDS_SLOT int64 = 21 + ASKS_SLOT int64 = 22 + BIDS_HEAD_SLOT int64 = 23 + ASKS_HEAD_SLOT int64 = 24 + TRADE_MARGIN_FRACTION_SLOT int64 = 28 + LIQUIDATION_MARGIN_FRACTION_SLOT int64 = 29 + ISOLATED_TRADE_MARGIN_FRACTION_SLOT int64 = 30 ISOLATED_LIQUIDATION_MARGIN_FRACTION_SLOT int64 = 31 - ACCOUNT_PREFERENCES_SLOT int64 = 33 - MAX_POSITION_CAP_SLOT int64 = 34 + ACCOUNT_PREFERENCES_SLOT int64 = 33 + MAX_POSITION_CAP_SLOT int64 = 34 ) // AMM State @@ -172,11 +172,11 @@ func accountPreferencesSlot(trader *common.Address) *big.Int { return new(big.Int).SetBytes(crypto.Keccak256(append(common.LeftPadBytes(trader.Bytes(), 32), common.LeftPadBytes(big.NewInt(ACCOUNT_PREFERENCES_SLOT).Bytes(), 32)...))) } -func getMarginType(stateDB contract.StateDB, market common.Address, trader *common.Address) uint8 { +func getMarginMode(stateDB contract.StateDB, market common.Address, trader *common.Address) uint8 { return uint8(stateDB.GetState(market, common.BigToHash(accountPreferencesSlot(trader))).Big().Uint64()) } -func traderMarginFraction(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { +func marginFraction(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { return stateDB.GetState(market, common.BigToHash(new(big.Int).Add(accountPreferencesSlot(trader), big.NewInt(1)))).Big() } @@ -186,7 +186,7 @@ func getMaxPositionCap(stateDB contract.StateDB, market common.Address) *big.Int func getMarginFractionByMode(stateDB contract.StateDB, market common.Address, trader *common.Address, mode uint8) *big.Int { if mode == hu.Maintenance_Margin { - if (getMarginType(stateDB, market, trader) == hu.Isolated_Margin) { + if getMarginMode(stateDB, market, trader) == hu.Isolated { return getIsolatedLiquidationMarginFraction(stateDB, market) } else { return getLiquidationMarginFraction(stateDB, market) @@ -198,26 +198,16 @@ func getMarginFractionByMode(stateDB contract.StateDB, market common.Address, tr } func getTraderMarginFraction(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { - traderMarginFraction_ := traderMarginFraction(stateDB, market, trader) - if (traderMarginFraction_.Cmp(big.NewInt(0)) != 0) { + traderMarginFraction_ := marginFraction(stateDB, market, trader) + if traderMarginFraction_.Sign() != 0 { return traderMarginFraction_ - } else if (getMarginType(stateDB, market, trader) == hu.Isolated_Margin) { + } else if getMarginMode(stateDB, market, trader) == hu.Isolated { return getIsolatedTradeMarginFraction(stateDB, market) } else { return getTradeMarginFraction(stateDB, market) } } -func getRequiredMargin(stateDB contract.StateDB, baseAsset *big.Int, price *big.Int, marketId int64, trader *common.Address) *big.Int { - quoteAsset := hu.Div1e18(hu.Mul(hu.Abs(baseAsset), price)) - return getRequiredMarginForQuote(stateDB, GetMarketAddressFromMarketID(marketId, stateDB), trader, quoteAsset) -} - -func getRequiredMarginForQuote(stateDB contract.StateDB, market common.Address, trader *common.Address, quote *big.Int) *big.Int { - marginFraction := getTraderMarginFraction(stateDB, market, trader) - return hu.Div1e6(hu.Mul(quote, marginFraction)) -} - func getPositionCap(stateDB contract.StateDB, market int64, trader *common.Address) *big.Int { marketAddress := GetMarketAddressFromMarketID(market, stateDB) maxPositionCap := getMaxPositionCap(stateDB, marketAddress) diff --git a/precompile/contracts/bibliophile/clearing_house.go b/precompile/contracts/bibliophile/clearing_house.go index 8a78b2f9b7..1a4a0733e8 100644 --- a/precompile/contracts/bibliophile/clearing_house.go +++ b/precompile/contracts/bibliophile/clearing_house.go @@ -72,7 +72,7 @@ type GetTraderDataForMarketOutput struct { RequiredMargin *big.Int UnrealizedPnl *big.Int PendingFunding *big.Int - IsIsolated bool + IsIsolated bool } func getNotionalPositionAndMargin(stateDB contract.StateDB, input *GetNotionalPositionAndMarginInput, upgradeVersion hu.UpgradeVersion) GetNotionalPositionAndMarginOutput { @@ -114,38 +114,24 @@ func getNotionalPositionAndMargin(stateDB contract.StateDB, input *GetNotionalPo } func getNotionalPositionAndRequiredMargin(stateDB contract.StateDB, input *GetNotionalPositionAndMarginInput) GetNotionalPositionAndMarginOutput { - positions, underlyingPrices, accountPreferences, activeMarketIds := getMarketsDataFromDB(stateDB, &input.Trader, input.Mode) - pendingFunding := big.NewInt(0) + margin := GetNormalizedMargin(stateDB, input.Trader) + accountData := getCrossMarginAccountData(stateDB, &input.Trader, input.Mode) if input.IncludeFundingPayments { - pendingFunding = getTotalFundingForCrossMarginPositions(stateDB, &input.Trader) + margin.Sub(margin, accountData.PendingFunding) } - notionalPosition, margin, requiredMargin := hu.GetNotionalPositionAndRequiredMargin( - &hu.HubbleState{ - Assets: GetCollaterals(stateDB), - OraclePrices: underlyingPrices, - ActiveMarkets: activeMarketIds, - }, - &hu.UserState{ - Positions: positions, - Margins: getMargins(stateDB, input.Trader), - PendingFunding: pendingFunding, - AccountPreferences: accountPreferences, - }, - ) return GetNotionalPositionAndMarginOutput{ - NotionalPosition: notionalPosition, + NotionalPosition: accountData.NotionalPosition, Margin: margin, - RequiredMargin: requiredMargin, + RequiredMargin: accountData.RequiredMargin, } } -func getCrossMarginAccountData(stateDB contract.StateDB, trader *common.Address, mode uint8, upgradeVersion hu.UpgradeVersion) GetTraderDataForMarketOutput { +func getCrossMarginAccountData(stateDB contract.StateDB, trader *common.Address, mode uint8) GetTraderDataForMarketOutput { positions, underlyingPrices, accountPreferences, activeMarketIds := getMarketsDataFromDB(stateDB, trader, mode) notionalPosition, requiredMargin, unrealizedPnl := hu.GetCrossMarginAccountData( &hu.HubbleState{ ActiveMarkets: activeMarketIds, OraclePrices: underlyingPrices, - UpgradeVersion: upgradeVersion, }, &hu.UserState{ Positions: positions, @@ -153,7 +139,7 @@ func getCrossMarginAccountData(stateDB contract.StateDB, trader *common.Address, }, ) pendingFunding := getTotalFundingForCrossMarginPositions(stateDB, trader) - return GetTraderDataForMarketOutput { + return GetTraderDataForMarketOutput{ NotionalPosition: notionalPosition, RequiredMargin: requiredMargin, UnrealizedPnl: unrealizedPnl, @@ -169,11 +155,10 @@ func getMarketsDataFromDB(stateDB contract.StateDB, trader *common.Address, mode accountPreferences = make(map[int]*hu.AccountPreferences, numMarkets) activeMarketIds = make([]int, numMarkets) for i, market := range markets { - // @todo can use `market` instead of `GetMarketAddressFromMarketID`? - positions[i] = getPosition(stateDB, GetMarketAddressFromMarketID(int64(i), stateDB), trader) + positions[i] = getPosition(stateDB, market, trader) underlyingPrices[i] = getUnderlyingPrice(stateDB, market) activeMarketIds[i] = i - accountPreferences[i].MarginType = getMarginType(stateDB, market, trader) + accountPreferences[i].MarginMode = getMarginMode(stateDB, market, trader) accountPreferences[i].MarginFraction = getMarginFractionByMode(stateDB, market, trader, mode) } return positions, underlyingPrices, accountPreferences, activeMarketIds @@ -182,7 +167,7 @@ func getMarketsDataFromDB(stateDB contract.StateDB, trader *common.Address, mode func getTotalFundingForCrossMarginPositions(stateDB contract.StateDB, trader *common.Address) *big.Int { totalFunding := big.NewInt(0) for _, market := range GetMarkets(stateDB) { - if getMarginType(stateDB, market, trader) == hu.Cross_Margin { + if getMarginMode(stateDB, market, trader) == hu.Cross { totalFunding.Add(totalFunding, getPendingFundingPayment(stateDB, market, trader)) } } @@ -196,7 +181,7 @@ func getTraderDataForMarket(stateDB contract.StateDB, trader *common.Address, ma underlyingPrice := getUnderlyingPrice(stateDB, market) notionalPosition, unrealizedPnl, requiredMargin := hu.GetTraderPositionDetails(position, underlyingPrice, marginFraction) pendingFunding := getPendingFundingPayment(stateDB, market, trader) - isIsolated := getMarginType(stateDB, market, trader) == hu.Isolated_Margin + isIsolated := getMarginMode(stateDB, market, trader) == hu.Isolated return GetTraderDataForMarketOutput{ IsIsolated: isIsolated, NotionalPosition: notionalPosition, diff --git a/precompile/contracts/bibliophile/client.go b/precompile/contracts/bibliophile/client.go index ae2b38bab0..731d29fa9f 100644 --- a/precompile/contracts/bibliophile/client.go +++ b/precompile/contracts/bibliophile/client.go @@ -53,14 +53,14 @@ type BibliophileClient interface { GetUpperAndLowerBoundForMarket(marketId int64) (*big.Int, *big.Int) GetAcceptableBoundsForLiquidation(marketId int64) (*big.Int, *big.Int) GetPositionCap(marketId int64, trader common.Address) *big.Int + GetTraderMarginFraction(market common.Address, trader *common.Address) *big.Int GetTimeStamp() uint64 GetNotionalPositionAndMargin(trader common.Address, includeFundingPayments bool, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int) GetNotionalPositionAndRequiredMargin(trader common.Address, includeFundingPayments bool, mode uint8) (*big.Int, *big.Int, *big.Int) - GetCrossMarginAccountData(trader common.Address, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int, *big.Int, *big.Int) + GetCrossMarginAccountData(trader common.Address, mode uint8) (*big.Int, *big.Int, *big.Int, *big.Int) GetTotalFundingForCrossMarginPositions(trader *common.Address) *big.Int GetTraderDataForMarket(trader common.Address, marketId int64, mode uint8) (bool, *big.Int, *big.Int, *big.Int, *big.Int) - GetRequiredMargin(baseAsset *big.Int, price *big.Int, marketId int64, trader *common.Address) *big.Int HasReferrer(trader common.Address) bool GetActiveMarketsCount() int64 @@ -224,8 +224,8 @@ func (b *bibliophileClient) GetNotionalPositionAndRequiredMargin(trader common.A return output.NotionalPosition, output.Margin, output.RequiredMargin } -func (b *bibliophileClient) GetCrossMarginAccountData(trader common.Address, mode uint8, upgradeVersion hu.UpgradeVersion) (*big.Int, *big.Int, *big.Int, *big.Int) { - output := getCrossMarginAccountData(b.accessibleState.GetStateDB(), &trader, mode, upgradeVersion) +func (b *bibliophileClient) GetCrossMarginAccountData(trader common.Address, mode uint8) (*big.Int, *big.Int, *big.Int, *big.Int) { + output := getCrossMarginAccountData(b.accessibleState.GetStateDB(), &trader, mode) return output.NotionalPosition, output.RequiredMargin, output.UnrealizedPnl, output.PendingFunding } @@ -238,8 +238,8 @@ func (b *bibliophileClient) GetTraderDataForMarket(trader common.Address, market return output.IsIsolated, output.NotionalPosition, output.UnrealizedPnl, output.RequiredMargin, output.PendingFunding } -func (b *bibliophileClient) GetRequiredMargin(baseAsset *big.Int, price *big.Int, marketId int64, trader *common.Address) *big.Int { - return getRequiredMargin(b.accessibleState.GetStateDB(), baseAsset, price, marketId, trader) +func (b *bibliophileClient) GetTraderMarginFraction(market common.Address, trader *common.Address) *big.Int { + return getTraderMarginFraction(b.accessibleState.GetStateDB(), market, trader) } func (b *bibliophileClient) HasReferrer(trader common.Address) bool { diff --git a/precompile/contracts/bibliophile/client_mock.go b/precompile/contracts/bibliophile/client_mock.go index 2a5f0d0007..9634ae2378 100644 --- a/precompile/contracts/bibliophile/client_mock.go +++ b/precompile/contracts/bibliophile/client_mock.go @@ -165,9 +165,9 @@ func (mr *MockBibliophileClientMockRecorder) GetBlockPlaced(orderHash interface{ } // GetCrossMarginAccountData mocks base method. -func (m *MockBibliophileClient) GetCrossMarginAccountData(trader common.Address, mode uint8, upgradeVersion hubbleutils.UpgradeVersion) (*big.Int, *big.Int, *big.Int, *big.Int) { +func (m *MockBibliophileClient) GetCrossMarginAccountData(trader common.Address, mode uint8) (*big.Int, *big.Int, *big.Int, *big.Int) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetCrossMarginAccountData", trader, mode, upgradeVersion) + ret := m.ctrl.Call(m, "GetCrossMarginAccountData", trader, mode) ret0, _ := ret[0].(*big.Int) ret1, _ := ret[1].(*big.Int) ret2, _ := ret[2].(*big.Int) @@ -176,9 +176,9 @@ func (m *MockBibliophileClient) GetCrossMarginAccountData(trader common.Address, } // GetCrossMarginAccountData indicates an expected call of GetCrossMarginAccountData. -func (mr *MockBibliophileClientMockRecorder) GetCrossMarginAccountData(trader, mode, upgradeVersion interface{}) *gomock.Call { +func (mr *MockBibliophileClientMockRecorder) GetCrossMarginAccountData(trader, mode interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCrossMarginAccountData", reflect.TypeOf((*MockBibliophileClient)(nil).GetCrossMarginAccountData), trader, mode, upgradeVersion) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCrossMarginAccountData", reflect.TypeOf((*MockBibliophileClient)(nil).GetCrossMarginAccountData), trader, mode) } // GetImpactMarginNotional mocks base method. @@ -408,20 +408,6 @@ func (mr *MockBibliophileClientMockRecorder) GetReduceOnlyAmount(trader, ammInde return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReduceOnlyAmount", reflect.TypeOf((*MockBibliophileClient)(nil).GetReduceOnlyAmount), trader, ammIndex) } -// GetRequiredMargin mocks base method. -func (m *MockBibliophileClient) GetRequiredMargin(baseAsset, price *big.Int, marketId int64, trader *common.Address) *big.Int { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRequiredMargin", baseAsset, price, marketId, trader) - ret0, _ := ret[0].(*big.Int) - return ret0 -} - -// GetRequiredMargin indicates an expected call of GetRequiredMargin. -func (mr *MockBibliophileClientMockRecorder) GetRequiredMargin(baseAsset, price, marketId, trader interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRequiredMargin", reflect.TypeOf((*MockBibliophileClient)(nil).GetRequiredMargin), baseAsset, price, marketId, trader) -} - // GetShortOpenOrdersAmount mocks base method. func (m *MockBibliophileClient) GetShortOpenOrdersAmount(trader common.Address, ammIndex *big.Int) *big.Int { m.ctrl.T.Helper() @@ -538,6 +524,20 @@ func (mr *MockBibliophileClientMockRecorder) GetTraderDataForMarket(trader, mark return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTraderDataForMarket", reflect.TypeOf((*MockBibliophileClient)(nil).GetTraderDataForMarket), trader, marketId, mode) } +// GetTraderMarginFraction mocks base method. +func (m *MockBibliophileClient) GetTraderMarginFraction(market common.Address, trader *common.Address) *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTraderMarginFraction", market, trader) + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// GetTraderMarginFraction indicates an expected call of GetTraderMarginFraction. +func (mr *MockBibliophileClientMockRecorder) GetTraderMarginFraction(market, trader interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTraderMarginFraction", reflect.TypeOf((*MockBibliophileClient)(nil).GetTraderMarginFraction), market, trader) +} + // GetUpperAndLowerBoundForMarket mocks base method. func (m *MockBibliophileClient) GetUpperAndLowerBoundForMarket(marketId int64) (*big.Int, *big.Int) { m.ctrl.T.Helper() diff --git a/precompile/contracts/juror/contract.go b/precompile/contracts/juror/contract.go index 9f6bf2ba20..3957d34c66 100644 --- a/precompile/contracts/juror/contract.go +++ b/precompile/contracts/juror/contract.go @@ -475,7 +475,6 @@ func createJurorPrecompile() contract.StatefulPrecompiledContract { "validateOrdersAndDetermineFillPrice": validateOrdersAndDetermineFillPrice, "validatePlaceIOCOrder": validatePlaceIOCOrder, "validatePlaceLimitOrder": validatePlaceLimitOrder, - // @todo add getRequiredMargin } for name, function := range abiFunctionMap { diff --git a/precompile/contracts/juror/limit_orders_test.go b/precompile/contracts/juror/limit_orders_test.go index 8e93613a15..d070d19bb9 100644 --- a/precompile/contracts/juror/limit_orders_test.go +++ b/precompile/contracts/juror/limit_orders_test.go @@ -810,7 +810,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { }) t.Run("when available margin is one less than requiredMargin and precompileVersion = 1", func(t *testing.T) { - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(2) mockBibliophile.EXPECT().GetMinSizeRequirement(longOrder.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) orderHash, err := GetLimitOrderHashFromContractStruct(&longOrder) if err != nil { @@ -820,9 +820,9 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, longOrder.AmmIndex).Return(reduceOnlyAmount).Times(1) - quoteAsset := big.NewInt(0).Abs(hu.Div(hu.Mul(longOrder.BaseAssetQuantity, upperBound), big.NewInt(1e18))) - requiredMargin := hu.Div(quoteAsset, big.NewInt(10)) // 10x leverage - mockBibliophile.EXPECT().GetRequiredMargin(longOrder.BaseAssetQuantity, longOrder.Price, longOrder.AmmIndex.Int64(), &trader).Return(requiredMargin).Times(1) + quoteAsset := big.NewInt(0).Abs(hu.Div(hu.Mul(longOrder.BaseAssetQuantity, longOrder.Price), big.NewInt(1e18))) + requiredMargin := hu.Div(quoteAsset, big.NewInt(10)) // 10x leverage + mockBibliophile.EXPECT().GetTraderMarginFraction(ammAddress, &trader).Return(big.NewInt(1e5)).Times(1) // 0.1 availableMargin := hu.Sub(requiredMargin, big.NewInt(1)) mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) @@ -899,7 +899,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { assert.Equal(t, big.NewInt(0), output.Res.ReserveAmount) }) t.Run("when available margin is one less than requiredMargin and precompileVersion = 1", func(t *testing.T) { - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(2) mockBibliophile.EXPECT().GetMinSizeRequirement(shortOrder.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) orderHash, err := GetLimitOrderHashFromContractStruct(&shortOrder) if err != nil { @@ -909,9 +909,9 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, shortOrder.AmmIndex).Return(reduceOnlyAmount).Times(1) - quoteAsset := big.NewInt(0).Abs(hu.Div(hu.Mul(shortOrder.BaseAssetQuantity, upperBound), big.NewInt(1e18))) - requiredMargin := hu.Div(quoteAsset, big.NewInt(10)) // 10x leverage - mockBibliophile.EXPECT().GetRequiredMargin(shortOrder.BaseAssetQuantity, shortOrder.Price, shortOrder.AmmIndex.Int64(), &trader).Return(requiredMargin).Times(1) + quoteAsset := big.NewInt(0).Abs(hu.Div(hu.Mul(shortOrder.BaseAssetQuantity, shortOrder.Price), big.NewInt(1e18))) + requiredMargin := hu.Div(quoteAsset, big.NewInt(10)) // 10x leverage + mockBibliophile.EXPECT().GetTraderMarginFraction(ammAddress, &trader).Return(big.NewInt(1e5)).Times(1) // 0.1 availableMargin := hu.Sub(requiredMargin, big.NewInt(1)) mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) mockBibliophile.EXPECT().GetAvailableMargin(trader, hu.V1).Return(availableMargin).Times(1) @@ -1228,7 +1228,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { requiredMargin := hu.Div(quoteAsset, big.NewInt(10)) // 10x leverage availableMargin := hu.Add(requiredMargin, big.NewInt(1)) - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(2) mockBibliophile.EXPECT().GetMinSizeRequirement(longOrder.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) orderHash, err := GetLimitOrderHashFromContractStruct(&longOrder) if err != nil { @@ -1246,7 +1246,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(1)).Times(2) mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetPositionCap(longOrder.AmmIndex.Int64(), trader).Return(hu.Mul(longOrder.BaseAssetQuantity, big.NewInt(2))).Times(1) - mockBibliophile.EXPECT().GetRequiredMargin(longOrder.BaseAssetQuantity, longOrder.Price, longOrder.AmmIndex.Int64(), &trader).Return(requiredMargin).Times(1) + mockBibliophile.EXPECT().GetTraderMarginFraction(ammAddress, &trader).Return(big.NewInt(1e5)).Times(1) // 0.1 output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -1259,7 +1259,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { requiredMargin := hu.Div(quoteAsset, big.NewInt(10)) // 10x leverage availableMargin := hu.Add(requiredMargin, big.NewInt(1)) - mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(2) mockBibliophile.EXPECT().GetMinSizeRequirement(shortOrder.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) orderHash, err := GetLimitOrderHashFromContractStruct(&shortOrder) if err != nil { @@ -1278,7 +1278,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetPositionCap(shortOrder.AmmIndex.Int64(), trader).Return(hu.Mul(shortOrder.BaseAssetQuantity, big.NewInt(-2))).Times(1) - mockBibliophile.EXPECT().GetRequiredMargin(shortOrder.BaseAssetQuantity, shortOrder.Price, shortOrder.AmmIndex.Int64(), &trader).Return(requiredMargin).Times(1) + mockBibliophile.EXPECT().GetTraderMarginFraction(ammAddress, &trader).Return(big.NewInt(1e5)).Times(1) // 0.1 output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) diff --git a/precompile/contracts/juror/matching_validation.go b/precompile/contracts/juror/matching_validation.go index 4df229c423..b993b71483 100644 --- a/precompile/contracts/juror/matching_validation.go +++ b/precompile/contracts/juror/matching_validation.go @@ -442,7 +442,10 @@ func getRequiredMargin(bibliophile b.BibliophileClient, order ILimitOrderBookOrd } func getRequiredMarginNew(bibliophile b.BibliophileClient, baseAsset *big.Int, price *big.Int, marketId int64, trader *common.Address) *big.Int { - return bibliophile.GetRequiredMargin(baseAsset, price, marketId, trader) + marketAddress := bibliophile.GetMarketAddressFromMarketID(marketId) + marginFraction := bibliophile.GetTraderMarginFraction(marketAddress,trader) + quoteAsset := hu.Div1e18(hu.Mul(hu.Abs(baseAsset), price)) + return hu.Div1e6(hu.Mul(quoteAsset, marginFraction)) } func getRequiredMarginLegacy(bibliophile b.BibliophileClient, order ILimitOrderBookOrder) *big.Int { diff --git a/precompile/contracts/juror/notional_position.go b/precompile/contracts/juror/notional_position.go index 85396c1907..465353d160 100644 --- a/precompile/contracts/juror/notional_position.go +++ b/precompile/contracts/juror/notional_position.go @@ -5,7 +5,7 @@ import ( b "github.com/ava-labs/subnet-evm/precompile/contracts/bibliophile" ) /** - * Depricated. Use traderviewer.GetNotionalPositionAndMargin instead + * Deprecated. Use traderviewer.GetNotionalPositionAndMargin instead */ func GetNotionalPositionAndMargin(bibliophile b.BibliophileClient, input *GetNotionalPositionAndMarginInput) GetNotionalPositionAndMarginOutput { notionalPosition, margin := bibliophile.GetNotionalPositionAndMargin(input.Trader, input.IncludeFundingPayments, input.Mode, hu.UpgradeVersionV0orV1(bibliophile.GetTimeStamp())) diff --git a/precompile/contracts/jurorv2/matching_validation.go b/precompile/contracts/jurorv2/matching_validation.go index 9611b6416f..6f8e9b6ea9 100644 --- a/precompile/contracts/jurorv2/matching_validation.go +++ b/precompile/contracts/jurorv2/matching_validation.go @@ -498,7 +498,7 @@ func validateLimitOrderLike(bibliophile b.BibliophileClient, order *hu.BaseOrder if bibliophile.GetPrecompileVersion(common.HexToAddress(SelfAddress)).Cmp(big.NewInt(1)) >= 0 { posSize := bibliophile.GetSize(market, &order.Trader) posCap := bibliophile.GetPositionCap(order.AmmIndex.Int64(), order.Trader) - if hu.Abs(hu.Add(posSize, order.BaseAssetQuantity)).Cmp(posCap) == 1 { + if hu.Abs(hu.Add(posSize, fillAmount)).Cmp(posCap) == 1 { return ErrOverPositionCap } } diff --git a/precompile/contracts/jurorv2/matching_validation_test.go b/precompile/contracts/jurorv2/matching_validation_test.go index 982452b33a..c085712a8d 100644 --- a/precompile/contracts/jurorv2/matching_validation_test.go +++ b/precompile/contracts/jurorv2/matching_validation_test.go @@ -105,7 +105,7 @@ func TestValidateLimitOrderLike(t *testing.T) { t.Run("position cap reached", func(t *testing.T) { mockBibliophile.EXPECT().GetPrecompileVersion(common.HexToAddress(SelfAddress)).Return(big.NewInt(1)).Times(1) mockBibliophile.EXPECT().GetSize(common.Address{}, &order.Trader).Return(big.NewInt(1)).Times(1) - mockBibliophile.EXPECT().GetPositionCap(int64(0), order.Trader).Return(order.BaseAssetQuantity).Times(1) + mockBibliophile.EXPECT().GetPositionCap(int64(0), order.Trader).Return(fillAmount).Times(1) err := validateLimitOrderLike(mockBibliophile, order, filledAmount, Placed, Long, fillAmount) assert.EqualError(t, err, ErrOverPositionCap.Error()) }) diff --git a/precompile/contracts/traderviewer/limit_orders.go b/precompile/contracts/traderviewer/limit_orders.go index 3efecee12d..60e53f7c64 100644 --- a/precompile/contracts/traderviewer/limit_orders.go +++ b/precompile/contracts/traderviewer/limit_orders.go @@ -26,8 +26,7 @@ func ValidateCancelLimitOrderV2(bibliophile b.BibliophileClient, inputStruct *Va trader := order.Trader if (!assertLowMargin && trader != sender && !bibliophile.IsTradingAuthority(trader, sender)) || - (assertLowMargin && !bibliophile.IsValidator(sender)) || - (assertOverPositionCap && !bibliophile.IsValidator(sender)) { + (assertLowMargin || assertOverPositionCap) && !bibliophile.IsValidator(sender) { response.Err = ErrUnauthorizedCancellation.Error() return } @@ -51,7 +50,7 @@ func ValidateCancelLimitOrderV2(bibliophile b.BibliophileClient, inputStruct *Va } response.Res.UnfilledAmount = hu.Sub(order.BaseAssetQuantity, bibliophile.GetOrderFilledAmount(orderHash)) response.Res.Amm = bibliophile.GetMarketAddressFromMarketID(order.AmmIndex.Int64()) - if assertLowMargin && bibliophile.GetAvailableMargin(trader, hu.UpgradeVersionV0orV1(bibliophile.GetTimeStamp())).Sign() != -1 { + if assertLowMargin && bibliophile.GetAvailableMargin(trader, hu.V2).Sign() != -1 { response.Err = "Not Low Margin" return } else if assertOverPositionCap { @@ -84,5 +83,8 @@ func GetLimitOrderHashFromContractStruct(o *ILimitOrderBookOrder) (common.Hash, } func GetRequiredMargin(bibliophile b.BibliophileClient, inputStruct *GetRequiredMarginInput) *big.Int { - return bibliophile.GetRequiredMargin(inputStruct.BaseAssetQuantity, inputStruct.Price, inputStruct.AmmIndex.Int64(), &inputStruct.Trader) + marketAddress := bibliophile.GetMarketAddressFromMarketID(inputStruct.AmmIndex.Int64()) + marginFraction := bibliophile.GetTraderMarginFraction(marketAddress, &inputStruct.Trader) + quoteAsset := hu.Div1e18(hu.Mul(hu.Abs(inputStruct.BaseAssetQuantity), inputStruct.Price)) + return hu.Div1e6(hu.Mul(quoteAsset, marginFraction)) } diff --git a/precompile/contracts/traderviewer/limit_orders_test.go b/precompile/contracts/traderviewer/limit_orders_test.go index dca480b1f7..28936fe421 100644 --- a/precompile/contracts/traderviewer/limit_orders_test.go +++ b/precompile/contracts/traderviewer/limit_orders_test.go @@ -132,8 +132,7 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { orderHash := getOrderHash(longOrder) mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) - mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) - mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V1).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V2).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().IsValidator(longOrder.Trader).Return(true).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) @@ -149,8 +148,7 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { orderHash := getOrderHash(shortOrder) mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) - mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) - mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V1).Return(big.NewInt(0)).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V2).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().IsValidator(shortOrder.Trader).Return(true).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) @@ -170,8 +168,7 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { orderHash := getOrderHash(longOrder) mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) - mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) - mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V1).Return(newMargin).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V2).Return(newMargin).Times(1) mockBibliophile.EXPECT().IsValidator(longOrder.Trader).Return(true).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) @@ -187,8 +184,7 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { orderHash := getOrderHash(shortOrder) mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) - mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) - mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V1).Return(newMargin).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V2).Return(newMargin).Times(1) mockBibliophile.EXPECT().IsValidator(shortOrder.Trader).Return(true).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) @@ -208,8 +204,7 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { orderHash := getOrderHash(longOrder) mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) - mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) - mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V1).Return(big.NewInt(-1)).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V2).Return(big.NewInt(-1)).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) mockBibliophile.EXPECT().IsValidator(longOrder.Trader).Return(true).Times(1) @@ -226,8 +221,7 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { orderHash := getOrderHash(shortOrder) mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) - mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) - mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V1).Return(big.NewInt(-1)).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V2).Return(big.NewInt(-1)).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) mockBibliophile.EXPECT().IsValidator(shortOrder.Trader).Return(true).Times(1) @@ -247,8 +241,7 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { filledAmount := hu.Div(longOrder.BaseAssetQuantity, big.NewInt(2)) mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) - mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) - mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V1).Return(big.NewInt(-1)).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(longOrder.Trader, hu.V2).Return(big.NewInt(-1)).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(filledAmount).Times(1) mockBibliophile.EXPECT().GetMarketAddressFromMarketID(longOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) mockBibliophile.EXPECT().IsValidator(longOrder.Trader).Return(true).Times(1) @@ -267,8 +260,7 @@ func TestValidateCancelLimitOrderV2(t *testing.T) { filledAmount := hu.Div(shortOrder.BaseAssetQuantity, big.NewInt(2)) mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(juror.Placed)).Times(1) - mockBibliophile.EXPECT().GetTimeStamp().Return(hu.V1ActivationTime).Times(1) - mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V1).Return(big.NewInt(-1)).Times(1) + mockBibliophile.EXPECT().GetAvailableMargin(shortOrder.Trader, hu.V2).Return(big.NewInt(-1)).Times(1) mockBibliophile.EXPECT().GetOrderFilledAmount(orderHash).Return(filledAmount).Times(1) mockBibliophile.EXPECT().GetMarketAddressFromMarketID(shortOrder.AmmIndex.Int64()).Return(ammAddress).Times(1) mockBibliophile.EXPECT().IsValidator(shortOrder.Trader).Return(true).Times(1) diff --git a/precompile/contracts/traderviewer/viewer.go b/precompile/contracts/traderviewer/viewer.go index 7c264bc0d5..63b2a42a27 100644 --- a/precompile/contracts/traderviewer/viewer.go +++ b/precompile/contracts/traderviewer/viewer.go @@ -2,7 +2,6 @@ package traderviewer import ( "math/big" - hu "github.com/ava-labs/subnet-evm/plugin/evm/orderbook/hubbleutils" b "github.com/ava-labs/subnet-evm/precompile/contracts/bibliophile" "github.com/ethereum/go-ethereum/common" ) @@ -17,7 +16,7 @@ func GetNotionalPositionAndMargin(bibliophile b.BibliophileClient, input *GetNot } func GetCrossMarginAccountData(bibliophile b.BibliophileClient, input *GetCrossMarginAccountDataInput) GetCrossMarginAccountDataOutput { - notionalPosition, requiredMargin, unrealizedPnl, pendingFunding := bibliophile.GetCrossMarginAccountData(input.Trader, input.Mode, hu.V2) // @todo check if this is the right upgrade version + notionalPosition, requiredMargin, unrealizedPnl, pendingFunding := bibliophile.GetCrossMarginAccountData(input.Trader, input.Mode) return GetCrossMarginAccountDataOutput{ NotionalPosition: notionalPosition, RequiredMargin: requiredMargin, From 8ca866ae980dc855535ac388b5c5176175ab6bbf Mon Sep 17 00:00:00 2001 From: Atul Agarwal <21087753+asquare08@users.noreply.github.com> Date: Fri, 22 Mar 2024 02:35:53 +0530 Subject: [PATCH 14/14] add test for getRequiredMargin --- .../contracts/juror/matching_validation.go | 2 +- .../traderviewer/limit_orders_test.go | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/precompile/contracts/juror/matching_validation.go b/precompile/contracts/juror/matching_validation.go index b993b71483..75354879e3 100644 --- a/precompile/contracts/juror/matching_validation.go +++ b/precompile/contracts/juror/matching_validation.go @@ -443,7 +443,7 @@ func getRequiredMargin(bibliophile b.BibliophileClient, order ILimitOrderBookOrd func getRequiredMarginNew(bibliophile b.BibliophileClient, baseAsset *big.Int, price *big.Int, marketId int64, trader *common.Address) *big.Int { marketAddress := bibliophile.GetMarketAddressFromMarketID(marketId) - marginFraction := bibliophile.GetTraderMarginFraction(marketAddress,trader) + marginFraction := bibliophile.GetTraderMarginFraction(marketAddress, trader) quoteAsset := hu.Div1e18(hu.Mul(hu.Abs(baseAsset), price)) return hu.Div1e6(hu.Mul(quoteAsset, marginFraction)) } diff --git a/precompile/contracts/traderviewer/limit_orders_test.go b/precompile/contracts/traderviewer/limit_orders_test.go index 28936fe421..e46f8e41b3 100644 --- a/precompile/contracts/traderviewer/limit_orders_test.go +++ b/precompile/contracts/traderviewer/limit_orders_test.go @@ -438,3 +438,44 @@ func getOrderHash(order ILimitOrderBookOrder) common.Hash { } return orderHash } + +func TestGetRequiredMargin(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockBibliophile := b.NewMockBibliophileClient(ctrl) + ammIndex := big.NewInt(0) + longBaseAssetQuantity := big.NewInt(5000000000000000000) + shortBaseAssetQuantity := big.NewInt(-5000000000000000000) + price := big.NewInt(100000000) + trader := common.HexToAddress("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC") + ammAddress := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8") + marginFraction := big.NewInt(2e5) // 0.2 + t.Run("when order is long", func(t *testing.T) { + input := getRequiredMarginInput(longBaseAssetQuantity, price, ammIndex, trader) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(ammIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetTraderMarginFraction(ammAddress, &trader).Return(marginFraction).Times(1) + output := GetRequiredMargin(mockBibliophile, &input) + expectedMargin := hu.Div1e18(hu.Mul(longBaseAssetQuantity, price)) + expectedMargin = hu.Div1e6(hu.Mul(expectedMargin, marginFraction)) + assert.Equal(t, expectedMargin, output) + }) + + t.Run("when order is short", func(t *testing.T) { + input := getRequiredMarginInput(shortBaseAssetQuantity, price, ammIndex, trader) + mockBibliophile.EXPECT().GetMarketAddressFromMarketID(ammIndex.Int64()).Return(ammAddress).Times(1) + mockBibliophile.EXPECT().GetTraderMarginFraction(ammAddress, &trader).Return(marginFraction).Times(1) + output := GetRequiredMargin(mockBibliophile, &input) + expectedMargin := hu.Div1e18(hu.Mul(hu.Abs(shortBaseAssetQuantity), price)) + expectedMargin = hu.Div1e6(hu.Mul(expectedMargin, marginFraction)) + assert.Equal(t, expectedMargin, output) + }) +} + +func getRequiredMarginInput(baseAssetQuantity *big.Int, price *big.Int, ammIndex *big.Int, trader common.Address) GetRequiredMarginInput { + return GetRequiredMarginInput{ + BaseAssetQuantity: baseAssetQuantity, + Price: price, + AmmIndex: ammIndex, + Trader: trader, + } +}