From ac44d3e7d780ba9dd72845f97500ff24c1296e60 Mon Sep 17 00:00:00 2001 From: atvanguard <3612498+atvanguard@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:39:22 +0530 Subject: [PATCH 1/4] return BadElement in validateLiquidation response --- .../contracts/hubble-v2/interfaces/IJuror.sol | 6 +++--- precompile/contracts/juror/contract.abi | 2 +- precompile/contracts/juror/contract.go | 5 +++-- .../contracts/juror/matching_validation.go | 18 ++++++++---------- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/contracts/contracts/hubble-v2/interfaces/IJuror.sol b/contracts/contracts/hubble-v2/interfaces/IJuror.sol index 727ccebb82..03330eb182 100644 --- a/contracts/contracts/hubble-v2/interfaces/IJuror.sol +++ b/contracts/contracts/hubble-v2/interfaces/IJuror.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import { IOrderHandler } from "./IOrderHandler.sol"; interface IJuror { - enum BadElement { Order0, Order1, Generic } + enum BadElement { Order0, Order1, Generic, NoError } // Order Matching function validateOrdersAndDetermineFillPrice( @@ -12,12 +12,12 @@ interface IJuror { int256 fillAmount ) external view - returns(string memory err, BadElement reason, IOrderHandler.MatchingValidationRes memory res); + returns(string memory err, BadElement element, IOrderHandler.MatchingValidationRes memory res); function validateLiquidationOrderAndDetermineFillPrice(bytes calldata data, uint256 liquidationAmount) external view - returns(string memory err, IOrderHandler.LiquidationMatchingValidationRes memory res); + returns(string memory err, BadElement element, IOrderHandler.LiquidationMatchingValidationRes memory res); // Limit Orders function validatePlaceLimitOrder(ILimitOrderBook.Order calldata order, address sender) diff --git a/precompile/contracts/juror/contract.abi b/precompile/contracts/juror/contract.abi index 79de043790..4a9f817580 100644 --- a/precompile/contracts/juror/contract.abi +++ b/precompile/contracts/juror/contract.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"bool","name":"includeFundingPayments","type":"bool"},{"internalType":"uint8","name":"mode","type":"uint8"}],"name":"getNotionalPositionAndMargin","outputs":[{"internalType":"uint256","name":"notionalPosition","type":"uint256"},{"internalType":"int256","name":"margin","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"}],"name":"validateCancelLimitOrder","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"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint256","name":"liquidationAmount","type":"uint256"}],"name":"validateLiquidationOrderAndDetermineFillPrice","outputs":[{"internalType":"string","name":"err","type":"string"},{"components":[{"components":[{"internalType":"uint256","name":"ammIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"},{"internalType":"bytes32","name":"orderHash","type":"bytes32"},{"internalType":"enum IClearingHouse.OrderExecutionMode","name":"mode","type":"uint8"}],"internalType":"struct IClearingHouse.Instruction","name":"instruction","type":"tuple"},{"internalType":"uint8","name":"orderType","type":"uint8"},{"internalType":"bytes","name":"encodedOrder","type":"bytes"},{"internalType":"uint256","name":"fillPrice","type":"uint256"},{"internalType":"int256","name":"fillAmount","type":"int256"}],"internalType":"struct IOrderHandler.LiquidationMatchingValidationRes","name":"res","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes[2]","name":"data","type":"bytes[2]"},{"internalType":"int256","name":"fillAmount","type":"int256"}],"name":"validateOrdersAndDetermineFillPrice","outputs":[{"internalType":"string","name":"err","type":"string"},{"internalType":"enum IJuror.BadElement","name":"reason","type":"uint8"},{"components":[{"components":[{"internalType":"uint256","name":"ammIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"},{"internalType":"bytes32","name":"orderHash","type":"bytes32"},{"internalType":"enum IClearingHouse.OrderExecutionMode","name":"mode","type":"uint8"}],"internalType":"struct IClearingHouse.Instruction[2]","name":"instructions","type":"tuple[2]"},{"internalType":"uint8[2]","name":"orderTypes","type":"uint8[2]"},{"internalType":"bytes[2]","name":"encodedOrders","type":"bytes[2]"},{"internalType":"uint256","name":"fillPrice","type":"uint256"}],"internalType":"struct IOrderHandler.MatchingValidationRes","name":"res","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"orderType","type":"uint8"},{"internalType":"uint256","name":"expireAt","type":"uint256"},{"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":"struct IImmediateOrCancelOrders.Order","name":"order","type":"tuple"},{"internalType":"address","name":"sender","type":"address"}],"name":"validatePlaceIOCOrder","outputs":[{"internalType":"string","name":"err","type":"string"},{"internalType":"bytes32","name":"orderHash","type":"bytes32"}],"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"}],"name":"validatePlaceLimitOrder","outputs":[{"internalType":"string","name":"err","type":"string"},{"internalType":"bytes32","name":"orderhash","type":"bytes32"},{"components":[{"internalType":"uint256","name":"reserveAmount","type":"uint256"},{"internalType":"address","name":"amm","type":"address"}],"internalType":"struct IOrderHandler.PlaceOrderRes","name":"res","type":"tuple"}],"stateMutability":"view","type":"function"}] +[{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"bool","name":"includeFundingPayments","type":"bool"},{"internalType":"uint8","name":"mode","type":"uint8"}],"name":"getNotionalPositionAndMargin","outputs":[{"internalType":"uint256","name":"notionalPosition","type":"uint256"},{"internalType":"int256","name":"margin","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"}],"name":"validateCancelLimitOrder","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"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint256","name":"liquidationAmount","type":"uint256"}],"name":"validateLiquidationOrderAndDetermineFillPrice","outputs":[{"internalType":"string","name":"err","type":"string"},{"internalType":"enum IJuror.BadElement","name":"element","type":"uint8"},{"components":[{"components":[{"internalType":"uint256","name":"ammIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"},{"internalType":"bytes32","name":"orderHash","type":"bytes32"},{"internalType":"enum IClearingHouse.OrderExecutionMode","name":"mode","type":"uint8"}],"internalType":"struct IClearingHouse.Instruction","name":"instruction","type":"tuple"},{"internalType":"uint8","name":"orderType","type":"uint8"},{"internalType":"bytes","name":"encodedOrder","type":"bytes"},{"internalType":"uint256","name":"fillPrice","type":"uint256"},{"internalType":"int256","name":"fillAmount","type":"int256"}],"internalType":"struct IOrderHandler.LiquidationMatchingValidationRes","name":"res","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes[2]","name":"data","type":"bytes[2]"},{"internalType":"int256","name":"fillAmount","type":"int256"}],"name":"validateOrdersAndDetermineFillPrice","outputs":[{"internalType":"string","name":"err","type":"string"},{"internalType":"enum IJuror.BadElement","name":"element","type":"uint8"},{"components":[{"components":[{"internalType":"uint256","name":"ammIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"},{"internalType":"bytes32","name":"orderHash","type":"bytes32"},{"internalType":"enum IClearingHouse.OrderExecutionMode","name":"mode","type":"uint8"}],"internalType":"struct IClearingHouse.Instruction[2]","name":"instructions","type":"tuple[2]"},{"internalType":"uint8[2]","name":"orderTypes","type":"uint8[2]"},{"internalType":"bytes[2]","name":"encodedOrders","type":"bytes[2]"},{"internalType":"uint256","name":"fillPrice","type":"uint256"}],"internalType":"struct IOrderHandler.MatchingValidationRes","name":"res","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"orderType","type":"uint8"},{"internalType":"uint256","name":"expireAt","type":"uint256"},{"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":"struct IImmediateOrCancelOrders.Order","name":"order","type":"tuple"},{"internalType":"address","name":"sender","type":"address"}],"name":"validatePlaceIOCOrder","outputs":[{"internalType":"string","name":"err","type":"string"},{"internalType":"bytes32","name":"orderHash","type":"bytes32"}],"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"}],"name":"validatePlaceLimitOrder","outputs":[{"internalType":"string","name":"err","type":"string"},{"internalType":"bytes32","name":"orderhash","type":"bytes32"},{"components":[{"internalType":"uint256","name":"reserveAmount","type":"uint256"},{"internalType":"address","name":"amm","type":"address"}],"internalType":"struct IOrderHandler.PlaceOrderRes","name":"res","type":"tuple"}],"stateMutability":"view","type":"function"}] diff --git a/precompile/contracts/juror/contract.go b/precompile/contracts/juror/contract.go index 93c098c970..5924dab8af 100644 --- a/precompile/contracts/juror/contract.go +++ b/precompile/contracts/juror/contract.go @@ -140,8 +140,9 @@ type ValidateLiquidationOrderAndDetermineFillPriceInput struct { } type ValidateLiquidationOrderAndDetermineFillPriceOutput struct { - Err string - Res IOrderHandlerLiquidationMatchingValidationRes + Err string + Element uint8 + Res IOrderHandlerLiquidationMatchingValidationRes } type ValidateOrdersAndDetermineFillPriceInput struct { diff --git a/precompile/contracts/juror/matching_validation.go b/precompile/contracts/juror/matching_validation.go index d4367f8b3a..b0f93833df 100644 --- a/precompile/contracts/juror/matching_validation.go +++ b/precompile/contracts/juror/matching_validation.go @@ -226,16 +226,16 @@ func determineFillPrice(bibliophile b.BibliophileClient, m0, m1 *Metadata) (*Fil func ValidateLiquidationOrderAndDetermineFillPrice(bibliophile b.BibliophileClient, inputStruct *ValidateLiquidationOrderAndDetermineFillPriceInput) ValidateLiquidationOrderAndDetermineFillPriceOutput { fillAmount := new(big.Int).Set(inputStruct.LiquidationAmount) if fillAmount.Sign() <= 0 { - return ValidateLiquidationOrderAndDetermineFillPriceOutput{Err: ErrInvalidFillAmount.Error()} + return ValidateLiquidationOrderAndDetermineFillPriceOutput{Err: ErrInvalidFillAmount.Error(), Element: uint8(Generic)} } decodeStep0, err := ob.DecodeTypeAndEncodedOrder(inputStruct.Data) if err != nil { - return ValidateLiquidationOrderAndDetermineFillPriceOutput{Err: err.Error()} + return ValidateLiquidationOrderAndDetermineFillPriceOutput{Err: err.Error(), Element: uint8(Order0)} } m0, err := validateOrder(bibliophile, decodeStep0.OrderType, decodeStep0.EncodedOrder, Liquidation, fillAmount) if err != nil { - return ValidateLiquidationOrderAndDetermineFillPriceOutput{Err: err.Error()} + return ValidateLiquidationOrderAndDetermineFillPriceOutput{Err: err.Error(), Element: uint8(Order0)} } if m0.BaseAssetQuantity.Sign() < 0 { @@ -244,16 +244,17 @@ func ValidateLiquidationOrderAndDetermineFillPrice(bibliophile b.BibliophileClie minSize := bibliophile.GetMinSizeRequirement(m0.AmmIndex.Int64()) if new(big.Int).Mod(fillAmount, minSize).Cmp(big.NewInt(0)) != 0 { - return ValidateLiquidationOrderAndDetermineFillPriceOutput{Err: ErrNotMultiple.Error()} + return ValidateLiquidationOrderAndDetermineFillPriceOutput{Err: ErrNotMultiple.Error(), Element: uint8(Generic)} } fillPrice, err := determineLiquidationFillPrice(bibliophile, m0) if err != nil { - return ValidateLiquidationOrderAndDetermineFillPriceOutput{Err: err.Error()} + return ValidateLiquidationOrderAndDetermineFillPriceOutput{Err: err.Error(), Element: uint8(Order0)} } return ValidateLiquidationOrderAndDetermineFillPriceOutput{ - Err: "", + Err: "", + Element: uint8(NoError), Res: IOrderHandlerLiquidationMatchingValidationRes{ Instruction: IClearingHouseInstruction{ AmmIndex: m0.AmmIndex, @@ -298,10 +299,7 @@ func validateOrder(bibliophile b.BibliophileClient, orderType ob.OrderType, enco if err != nil { return nil, err } - metadata, err = validateExecuteLimitOrder(bibliophile, order, side, fillAmount, orderHash) - if err != nil { - return nil, err - } + return validateExecuteLimitOrder(bibliophile, order, side, fillAmount, orderHash) } if orderType == ob.IOC { order, err := ob.DecodeIOCOrder(encodedOrder) From aedb2634bc29f315c8f8d66dc7bc7c4d0de4149c Mon Sep 17 00:00:00 2001 From: atvanguard <3612498+atvanguard@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:23:22 +0530 Subject: [PATCH 2/4] misc precompile updates --- .../evm/orderbook/hubbleutils/hubble_math.go | 4 ++ .../contracts/bibliophile/clearing_house.go | 6 ++ precompile/contracts/bibliophile/client.go | 5 ++ precompile/contracts/bibliophile/referral.go | 29 +++++++++ precompile/contracts/juror/contract.go | 2 +- precompile/contracts/juror/ioc_orders.go | 24 +++++++- precompile/contracts/juror/limit_orders.go | 60 ++++++++++++------- .../contracts/juror/matching_validation.go | 1 + 8 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 precompile/contracts/bibliophile/referral.go diff --git a/plugin/evm/orderbook/hubbleutils/hubble_math.go b/plugin/evm/orderbook/hubbleutils/hubble_math.go index d19b87deeb..d090e98307 100644 --- a/plugin/evm/orderbook/hubbleutils/hubble_math.go +++ b/plugin/evm/orderbook/hubbleutils/hubble_math.go @@ -41,3 +41,7 @@ func Mul(a, b *big.Int) *big.Int { func Div(a, b *big.Int) *big.Int { return new(big.Int).Div(a, b) } + +func Abs(a *big.Int) *big.Int { + return new(big.Int).Abs(a) +} diff --git a/precompile/contracts/bibliophile/clearing_house.go b/precompile/contracts/bibliophile/clearing_house.go index 9d7041ae5d..126c0568f3 100644 --- a/precompile/contracts/bibliophile/clearing_house.go +++ b/precompile/contracts/bibliophile/clearing_house.go @@ -16,6 +16,7 @@ const ( MIN_ALLOWABLE_MARGIN_SLOT int64 = 2 TAKER_FEE_SLOT int64 = 3 AMMS_SLOT int64 = 12 + REFERRAL_SLOT int64 = 13 ) type MarginMode uint8 @@ -134,3 +135,8 @@ func getMarketAddressFromMarketID(marketID int64, stateDB contract.StateDB) comm amm := stateDB.GetState(common.HexToAddress(CLEARING_HOUSE_GENESIS_ADDRESS), common.BigToHash(new(big.Int).Add(baseStorageSlot, big.NewInt(marketID)))) return common.BytesToAddress(amm.Bytes()) } + +func getReferralAddress(stateDB contract.StateDB) common.Address { + referral := stateDB.GetState(common.HexToAddress(CLEARING_HOUSE_GENESIS_ADDRESS), common.BigToHash(big.NewInt(REFERRAL_SLOT))) + return common.BytesToAddress(referral.Bytes()) +} diff --git a/precompile/contracts/bibliophile/client.go b/precompile/contracts/bibliophile/client.go index c7c072b29a..e56d546439 100644 --- a/precompile/contracts/bibliophile/client.go +++ b/precompile/contracts/bibliophile/client.go @@ -46,6 +46,7 @@ type BibliophileClient interface { GetAccessibleState() contract.AccessibleState GetNotionalPositionAndMargin(trader common.Address, includeFundingPayments bool, mode uint8) (*big.Int, *big.Int) + HasReferrer(trader common.Address) bool } // Define a structure that will implement the Bibliophile interface @@ -179,3 +180,7 @@ func (b *bibliophileClient) GetNotionalPositionAndMargin(trader common.Address, output := getNotionalPositionAndMargin(b.accessibleState.GetStateDB(), &GetNotionalPositionAndMarginInput{Trader: trader, IncludeFundingPayments: includeFundingPayments, Mode: mode}) return output.NotionalPosition, output.Margin } + +func (b *bibliophileClient) HasReferrer(trader common.Address) bool { + return hasReferrer(b.accessibleState.GetStateDB(), trader) +} diff --git a/precompile/contracts/bibliophile/referral.go b/precompile/contracts/bibliophile/referral.go new file mode 100644 index 0000000000..f4695a3acf --- /dev/null +++ b/precompile/contracts/bibliophile/referral.go @@ -0,0 +1,29 @@ +package bibliophile + +import ( + "math/big" + + "github.com/ava-labs/subnet-evm/precompile/contract" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +const ( + TRADER_TO_REFERRER_SLOT int64 = 3 + RESTRICTED_INVITES_SLOT int64 = 6 +) + +func restrictedInvites(stateDB contract.StateDB, referralContract common.Address) bool { + return stateDB.GetState(referralContract, common.BigToHash(big.NewInt(RESTRICTED_INVITES_SLOT))).Big().Uint64() == 1 +} + +func traderToReferrer(stateDB contract.StateDB, referralContract, trader common.Address) common.Address { + pos := crypto.Keccak256(append(common.LeftPadBytes(trader.Bytes(), 32), common.LeftPadBytes(big.NewInt(TRADER_TO_REFERRER_SLOT).Bytes(), 32)...)) + return common.BytesToAddress(stateDB.GetState(referralContract, common.BytesToHash(pos)).Bytes()) +} + +func hasReferrer(stateDB contract.StateDB, trader common.Address) bool { + referralContract := getReferralAddress(stateDB) + return !restrictedInvites(stateDB, referralContract) || traderToReferrer(stateDB, referralContract, trader) != common.Address{} +} diff --git a/precompile/contracts/juror/contract.go b/precompile/contracts/juror/contract.go index 5924dab8af..386fa418ba 100644 --- a/precompile/contracts/juror/contract.go +++ b/precompile/contracts/juror/contract.go @@ -172,7 +172,7 @@ type ValidatePlaceLimitOrderInput struct { } type ValidatePlaceLimitOrderOutput struct { - Errs string + Err string Orderhash [32]byte Res IOrderHandlerPlaceOrderRes } diff --git a/precompile/contracts/juror/ioc_orders.go b/precompile/contracts/juror/ioc_orders.go index 138c08eda2..f36ef19ecc 100644 --- a/precompile/contracts/juror/ioc_orders.go +++ b/precompile/contracts/juror/ioc_orders.go @@ -5,6 +5,7 @@ import ( "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" ) @@ -51,10 +52,27 @@ func ValidatePlaceIOCorder(bibliophile b.BibliophileClient, inputStruct *Validat response.Err = ErrInvalidOrder.Error() return } + + if !bibliophile.HasReferrer(order.Trader) { + response.Err = ErrNoReferrer.Error() + } + // this check is sort of redundant because either ways user can circumvent this by placing several reduceOnly order in a single tx/block - // if order.ReduceOnly { - // @todo - // } + if order.ReduceOnly { + ammAddress := bibliophile.GetMarketAddressFromMarketID(order.AmmIndex.Int64()) + posSize := bibliophile.GetSize(ammAddress, &trader) + // a reduce only order should reduce position + if !reducesPosition(posSize, order.BaseAssetQuantity) { + response.Err = ErrReduceOnlyBaseAssetQuantityInvalid.Error() + return + } + + reduceOnlyAmount := bibliophile.GetReduceOnlyAmount(trader, order.AmmIndex) + if hu.Abs(hu.Add(reduceOnlyAmount, order.BaseAssetQuantity)).Cmp(hu.Abs(posSize)) == 1 { + response.Err = ErrNetReduceOnlyAmountExceeded.Error() + return + } + } return response } diff --git a/precompile/contracts/juror/limit_orders.go b/precompile/contracts/juror/limit_orders.go index 54c0ac85fd..9f0e96d65e 100644 --- a/precompile/contracts/juror/limit_orders.go +++ b/precompile/contracts/juror/limit_orders.go @@ -4,6 +4,7 @@ import ( "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" "github.com/ethereum/go-ethereum/common" ) @@ -18,32 +19,32 @@ func ValidatePlaceLimitOrder(bibliophile b.BibliophileClient, inputStruct *Valid response.Orderhash = orderHash if err != nil { - response.Errs = err.Error() + response.Err = err.Error() return } if order.Price.Sign() != 1 { - response.Errs = ErrInvalidPrice.Error() + response.Err = ErrInvalidPrice.Error() return } trader := order.Trader if trader != sender && !bibliophile.IsTradingAuthority(trader, sender) { - response.Errs = ErrNoTradingAuthority.Error() + response.Err = ErrNoTradingAuthority.Error() return } ammAddress := bibliophile.GetMarketAddressFromMarketID(order.AmmIndex.Int64()) response.Res.Amm = ammAddress if order.BaseAssetQuantity.Sign() == 0 { - response.Errs = ErrBaseAssetQuantityZero.Error() + response.Err = ErrBaseAssetQuantityZero.Error() return } minSize := bibliophile.GetMinSizeRequirement(order.AmmIndex.Int64()) if new(big.Int).Mod(order.BaseAssetQuantity, minSize).Sign() != 0 { - response.Errs = ErrNotMultiple.Error() + response.Err = ErrNotMultiple.Error() return } status := OrderStatus(bibliophile.GetOrderStatus(orderHash)) if status != Invalid { - response.Errs = ErrOrderAlreadyExists.Error() + response.Err = ErrOrderAlreadyExists.Error() return } @@ -52,7 +53,7 @@ func ValidatePlaceLimitOrder(bibliophile b.BibliophileClient, inputStruct *Valid // this should only happen when a trader with open reduce only orders was liquidated if (posSize.Sign() == 0 && reduceOnlyAmount.Sign() != 0) || (posSize.Sign() != 0 && new(big.Int).Mul(posSize, reduceOnlyAmount).Sign() == 1) { // if position is non-zero then reduceOnlyAmount should be zero or have the opposite sign as posSize - response.Errs = ErrStaleReduceOnlyOrders.Error() + response.Err = ErrStaleReduceOnlyOrders.Error() return } @@ -61,41 +62,60 @@ func ValidatePlaceLimitOrder(bibliophile b.BibliophileClient, inputStruct *Valid orderSide = Side(Short) } if order.ReduceOnly { + // a reduce only order should reduce position if !reducesPosition(posSize, order.BaseAssetQuantity) { - response.Errs = ErrReduceOnlyBaseAssetQuantityInvalid.Error() + response.Err = ErrReduceOnlyBaseAssetQuantityInvalid.Error() return } longOrdersAmount := bibliophile.GetLongOpenOrdersAmount(trader, order.AmmIndex) shortOrdersAmount := bibliophile.GetShortOpenOrdersAmount(trader, order.AmmIndex) - if (orderSide == Side(Long) && longOrdersAmount.Sign() != 0) || (orderSide == Side(Short) && shortOrdersAmount.Sign() != 0) { - response.Errs = ErrOpenOrders.Error() + + // if the trader is placing a reduceOnly long that means they have a short position + // we allow only 1 kind of order in the opposite direction of the position + // otherwise we run the risk of having stale reduceOnly orders (orders that are not actually reducing the position) + if (orderSide == Side(Long) && longOrdersAmount.Sign() != 0) || + (orderSide == Side(Short) && shortOrdersAmount.Sign() != 0) { + response.Err = ErrOpenOrders.Error() return } - if big.NewInt(0).Abs(big.NewInt(0).Add(reduceOnlyAmount, order.BaseAssetQuantity)).Cmp(big.NewInt(0).Abs(posSize)) == 1 { - response.Errs = ErrNetReduceOnlyAmountExceeded.Error() + if hu.Abs(hu.Add(reduceOnlyAmount, order.BaseAssetQuantity)).Cmp(hu.Abs(posSize)) == 1 { + response.Err = ErrNetReduceOnlyAmountExceeded.Error() return } } else { - if reduceOnlyAmount.Sign() != 0 && order.BaseAssetQuantity.Sign() != posSize.Sign() { - response.Errs = ErrOpenReduceOnlyOrders.Error() + // we allow only 1 kind of order in the opposite direction of the position + if order.BaseAssetQuantity.Sign() != posSize.Sign() && reduceOnlyAmount.Sign() != 0 { + response.Err = ErrOpenReduceOnlyOrders.Error() return } availableMargin := bibliophile.GetAvailableMargin(trader) requiredMargin := getRequiredMargin(bibliophile, order) if availableMargin.Cmp(requiredMargin) == -1 { - response.Errs = ErrInsufficientMargin.Error() + response.Err = ErrInsufficientMargin.Error() return } response.Res.ReserveAmount = requiredMargin } + if order.PostOnly { - asksHead := bibliophile.GetAsksHead(ammAddress) - bidsHead := bibliophile.GetBidsHead(ammAddress) - if (orderSide == Side(Short) && bidsHead.Sign() != 0 && order.Price.Cmp(bidsHead) != 1) || (orderSide == Side(Long) && asksHead.Sign() != 0 && order.Price.Cmp(asksHead) != -1) { - response.Errs = ErrCrossingMarket.Error() - return + if orderSide == Side(Short) { + bidsHead := bibliophile.GetBidsHead(ammAddress) + if bidsHead.Sign() != 0 && order.Price.Cmp(bidsHead) != 1 { + response.Err = ErrCrossingMarket.Error() + return + } + } else if orderSide == Side(Long) { + asksHead := bibliophile.GetAsksHead(ammAddress) + if asksHead.Sign() != 0 && order.Price.Cmp(asksHead) != -1 { + response.Err = ErrCrossingMarket.Error() + return + } } } + + if !bibliophile.HasReferrer(order.Trader) { + response.Err = ErrNoReferrer.Error() + } return response } diff --git a/precompile/contracts/juror/matching_validation.go b/precompile/contracts/juror/matching_validation.go index b0f93833df..8dd66cd575 100644 --- a/precompile/contracts/juror/matching_validation.go +++ b/precompile/contracts/juror/matching_validation.go @@ -67,6 +67,7 @@ var ( ErrOpenOrders = errors.New("open orders") ErrOpenReduceOnlyOrders = errors.New("open reduce only orders") ErrNoTradingAuthority = errors.New("no trading authority") + ErrNoReferrer = errors.New("no referrer") ) type BadElement uint8 From be770f42b8f7f7e6fd6edb64d16995200004951d Mon Sep 17 00:00:00 2001 From: atvanguard <3612498+atvanguard@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:50:01 +0530 Subject: [PATCH 3/4] fix tests --- .../contracts/bibliophile/client_mock.go | 14 +++ precompile/contracts/juror/contract.go | 2 +- precompile/contracts/juror/limit_orders.go | 17 +--- .../contracts/juror/limit_orders_test.go | 94 ++++++++++--------- 4 files changed, 71 insertions(+), 56 deletions(-) diff --git a/precompile/contracts/bibliophile/client_mock.go b/precompile/contracts/bibliophile/client_mock.go index d75f596fdd..49ffe04f74 100644 --- a/precompile/contracts/bibliophile/client_mock.go +++ b/precompile/contracts/bibliophile/client_mock.go @@ -375,6 +375,20 @@ func (mr *MockBibliophileClientMockRecorder) GetUpperAndLowerBoundForMarket(mark return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUpperAndLowerBoundForMarket", reflect.TypeOf((*MockBibliophileClient)(nil).GetUpperAndLowerBoundForMarket), marketId) } +// HasReferrer mocks base method. +func (m *MockBibliophileClient) HasReferrer(trader common.Address) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasReferrer", trader) + ret0, _ := ret[0].(bool) + return ret0 +} + +// HasReferrer indicates an expected call of HasReferrer. +func (mr *MockBibliophileClientMockRecorder) HasReferrer(trader interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasReferrer", reflect.TypeOf((*MockBibliophileClient)(nil).HasReferrer), trader) +} + // IOC_GetBlockPlaced mocks base method. func (m *MockBibliophileClient) IOC_GetBlockPlaced(orderHash [32]byte) *big.Int { m.ctrl.T.Helper() diff --git a/precompile/contracts/juror/contract.go b/precompile/contracts/juror/contract.go index 386fa418ba..84e38fcdb1 100644 --- a/precompile/contracts/juror/contract.go +++ b/precompile/contracts/juror/contract.go @@ -432,7 +432,7 @@ func PackValidatePlaceLimitOrder(inputStruct ValidatePlaceLimitOrderInput) ([]by // to conform the ABI outputs. func PackValidatePlaceLimitOrderOutput(outputStruct ValidatePlaceLimitOrderOutput) ([]byte, error) { return JurorABI.PackOutput("validatePlaceLimitOrder", - outputStruct.Errs, + outputStruct.Err, outputStruct.Orderhash, outputStruct.Res, ) diff --git a/precompile/contracts/juror/limit_orders.go b/precompile/contracts/juror/limit_orders.go index 9f0e96d65e..95fb0153f1 100644 --- a/precompile/contracts/juror/limit_orders.go +++ b/precompile/contracts/juror/limit_orders.go @@ -98,18 +98,11 @@ func ValidatePlaceLimitOrder(bibliophile b.BibliophileClient, inputStruct *Valid } if order.PostOnly { - if orderSide == Side(Short) { - bidsHead := bibliophile.GetBidsHead(ammAddress) - if bidsHead.Sign() != 0 && order.Price.Cmp(bidsHead) != 1 { - response.Err = ErrCrossingMarket.Error() - return - } - } else if orderSide == Side(Long) { - asksHead := bibliophile.GetAsksHead(ammAddress) - if asksHead.Sign() != 0 && order.Price.Cmp(asksHead) != -1 { - response.Err = ErrCrossingMarket.Error() - return - } + asksHead := bibliophile.GetAsksHead(ammAddress) + bidsHead := bibliophile.GetBidsHead(ammAddress) + if (orderSide == Side(Short) && bidsHead.Sign() != 0 && order.Price.Cmp(bidsHead) != 1) || (orderSide == Side(Long) && asksHead.Sign() != 0 && order.Price.Cmp(asksHead) != -1) { + response.Err = ErrCrossingMarket.Error() + return } } diff --git a/precompile/contracts/juror/limit_orders_test.go b/precompile/contracts/juror/limit_orders_test.go index 3ffa390b1a..60183f7d0d 100644 --- a/precompile/contracts/juror/limit_orders_test.go +++ b/precompile/contracts/juror/limit_orders_test.go @@ -35,7 +35,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order.AmmIndex.Int64()).Return(ammAddress).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: order, Sender: trader}) - assert.Equal(t, ErrBaseAssetQuantityZero.Error(), output.Errs) + assert.Equal(t, ErrBaseAssetQuantityZero.Error(), output.Err) expectedOrderHash, _ := GetLimitOrderHashFromContractStruct(&order) assert.Equal(t, common.BytesToHash(output.Orderhash[:]), expectedOrderHash) assert.Equal(t, output.Res.Amm, ammAddress) @@ -49,7 +49,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().IsTradingAuthority(order.Trader, sender).Return(false).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: order, Sender: sender}) assert.Equal(t, "de9b5c2bf047cda53602c6a3223cd4b84b2b659f2ad6bc4b3fb29aed156185bd", hex.EncodeToString(output.Orderhash[:])) - assert.Equal(t, ErrNoTradingAuthority.Error(), output.Errs) + assert.Equal(t, ErrNoTradingAuthority.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) @@ -57,7 +57,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: order, Sender: sender}) // fmt.Println("Orderhash", hex.EncodeToString(output.Orderhash[:])) assert.Equal(t, "8c9158cccd9795896fef87cc969deb425499f230ae9a4427d314f89ac76a0288", hex.EncodeToString(output.Orderhash[:])) - assert.Equal(t, ErrNoTradingAuthority.Error(), output.Errs) + assert.Equal(t, ErrNoTradingAuthority.Error(), output.Err) }) }) t.Run("when either sender is trader or a trading authority", func(t *testing.T) { @@ -70,7 +70,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order.AmmIndex.Int64()).Return(ammAddress).Times(1) mockBibliophile.EXPECT().GetMinSizeRequirement(order.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: order, Sender: trader}) - assert.Equal(t, ErrNotMultiple.Error(), output.Errs) + assert.Equal(t, ErrNotMultiple.Error(), output.Err) expectedOrderHash, _ := GetLimitOrderHashFromContractStruct(&order) assert.Equal(t, common.BytesToHash(output.Orderhash[:]), expectedOrderHash) assert.Equal(t, output.Res.Amm, ammAddress) @@ -83,7 +83,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order.AmmIndex.Int64()).Return(ammAddress).Times(1) mockBibliophile.EXPECT().GetMinSizeRequirement(order.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: order, Sender: trader}) - assert.Equal(t, ErrNotMultiple.Error(), output.Errs) + assert.Equal(t, ErrNotMultiple.Error(), output.Err) expectedOrderHash, _ := GetLimitOrderHashFromContractStruct(&order) assert.Equal(t, expectedOrderHash, common.BytesToHash(output.Orderhash[:])) assert.Equal(t, ammAddress, output.Res.Amm) @@ -98,7 +98,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order.AmmIndex.Int64()).Return(ammAddress).Times(1) mockBibliophile.EXPECT().GetMinSizeRequirement(order.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: order, Sender: trader}) - assert.Equal(t, ErrNotMultiple.Error(), output.Errs) + assert.Equal(t, ErrNotMultiple.Error(), output.Err) expectedOrderHash, _ := GetLimitOrderHashFromContractStruct(&order) assert.Equal(t, expectedOrderHash, common.BytesToHash(output.Orderhash[:])) assert.Equal(t, ammAddress, output.Res.Amm) @@ -111,7 +111,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetMarketAddressFromMarketID(order.AmmIndex.Int64()).Return(ammAddress).Times(1) mockBibliophile.EXPECT().GetMinSizeRequirement(order.AmmIndex.Int64()).Return(minSizeRequirement).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: order, Sender: trader}) - assert.Equal(t, ErrNotMultiple.Error(), output.Errs) + assert.Equal(t, ErrNotMultiple.Error(), output.Err) expectedOrderHash, _ := GetLimitOrderHashFromContractStruct(&order) assert.Equal(t, expectedOrderHash, common.BytesToHash(output.Orderhash[:])) assert.Equal(t, ammAddress, output.Res.Amm) @@ -135,7 +135,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { } mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Placed)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrOrderAlreadyExists.Error(), output.Errs) + assert.Equal(t, ErrOrderAlreadyExists.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) @@ -151,7 +151,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { } mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Placed)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrOrderAlreadyExists.Error(), output.Errs) + assert.Equal(t, ErrOrderAlreadyExists.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) @@ -169,7 +169,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { } mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Filled)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrOrderAlreadyExists.Error(), output.Errs) + assert.Equal(t, ErrOrderAlreadyExists.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) @@ -185,7 +185,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { } mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Filled)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrOrderAlreadyExists.Error(), output.Errs) + assert.Equal(t, ErrOrderAlreadyExists.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) @@ -203,7 +203,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { } mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Cancelled)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrOrderAlreadyExists.Error(), output.Errs) + assert.Equal(t, ErrOrderAlreadyExists.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) @@ -219,7 +219,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { } mockBibliophile.EXPECT().GetOrderStatus(orderHash).Return(int64(Cancelled)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrOrderAlreadyExists.Error(), output.Errs) + assert.Equal(t, ErrOrderAlreadyExists.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) @@ -249,7 +249,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrReduceOnlyBaseAssetQuantityInvalid.Error(), output.Errs) + assert.Equal(t, ErrReduceOnlyBaseAssetQuantityInvalid.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) @@ -268,7 +268,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrReduceOnlyBaseAssetQuantityInvalid.Error(), output.Errs) + assert.Equal(t, ErrReduceOnlyBaseAssetQuantityInvalid.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) @@ -289,7 +289,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, shortOrder.AmmIndex).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrReduceOnlyBaseAssetQuantityInvalid.Error(), output.Errs) + assert.Equal(t, ErrReduceOnlyBaseAssetQuantityInvalid.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) @@ -308,7 +308,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetSize(ammAddress, &trader).Return(positionSize).Times(1) mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrReduceOnlyBaseAssetQuantityInvalid.Error(), output.Errs) + assert.Equal(t, ErrReduceOnlyBaseAssetQuantityInvalid.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) @@ -336,7 +336,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, longOrder.AmmIndex).Return(longOpenOrdersAmount).Times(1) mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrOpenOrders.Error(), output.Errs) + assert.Equal(t, ErrOpenOrders.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) @@ -361,7 +361,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(shortOpenOrdersAmount).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrOpenOrders.Error(), output.Errs) + assert.Equal(t, ErrOpenOrders.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) @@ -388,7 +388,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetLongOpenOrdersAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) mockBibliophile.EXPECT().GetShortOpenOrdersAmount(trader, longOrder.AmmIndex).Return(big.NewInt(0)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrNetReduceOnlyAmountExceeded.Error(), output.Errs) + assert.Equal(t, ErrNetReduceOnlyAmountExceeded.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) @@ -411,7 +411,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) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrNetReduceOnlyAmountExceeded.Error(), output.Errs) + assert.Equal(t, ErrNetReduceOnlyAmountExceeded.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) @@ -436,8 +436,9 @@ 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().HasReferrer(trader).Return(true).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, "", output.Errs) + assert.Equal(t, "", 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) @@ -459,8 +460,9 @@ 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().HasReferrer(trader).Return(true).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, "", output.Errs) + assert.Equal(t, "", 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) @@ -492,7 +494,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrCrossingMarket.Error(), output.Errs) + assert.Equal(t, ErrCrossingMarket.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) @@ -515,7 +517,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrCrossingMarket.Error(), output.Errs) + assert.Equal(t, ErrCrossingMarket.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) @@ -543,7 +545,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrCrossingMarket.Error(), output.Errs) + assert.Equal(t, ErrCrossingMarket.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) @@ -566,7 +568,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrCrossingMarket.Error(), output.Errs) + assert.Equal(t, ErrCrossingMarket.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) @@ -594,8 +596,9 @@ 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().HasReferrer(trader).Return(true).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, "", output.Errs) + assert.Equal(t, "", 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) @@ -620,8 +623,9 @@ 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) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, "", output.Errs) + assert.Equal(t, "", 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) @@ -650,7 +654,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, longOrder.AmmIndex).Return(reduceOnlyAmount).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrOpenReduceOnlyOrders.Error(), output.Errs) + assert.Equal(t, ErrOpenReduceOnlyOrders.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) @@ -671,7 +675,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetReduceOnlyAmount(trader, shortOrder.AmmIndex).Return(reduceOnlyAmount).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrOpenReduceOnlyOrders.Error(), output.Errs) + assert.Equal(t, ErrOpenReduceOnlyOrders.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) @@ -706,7 +710,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(trader).Return(availableMargin).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrInsufficientMargin.Error(), output.Errs) + 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) @@ -730,7 +734,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(trader).Return(availableMargin).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrInsufficientMargin.Error(), output.Errs) + 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) @@ -762,7 +766,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(trader).Return(availableMargin).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrInsufficientMargin.Error(), output.Errs) + 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) @@ -787,7 +791,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(trader).Return(availableMargin).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrInsufficientMargin.Error(), output.Errs) + 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) @@ -822,8 +826,9 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetTakerFee().Return(takerFee).Times(1) mockBibliophile.EXPECT().GetAvailableMargin(trader).Return(availableMargin).Times(1) + mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, "", output.Errs) + 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) @@ -851,8 +856,9 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetTakerFee().Return(takerFee).Times(1) mockBibliophile.EXPECT().GetAvailableMargin(trader).Return(availableMargin).Times(1) + mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, "", output.Errs) + 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) @@ -893,7 +899,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrCrossingMarket.Error(), output.Errs) + assert.Equal(t, ErrCrossingMarket.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) assert.Equal(t, ammAddress, output.Res.Amm) assert.Equal(t, requiredMargin, output.Res.ReserveAmount) @@ -924,7 +930,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, ErrCrossingMarket.Error(), output.Errs) + assert.Equal(t, ErrCrossingMarket.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) assert.Equal(t, ammAddress, output.Res.Amm) assert.Equal(t, requiredMargin, output.Res.ReserveAmount) @@ -959,7 +965,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrCrossingMarket.Error(), output.Errs) + assert.Equal(t, ErrCrossingMarket.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) assert.Equal(t, ammAddress, output.Res.Amm) assert.Equal(t, requiredMargin, output.Res.ReserveAmount) @@ -990,7 +996,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, ErrCrossingMarket.Error(), output.Errs) + assert.Equal(t, ErrCrossingMarket.Error(), output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) assert.Equal(t, ammAddress, output.Res.Amm) assert.Equal(t, requiredMargin, output.Res.ReserveAmount) @@ -1023,8 +1029,9 @@ 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) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) - assert.Equal(t, "", output.Errs) + 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) @@ -1054,8 +1061,9 @@ 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) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) - assert.Equal(t, "", output.Errs) + 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) From 7e989fefa5a3894f9c95884e718da017b5b4c213 Mon Sep 17 00:00:00 2001 From: atvanguard <3612498+atvanguard@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:05:32 +0530 Subject: [PATCH 4/4] referrer and price precision check --- .../evm/orderbook/hubbleutils/hubble_math.go | 12 +++++ precompile/contracts/bibliophile/amm.go | 52 ++++++++++++------- precompile/contracts/bibliophile/client.go | 5 ++ .../contracts/bibliophile/client_mock.go | 14 +++++ precompile/contracts/bibliophile/orderbook.go | 16 +++--- precompile/contracts/juror/ioc_orders.go | 11 ++++ precompile/contracts/juror/limit_orders.go | 6 +++ .../contracts/juror/limit_orders_test.go | 13 +++-- .../contracts/juror/matching_validation.go | 1 + 9 files changed, 97 insertions(+), 33 deletions(-) diff --git a/plugin/evm/orderbook/hubbleutils/hubble_math.go b/plugin/evm/orderbook/hubbleutils/hubble_math.go index d090e98307..3960ed77f0 100644 --- a/plugin/evm/orderbook/hubbleutils/hubble_math.go +++ b/plugin/evm/orderbook/hubbleutils/hubble_math.go @@ -10,6 +10,10 @@ var ( ONE_E_18 = big.NewInt(1e18) ) +func Add1e6(a *big.Int) *big.Int { + return Add(a, ONE_E_6) +} + func Mul1e6(a *big.Int) *big.Int { return Mul(a, ONE_E_6) } @@ -45,3 +49,11 @@ func Div(a, b *big.Int) *big.Int { func Abs(a *big.Int) *big.Int { return new(big.Int).Abs(a) } + +func RoundOff(a, b *big.Int) *big.Int { + return Mul(Div(a, b), b) +} + +func Mod(a, b *big.Int) *big.Int { + return new(big.Int).Mod(a, b) +} diff --git a/precompile/contracts/bibliophile/amm.go b/precompile/contracts/bibliophile/amm.go index 82abf5068d..ff19357269 100644 --- a/precompile/contracts/bibliophile/amm.go +++ b/precompile/contracts/bibliophile/amm.go @@ -10,22 +10,23 @@ import ( ) const ( - VAR_POSITIONS_SLOT int64 = 5 - VAR_CUMULATIVE_PREMIUM_FRACTION int64 = 6 - MAX_ORACLE_SPREAD_RATIO_SLOT int64 = 7 - MAX_LIQUIDATION_RATIO_SLOT int64 = 8 - MIN_SIZE_REQUIREMENT_SLOT int64 = 9 - ORACLE_SLOT int64 = 10 - UNDERLYING_ASSET_SLOT int64 = 11 - MAX_LIQUIDATION_PRICE_SPREAD int64 = 17 - RED_STONE_ADAPTER_SLOT int64 = 21 - RED_STONE_FEED_ID_SLOT int64 = 22 - IMPACT_MARGIN_NOTIONAL_SLOT int64 = 27 - LAST_TRADE_PRICE_SLOT int64 = 28 - BIDS_SLOT int64 = 29 - ASKS_SLOT int64 = 30 - BIDS_HEAD_SLOT int64 = 31 - ASKS_HEAD_SLOT int64 = 32 + 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 + ORACLE_SLOT int64 = 6 + UNDERLYING_ASSET_SLOT int64 = 7 + MAX_LIQUIDATION_PRICE_SPREAD int64 = 12 + RED_STONE_ADAPTER_SLOT int64 = 16 + RED_STONE_FEED_ID_SLOT int64 = 17 + IMPACT_MARGIN_NOTIONAL_SLOT int64 = 22 + LAST_TRADE_PRICE_SLOT int64 = 23 + BIDS_SLOT int64 = 24 + ASKS_SLOT int64 = 25 + BIDS_HEAD_SLOT int64 = 24 + ASKS_HEAD_SLOT int64 = 25 + MULTIPLIER_SLOT int64 = 30 ) const ( @@ -51,19 +52,28 @@ func GetCumulativePremiumFraction(stateDB contract.StateDB, market common.Addres // GetMaxOraclePriceSpread returns the maxOracleSpreadRatio for a given market func GetMaxOraclePriceSpread(stateDB contract.StateDB, marketID int64) *big.Int { - market := getMarketAddressFromMarketID(marketID, stateDB) + return getMaxOraclePriceSpread(stateDB, getMarketAddressFromMarketID(marketID, stateDB)) +} + +func getMaxOraclePriceSpread(stateDB contract.StateDB, market common.Address) *big.Int { return fromTwosComplement(stateDB.GetState(market, common.BigToHash(big.NewInt(MAX_ORACLE_SPREAD_RATIO_SLOT))).Bytes()) } // GetMaxLiquidationPriceSpread returns the maxOracleSpreadRatio for a given market func GetMaxLiquidationPriceSpread(stateDB contract.StateDB, marketID int64) *big.Int { - market := getMarketAddressFromMarketID(marketID, stateDB) + return getMaxLiquidationPriceSpread(stateDB, getMarketAddressFromMarketID(marketID, stateDB)) +} + +func getMaxLiquidationPriceSpread(stateDB contract.StateDB, market common.Address) *big.Int { return fromTwosComplement(stateDB.GetState(market, common.BigToHash(big.NewInt(MAX_LIQUIDATION_PRICE_SPREAD))).Bytes()) } // GetMaxLiquidationRatio returns the maxLiquidationPriceSpread for a given market func GetMaxLiquidationRatio(stateDB contract.StateDB, marketID int64) *big.Int { - market := getMarketAddressFromMarketID(marketID, stateDB) + return getMaxLiquidationRatio(stateDB, getMarketAddressFromMarketID(marketID, stateDB)) +} + +func getMaxLiquidationRatio(stateDB contract.StateDB, market common.Address) *big.Int { return fromTwosComplement(stateDB.GetState(market, common.BigToHash(big.NewInt(MAX_LIQUIDATION_RATIO_SLOT))).Bytes()) } @@ -73,6 +83,10 @@ func GetMinSizeRequirement(stateDB contract.StateDB, marketID int64) *big.Int { return fromTwosComplement(stateDB.GetState(market, common.BigToHash(big.NewInt(MIN_SIZE_REQUIREMENT_SLOT))).Bytes()) } +func getMultiplier(stateDB contract.StateDB, market common.Address) *big.Int { + return stateDB.GetState(market, common.BigToHash(big.NewInt(MULTIPLIER_SLOT))).Big() +} + func getOracleAddress(stateDB contract.StateDB, market common.Address) common.Address { return common.BytesToAddress(stateDB.GetState(market, common.BigToHash(big.NewInt(ORACLE_SLOT))).Bytes()) } diff --git a/precompile/contracts/bibliophile/client.go b/precompile/contracts/bibliophile/client.go index e56d546439..0815ae9774 100644 --- a/precompile/contracts/bibliophile/client.go +++ b/precompile/contracts/bibliophile/client.go @@ -41,6 +41,7 @@ type BibliophileClient interface { GetImpactMarginNotional(ammAddress common.Address) *big.Int GetBidsHead(market common.Address) *big.Int GetAsksHead(market common.Address) *big.Int + GetPriceMultiplier(market common.Address) *big.Int GetUpperAndLowerBoundForMarket(marketId int64) (*big.Int, *big.Int) GetAcceptableBoundsForLiquidation(marketId int64) (*big.Int, *big.Int) @@ -160,6 +161,10 @@ func (b *bibliophileClient) GetAsksHead(market common.Address) *big.Int { return getAsksHead(b.accessibleState.GetStateDB(), market) } +func (b *bibliophileClient) GetPriceMultiplier(market common.Address) *big.Int { + return getMultiplier(b.accessibleState.GetStateDB(), market) +} + func (b *bibliophileClient) GetLongOpenOrdersAmount(trader common.Address, ammIndex *big.Int) *big.Int { return getLongOpenOrdersAmount(b.accessibleState.GetStateDB(), trader, ammIndex) } diff --git a/precompile/contracts/bibliophile/client_mock.go b/precompile/contracts/bibliophile/client_mock.go index 49ffe04f74..a914fc80d9 100644 --- a/precompile/contracts/bibliophile/client_mock.go +++ b/precompile/contracts/bibliophile/client_mock.go @@ -304,6 +304,20 @@ func (mr *MockBibliophileClientMockRecorder) GetOrderStatus(orderHash interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOrderStatus", reflect.TypeOf((*MockBibliophileClient)(nil).GetOrderStatus), orderHash) } +// GetPriceMultiplier mocks base method. +func (m *MockBibliophileClient) GetPriceMultiplier(market common.Address) *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPriceMultiplier", market) + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// GetPriceMultiplier indicates an expected call of GetPriceMultiplier. +func (mr *MockBibliophileClientMockRecorder) GetPriceMultiplier(market interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPriceMultiplier", reflect.TypeOf((*MockBibliophileClient)(nil).GetPriceMultiplier), market) +} + // GetReduceOnlyAmount mocks base method. func (m *MockBibliophileClient) GetReduceOnlyAmount(trader common.Address, ammIndex *big.Int) *big.Int { m.ctrl.T.Helper() diff --git a/precompile/contracts/bibliophile/orderbook.go b/precompile/contracts/bibliophile/orderbook.go index c7ba94b25b..2c2b5823cc 100644 --- a/precompile/contracts/bibliophile/orderbook.go +++ b/precompile/contracts/bibliophile/orderbook.go @@ -85,22 +85,20 @@ func IsValidator(stateDB contract.StateDB, senderOrSigner common.Address) bool { // Helper functions func GetAcceptableBounds(stateDB contract.StateDB, marketID int64) (upperBound, lowerBound *big.Int) { - spreadLimit := GetMaxOraclePriceSpread(stateDB, marketID) - oraclePrice := getUnderlyingPriceForMarket(stateDB, marketID) - return calculateBounds(spreadLimit, oraclePrice) + market := getMarketAddressFromMarketID(marketID, stateDB) + return calculateBounds(getMaxOraclePriceSpread(stateDB, market), getUnderlyingPrice(stateDB, market), getMultiplier(stateDB, market)) } func GetAcceptableBoundsForLiquidation(stateDB contract.StateDB, marketID int64) (upperBound, lowerBound *big.Int) { - spreadLimit := GetMaxLiquidationPriceSpread(stateDB, marketID) - oraclePrice := getUnderlyingPriceForMarket(stateDB, marketID) - return calculateBounds(spreadLimit, oraclePrice) + market := getMarketAddressFromMarketID(marketID, stateDB) + return calculateBounds(getMaxLiquidationPriceSpread(stateDB, market), getUnderlyingPrice(stateDB, market), getMultiplier(stateDB, market)) } -func calculateBounds(spreadLimit, oraclePrice *big.Int) (*big.Int, *big.Int) { - upperbound := hu.Div1e6(hu.Mul(oraclePrice, hu.Add(hu.ONE_E_6, spreadLimit))) +func calculateBounds(spreadLimit, oraclePrice, multiplier *big.Int) (*big.Int, *big.Int) { + upperbound := hu.RoundOff(hu.Div1e6(hu.Mul(oraclePrice, hu.Add1e6(spreadLimit))), multiplier) lowerbound := big.NewInt(0) if spreadLimit.Cmp(hu.ONE_E_6) == -1 { - lowerbound = hu.Div1e6(hu.Mul(oraclePrice, hu.Sub(hu.ONE_E_6, spreadLimit))) + lowerbound = hu.RoundOff(hu.Div1e6(hu.Mul(oraclePrice, hu.Sub(hu.ONE_E_6, spreadLimit))), multiplier) } return upperbound, lowerbound } diff --git a/precompile/contracts/juror/ioc_orders.go b/precompile/contracts/juror/ioc_orders.go index f36ef19ecc..954c8278f9 100644 --- a/precompile/contracts/juror/ioc_orders.go +++ b/precompile/contracts/juror/ioc_orders.go @@ -73,6 +73,17 @@ func ValidatePlaceIOCorder(bibliophile b.BibliophileClient, inputStruct *Validat return } } + + if order.Price.Sign() != 1 { + response.Err = ErrInvalidPrice.Error() + return + } + + ammAddress := bibliophile.GetMarketAddressFromMarketID(order.AmmIndex.Int64()) + if hu.Mod(order.Price, bibliophile.GetPriceMultiplier(ammAddress)).Sign() != 0 { + response.Err = ErrPricePrecision.Error() + return + } return response } diff --git a/precompile/contracts/juror/limit_orders.go b/precompile/contracts/juror/limit_orders.go index 95fb0153f1..2678e8ac28 100644 --- a/precompile/contracts/juror/limit_orders.go +++ b/precompile/contracts/juror/limit_orders.go @@ -109,6 +109,12 @@ func ValidatePlaceLimitOrder(bibliophile b.BibliophileClient, inputStruct *Valid if !bibliophile.HasReferrer(order.Trader) { response.Err = ErrNoReferrer.Error() } + + if hu.Mod(order.Price, bibliophile.GetPriceMultiplier(ammAddress)).Sign() != 0 { + response.Err = ErrPricePrecision.Error() + return + } + return response } diff --git a/precompile/contracts/juror/limit_orders_test.go b/precompile/contracts/juror/limit_orders_test.go index 60183f7d0d..4bb697be60 100644 --- a/precompile/contracts/juror/limit_orders_test.go +++ b/precompile/contracts/juror/limit_orders_test.go @@ -437,6 +437,7 @@ func TestValidatePlaceLimitOrder(t *testing.T) { 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().HasReferrer(trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -461,6 +462,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().HasReferrer(trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -597,6 +599,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().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -622,8 +625,8 @@ 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().HasReferrer(trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -825,8 +828,8 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetMinAllowableMargin().Return(minAllowableMargin).Times(1) mockBibliophile.EXPECT().GetTakerFee().Return(takerFee).Times(1) mockBibliophile.EXPECT().GetAvailableMargin(trader).Return(availableMargin).Times(1) - mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -855,8 +858,8 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetMinAllowableMargin().Return(minAllowableMargin).Times(1) mockBibliophile.EXPECT().GetTakerFee().Return(takerFee).Times(1) mockBibliophile.EXPECT().GetAvailableMargin(trader).Return(availableMargin).Times(1) - mockBibliophile.EXPECT().HasReferrer(trader).Return(true).Times(1) + mockBibliophile.EXPECT().GetPriceMultiplier(ammAddress).Return(big.NewInt(1)).Times(1) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: shortOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -1028,8 +1031,8 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(trader).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) output := ValidatePlaceLimitOrder(mockBibliophile, &ValidatePlaceLimitOrderInput{Order: longOrder, Sender: trader}) assert.Equal(t, "", output.Err) assert.Equal(t, orderHash, common.BytesToHash(output.Orderhash[:])) @@ -1060,8 +1063,8 @@ func TestValidatePlaceLimitOrder(t *testing.T) { mockBibliophile.EXPECT().GetAvailableMargin(trader).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) 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 8dd66cd575..0f61f3b611 100644 --- a/precompile/contracts/juror/matching_validation.go +++ b/precompile/contracts/juror/matching_validation.go @@ -50,6 +50,7 @@ var ( ErrInvalidOrder = errors.New("invalid order") ErrInvalidPrice = errors.New("invalid price") + ErrPricePrecision = errors.New("invalid price precision") ErrCancelledOrder = errors.New("cancelled order") ErrFilledOrder = errors.New("filled order") ErrOrderAlreadyExists = errors.New("order already exists")