From 475c030e9a1fbba69d193796dd22cb0bc912a383 Mon Sep 17 00:00:00 2001 From: andrewnguyen22 Date: Mon, 5 May 2025 07:41:06 -0400 Subject: [PATCH] issue-#170 --- cmd/cli/admin.go | 15 +- cmd/cli/query.go | 2 +- cmd/rpc/admin.go | 14 +- cmd/rpc/client.go | 12 +- cmd/rpc/query.go | 6 +- cmd/rpc/sock.go | 2 +- cmd/rpc/types.go | 12 +- cmd/rpc/web/explorer/components/table.jsx | 5 +- cmd/rpc/web/explorer/styles/globals.css | 17 +- cmd/rpc/web/wallet/components/account.jsx | 124 ++++----- cmd/rpc/web/wallet/components/api.js | 10 +- cmd/rpc/web/wallet/components/util.js | 6 +- fsm/genesis.go | 4 +- fsm/genesis_test.go | 40 +-- fsm/key.go | 8 +- fsm/message.go | 13 +- fsm/message.pb.go | 68 +++-- fsm/message_helpers.go | 22 +- fsm/message_test.go | 102 ++++--- fsm/swap.go | 205 +++++++------- fsm/swap_test.go | 139 +++++----- fsm/transaction.go | 54 ++-- lib/.proto/certificate.proto | 12 +- lib/.proto/message.proto | 6 +- lib/.proto/swap.proto | 2 +- lib/certificate.go | 57 +++- lib/certificate.pb.go | 46 ++-- lib/certificate_test.go | 15 +- lib/consensus.go | 22 +- lib/error.go | 4 +- lib/swap.go | 120 +------- lib/swap.pb.go | 8 +- lib/swap_test.go | 317 ---------------------- lib/util.go | 24 ++ store/store_test.go | 20 +- 35 files changed, 627 insertions(+), 906 deletions(-) delete mode 100644 lib/swap_test.go diff --git a/cmd/cli/admin.go b/cmd/cli/admin.go index c03af31106..7baf18e6f3 100644 --- a/cmd/cli/admin.go +++ b/cmd/cli/admin.go @@ -228,7 +228,7 @@ var ( Short: "edit an existing sell order - use the simulate flag to generate json only", Args: cobra.MinimumNArgs(6), Run: func(cmd *cobra.Command, args []string) { - writeTxResultToConsole(client.TxEditOrder(argGetAddrOrNickname(args[0]), uint64(argToInt(args[1])), uint64(argToInt(args[2])), uint64(argToInt(args[3])), uint64(argToInt(args[4])), args[5], getPassword(), !sim, fee)) + writeTxResultToConsole(client.TxEditOrder(argGetAddrOrNickname(args[0]), uint64(argToInt(args[1])), uint64(argToInt(args[2])), args[3], uint64(argToInt(args[4])), args[5], getPassword(), !sim, fee)) }, } @@ -237,7 +237,7 @@ var ( Short: "delete an existing sell order - use the simulate flag to generate json only", Args: cobra.MinimumNArgs(3), Run: func(cmd *cobra.Command, args []string) { - writeTxResultToConsole(client.TxDeleteOrder(argGetAddrOrNickname(args[0]), uint64(argToInt(args[1])), uint64(argToInt(args[2])), getPassword(), !sim, fee)) + writeTxResultToConsole(client.TxDeleteOrder(argGetAddrOrNickname(args[0]), args[1], uint64(argToInt(args[2])), getPassword(), !sim, fee)) }, } @@ -246,7 +246,16 @@ var ( Short: "lock an existing sell order - use the simulate flag to generate json only", Args: cobra.MinimumNArgs(3), Run: func(cmd *cobra.Command, args []string) { - writeTxResultToConsole(client.TxLockOrder(argGetAddrOrNickname(args[0]), argGetAddr(args[1]), uint64(argToInt(args[2])), getPassword(), !sim, fee)) + writeTxResultToConsole(client.TxLockOrder(argGetAddrOrNickname(args[0]), argGetAddr(args[1]), args[2], getPassword(), !sim, fee)) + }, + } + + txCloseOrderCmd = &cobra.Command{ + Use: "tx-close-order
--fee=10000 --simulate=true", + Short: "closes an existing locked sell order - use the simulate flag to generate json only", + Args: cobra.MinimumNArgs(2), + Run: func(cmd *cobra.Command, args []string) { + writeTxResultToConsole(client.TxCloseOrder(argGetAddrOrNickname(args[0]), args[1], getPassword(), !sim, fee)) }, } diff --git a/cmd/cli/query.go b/cmd/cli/query.go index a3b80b2247..9cbd16c07c 100644 --- a/cmd/cli/query.go +++ b/cmd/cli/query.go @@ -173,7 +173,7 @@ var ( Short: "query a specific sell order", Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { - writeToConsole(client.Order(height, uint64(argToInt(args[0])), committee)) + writeToConsole(client.Order(height, args[0], committee)) }, } diff --git a/cmd/rpc/admin.go b/cmd/rpc/admin.go index 23fc60d402..16daafbacb 100644 --- a/cmd/rpc/admin.go +++ b/cmd/rpc/admin.go @@ -347,8 +347,13 @@ func (s *Server) TransactionLockOrder(w http.ResponseWriter, r *http.Request, _ if err := s.getFeeFromState(w, ptr, fsm.MessageSendName, true); err != nil { return nil, err } + // convert the order id to bytes + oId, err := lib.StringToBytes(ptr.OrderId) + if err != nil { + return nil, err + } // Create and return the transaction to be sent - return fsm.NewLockOrderTx(p, lib.LockOrder{OrderId: ptr.OrderId, BuyerSendAddress: p.PublicKey().Address().Bytes(), BuyerReceiveAddress: ptr.ReceiveAddress}, s.config.NetworkID, s.config.ChainId, ptr.Fee, s.controller.ChainHeight()) + return fsm.NewLockOrderTx(p, lib.LockOrder{OrderId: oId, BuyerSendAddress: p.PublicKey().Address().Bytes(), BuyerReceiveAddress: ptr.ReceiveAddress}, s.config.NetworkID, s.config.ChainId, ptr.Fee, s.controller.ChainHeight()) }) } @@ -382,8 +387,13 @@ func (s *Server) TransactionCloseOrder(w http.ResponseWriter, r *http.Request, _ if int64(order.BuyerChainDeadline)-int64(s.controller.ChainHeight()) < 10 { return nil, fmt.Errorf("too close to buyer chain deadline") } + // convert the order id to bytes + oId, err := lib.StringToBytes(ptr.OrderId) + if err != nil { + return nil, err + } // Create the close order structure - co := lib.CloseOrder{OrderId: ptr.OrderId, CloseOrder: true} + co := lib.CloseOrder{OrderId: oId, CloseOrder: true} // Exit with the new CloseOrderTx return fsm.NewCloseOrderTx(p, co, crypto.NewAddress(order.SellerReceiveAddress), order.RequestedAmount, s.config.NetworkID, s.config.ChainId, ptr.Fee, s.controller.ChainHeight()) }) diff --git a/cmd/rpc/client.go b/cmd/rpc/client.go index c6da9ff0c2..cf4ecab45c 100644 --- a/cmd/rpc/client.go +++ b/cmd/rpc/client.go @@ -194,7 +194,7 @@ func (c *Client) RetiredCommittees(height uint64) (p *[]uint64, err lib.ErrorI) return } -func (c *Client) Order(height, orderId, chainId uint64) (p *lib.SellOrder, err lib.ErrorI) { +func (c *Client) Order(height uint64, orderId string, chainId uint64) (p *lib.SellOrder, err lib.ErrorI) { p = new(lib.SellOrder) err = c.orderRequest(OrderRouteName, height, orderId, chainId, p) return @@ -578,7 +578,7 @@ func (c *Client) TxCreateOrder(from AddrOrNickname, sellAmount, receiveAmount, c return c.transactionRequest(TxCreateOrderRouteName, txReq, submit) } -func (c *Client) TxEditOrder(from AddrOrNickname, sellAmount, receiveAmount, orderId, chainId uint64, receiveAddress string, +func (c *Client) TxEditOrder(from AddrOrNickname, sellAmount, receiveAmount uint64, orderId string, chainId uint64, receiveAddress string, pwd string, submit bool, optFee uint64) (hash *string, tx json.RawMessage, e lib.ErrorI) { receiveAddr, err := lib.NewHexBytesFromString(receiveAddress) if err != nil { @@ -603,7 +603,7 @@ func (c *Client) TxEditOrder(from AddrOrNickname, sellAmount, receiveAmount, ord return c.transactionRequest(TxEditOrderRouteName, txReq, submit) } -func (c *Client) TxDeleteOrder(from AddrOrNickname, orderId, chainId uint64, +func (c *Client) TxDeleteOrder(from AddrOrNickname, orderId string, chainId uint64, pwd string, submit bool, optFee uint64) (hash *string, tx json.RawMessage, e lib.ErrorI) { txReq := txDeleteOrder{ Fee: optFee, @@ -620,7 +620,7 @@ func (c *Client) TxDeleteOrder(from AddrOrNickname, orderId, chainId uint64, return c.transactionRequest(TxDeleteOrderRouteName, txReq, submit) } -func (c *Client) TxLockOrder(from AddrOrNickname, receiveAddress string, orderId uint64, +func (c *Client) TxLockOrder(from AddrOrNickname, receiveAddress string, orderId string, pwd string, submit bool, optFee uint64) (hash *string, tx json.RawMessage, e lib.ErrorI) { receiveHex, err := lib.NewHexBytesFromString(receiveAddress) if err != nil { @@ -640,7 +640,7 @@ func (c *Client) TxLockOrder(from AddrOrNickname, receiveAddress string, orderId return c.transactionRequest(TxLockOrderRouteName, txReq, submit) } -func (c *Client) TxCloseOrder(from AddrOrNickname, orderId uint64, pwd string, submit bool, optFee uint64) (hash *string, tx json.RawMessage, e lib.ErrorI) { +func (c *Client) TxCloseOrder(from AddrOrNickname, orderId string, pwd string, submit bool, optFee uint64) (hash *string, tx json.RawMessage, e lib.ErrorI) { txReq := txCloseOrder{ Fee: optFee, OrderId: orderId, @@ -850,7 +850,7 @@ func (c *Client) heightRequest(routeName string, height uint64, ptr any) (err li return } -func (c *Client) orderRequest(routeName string, height, orderId, chainId uint64, ptr any) (err lib.ErrorI) { +func (c *Client) orderRequest(routeName string, height uint64, orderId string, chainId uint64, ptr any) (err lib.ErrorI) { bz, err := lib.MarshalJSON(orderRequest{ ChainId: chainId, OrderId: orderId, diff --git a/cmd/rpc/query.go b/cmd/rpc/query.go index 38da88973f..832a084202 100644 --- a/cmd/rpc/query.go +++ b/cmd/rpc/query.go @@ -262,7 +262,11 @@ func (s *Server) RetiredCommittees(w http.ResponseWriter, r *http.Request, _ htt func (s *Server) Order(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // Invoke helper with the HTTP request, response writer and an inline callback s.orderParams(w, r, func(s *fsm.StateMachine, p *orderRequest) (any, lib.ErrorI) { - return s.GetOrder(p.OrderId, p.ChainId) + orderId, err := lib.StringToBytes(p.OrderId) + if err != nil { + return nil, err + } + return s.GetOrder(orderId, p.ChainId) }) } diff --git a/cmd/rpc/sock.go b/cmd/rpc/sock.go index 186052007e..4bae647f08 100644 --- a/cmd/rpc/sock.go +++ b/cmd/rpc/sock.go @@ -179,7 +179,7 @@ func (r *RCManager) GetOrders(rootChainId, rootHeight, id uint64) (*lib.OrderBoo } // Order() returns a specific order from the root order book -func (r *RCManager) GetOrder(rootChainId, height, orderId, chainId uint64) (*lib.SellOrder, lib.ErrorI) { +func (r *RCManager) GetOrder(rootChainId, height uint64, orderId string, chainId uint64) (*lib.SellOrder, lib.ErrorI) { // if the root chain id is the same as the info sub, found := r.subscriptions[rootChainId] if !found { diff --git a/cmd/rpc/types.go b/cmd/rpc/types.go index 2ad57a5bc5..b277392b0d 100644 --- a/cmd/rpc/types.go +++ b/cmd/rpc/types.go @@ -20,7 +20,7 @@ type chainRequest struct { type orderRequest struct { ChainId uint64 `json:"chainId"` - OrderId uint64 `json:"orderId"` + OrderId string `json:"orderId"` heightRequest } @@ -224,7 +224,7 @@ type txEditOrder struct { Submit bool `json:"submit"` ReceiveAmount uint64 `json:"receiveAmount"` ReceiveAddress lib.HexBytes `json:"receiveAddress"` - OrderId uint64 `json:"orderId"` + OrderId string `json:"orderId"` fromFields txChangeParamRequest committeesRequest @@ -232,7 +232,7 @@ type txEditOrder struct { type txDeleteOrder struct { Fee uint64 `json:"fee"` - OrderId uint64 `json:"orderId"` + OrderId string `json:"orderId"` Password string `json:"password"` fromFields txChangeParamRequest @@ -241,7 +241,7 @@ type txDeleteOrder struct { type txLockOrder struct { Fee uint64 `json:"fee"` - OrderId uint64 `json:"orderId"` + OrderId string `json:"orderId"` Password string `json:"password"` ReceiveAddress lib.HexBytes `json:"receiveAddress"` fromFields @@ -251,7 +251,7 @@ type txLockOrder struct { type txCloseOrder struct { Fee uint64 `json:"fee"` - OrderId uint64 `json:"orderId"` + OrderId string `json:"orderId"` Password string `json:"password"` fromFields } @@ -304,7 +304,7 @@ type txRequest struct { Submit bool `json:"submit"` ReceiveAmount uint64 `json:"receiveAmount"` ReceiveAddress lib.HexBytes `json:"receiveAddress"` - OrderId uint64 `json:"orderId"` + OrderId string `json:"orderId"` Memo string `json:"memo"` PollJSON json.RawMessage `json:"pollJSON"` PollApprove bool `json:"pollApprove"` diff --git a/cmd/rpc/web/explorer/components/table.jsx b/cmd/rpc/web/explorer/components/table.jsx index 5d54527244..96f224979e 100644 --- a/cmd/rpc/web/explorer/components/table.jsx +++ b/cmd/rpc/web/explorer/components/table.jsx @@ -15,6 +15,7 @@ import { // convertValue() converts the value based on its key and handles different types function convertValue(k, v, openModal) { + if (k === "Id") return v; if (k === "publicKey") return ; if (isHex(v) || k === "height") { const content = isNumber(v) ? v : ; @@ -122,7 +123,7 @@ function convertGovernanceParams(v) { function convertOrder(v) { const exchangeRate = v.requestedAmount / v.amountForSale; return { - Id: v.id ?? 0, + Id: v.id ?? "error", Chain: v.committee, AmountForSale: toCNPY(v.amountForSale), Rate: exchangeRate.toFixed(2), @@ -226,7 +227,7 @@ export default function DTable(props) { {sortedData.map((val, idx) => ( {Object.keys(val).map((k, i) => ( - + {convertValue(k, val[k], props.openModal)} ))} diff --git a/cmd/rpc/web/explorer/styles/globals.css b/cmd/rpc/web/explorer/styles/globals.css index 7fa605e23b..8baf26b791 100644 --- a/cmd/rpc/web/explorer/styles/globals.css +++ b/cmd/rpc/web/explorer/styles/globals.css @@ -137,6 +137,7 @@ input::placeholder { } .table { + table-layout: auto; background-color: #d2d2d2; border: 1px solid #d2d2d2; border-radius: 5px !important; @@ -364,10 +365,6 @@ h5 { margin-bottom: 25px !important; } -th { - font-family: var(--font-heading); -} - .detail-table-title { white-space: nowrap; font-weight: 600; @@ -411,6 +408,18 @@ th { float: right; } +td, th { + font-family: var(--font-heading); +} + +.large-table-col { + max-width: 1000px !important; + font-size: 13px !important; + padding-left: 10px !important; + padding-top: 15px !important; + padding-bottom: 15px !important; +} + .table-col { max-width: 200px !important; font-size: 13px !important; diff --git a/cmd/rpc/web/wallet/components/account.jsx b/cmd/rpc/web/wallet/components/account.jsx index 87b6a22a9e..305ca5f12e 100644 --- a/cmd/rpc/web/wallet/components/account.jsx +++ b/cmd/rpc/web/wallet/components/account.jsx @@ -2,23 +2,24 @@ import { KeystoreGet, KeystoreImport, KeystoreNew, - TxLockOrder, TxCloseOrder, TxCreateOrder, TxDeleteOrder, TxEditOrder, TxEditStake, + TxLockOrder, TxPause, TxSend, TxStake, TxUnpause, - TxUnstake, + TxUnstake } from "@/components/api"; import FormInputs from "@/components/form_inputs"; import { copy, downloadJSON, formatNumber, + getActionFee, getFormInputs, numberFromCommas, onFormSubmit, @@ -26,23 +27,22 @@ import { retryWithDelay, toCNPY, toUCNPY, - withTooltip, - getActionFee, + withTooltip } from "@/components/util"; import { KeystoreContext } from "@/pages"; import { - LockIcon, + CloseIcon, CopyIcon, + DeleteOrderIcon, EditOrderIcon, EditStakeIcon, - DeleteOrderIcon, + LockIcon, PauseIcon, - UnpauseIcon, SendIcon, StakeIcon, SwapIcon, - UnstakeIcon, - CloseIcon, + UnpauseIcon, + UnstakeIcon } from "@/components/svg_icons"; import { useContext, useEffect, useRef, useState } from "react"; import { Button, Card, Col, Form, Modal, Row, Spinner, Table } from "react-bootstrap"; @@ -67,7 +67,7 @@ const transactionButtons = [ { title: "LOCK", name: "lock_order", src: LockIcon }, { title: "CLOSE", name: "close_order", src: CloseIcon }, { title: "REPRICE", name: "edit_order", src: EditOrderIcon }, - { title: "VOID", name: "delete_order", src: DeleteOrderIcon }, + { title: "VOID", name: "delete_order", src: DeleteOrderIcon } ]; // Accounts() returns the main component of this file @@ -89,7 +89,7 @@ export default function Accounts({ keygroup, account, validator, setActiveKey, p showAlert: false, alertMsg: "", primaryColor: "", - greyColor: "", + greyColor: "" }), acc = account.account; @@ -124,7 +124,7 @@ export default function Accounts({ keygroup, account, validator, setActiveKey, p setState((prevState) => ({ ...prevState, primaryColor, - greyColor, + greyColor })); }, []); @@ -138,7 +138,7 @@ export default function Accounts({ keygroup, account, validator, setActiveKey, p showModal: false, showPKModal: false, showNewModal: false, - showPKImportModal: false, + showPKImportModal: false }); } @@ -200,7 +200,7 @@ export default function Accounts({ keygroup, account, validator, setActiveKey, p resetState, 10, 1000, - false, + false ); } @@ -209,7 +209,7 @@ export default function Accounts({ keygroup, account, validator, setActiveKey, p onFormSubmit(state, e, ks, (r) => KeystoreGet(r.sender, r.password, r.nickname).then((r) => { setState({ ...state, showSubmit: Object.keys(state.txResult).length === 0, pk: r }); - }), + }) ); } @@ -219,7 +219,7 @@ export default function Accounts({ keygroup, account, validator, setActiveKey, p KeystoreNew(r.password, r.nickname).then((r) => { setState({ ...state, showSubmit: Object.keys(state.txResult).length === 0, pk: r }); setActivePrivateKey(r.nickname); - }), + }) ); } @@ -266,7 +266,7 @@ export default function Accounts({ keygroup, account, validator, setActiveKey, p r.memo, fee, r.password, - submit, + submit ), "edit-stake": () => TxEditStake( @@ -280,29 +280,29 @@ export default function Accounts({ keygroup, account, validator, setActiveKey, p r.memo, fee, r.password, - submit, + submit ), unstake: () => TxUnstake(r.sender, r.signer, r.memo, fee, r.password, submit), pause: () => TxPause(r.sender, r.signer, r.memo, fee, r.password, submit), unpause: () => TxUnpause(r.sender, r.signer, r.memo, fee, r.password, submit), create_order: () => TxCreateOrder(r.sender, r.chainId, amount, receiveAmount, r.receiveAddress, r.memo, fee, r.password, submit), - close_order: () => TxCloseOrder(r.sender, numberFromCommas(r.orderId), fee, r.password, submit), - lock_order: () => TxLockOrder(r.sender, r.receiveAddress, numberFromCommas(r.orderId), fee, r.password, submit), + close_order: () => TxCloseOrder(r.sender, r.orderId, fee, r.password, submit), + lock_order: () => TxLockOrder(r.sender, r.receiveAddress, r.orderId, fee, r.password, submit), edit_order: () => TxEditOrder( r.sender, r.chainId, - numberFromCommas(r.orderId), + r.orderId, amount, receiveAmount, r.receiveAddress, r.memo, fee, r.password, - submit, + submit ), - delete_order: () => TxDeleteOrder(r.sender, r.chainId, r.orderId, r.memo, fee, r.password, submit), + delete_order: () => TxDeleteOrder(r.sender, r.chainId, r.orderId, r.memo, fee, r.password, submit) }; const txFunction = txMap[state.txType]; @@ -316,7 +316,7 @@ export default function Accounts({ keygroup, account, validator, setActiveKey, p setState({ ...state, showAlert: true, - alertMsg: "Transaction failed. Please verify the fields and try again.", + alertMsg: "Transaction failed. Please verify the fields and try again." }); }); } @@ -379,7 +379,7 @@ export default function Accounts({ keygroup, account, validator, setActiveKey, p {[ { title: "Account Type", info: getAccountType() }, { title: "Stake Amount", info: getValidatorAmount(), after: " cnpy" }, - { title: "Staked Status", info: getStakedStatus() }, + { title: "Staked Status", info: getStakedStatus() } ].map((v, i) => ( ))} @@ -389,7 +389,7 @@ export default function Accounts({ keygroup, account, validator, setActiveKey, p {[ { title: "Nickname", info: keygroup.keyNickname }, { title: "Address", info: keygroup.keyAddress }, - { title: "Public Key", info: keygroup.publicKey }, + { title: "Public Key", info: keygroup.publicKey } ].map((v, i) => ( ))} @@ -496,7 +496,7 @@ function AccSumTabCol({ detail, i, state, setState }) { , detail, i, - "top", + "top" ); } @@ -566,25 +566,25 @@ function RenderButtons({ type, state, closeOnClick }) { // RenderModal() returns the transaction modal function RenderModal({ - show, - title, - txType, - onFormSub, - keyGroup, - account, - validator, - onHide, - btnType, - setState, - state, - closeOnClick, - keystore, - showAlert = false, - alertMsg, - JsonViewVariant, - onFormFieldChange, - params, -}) { + show, + title, + txType, + onFormSub, + keyGroup, + account, + validator, + onHide, + btnType, + setState, + state, + closeOnClick, + keystore, + showAlert = false, + alertMsg, + JsonViewVariant, + onFormFieldChange, + params + }) { return (
@@ -608,7 +608,7 @@ function RenderModal({ } return input; })} - account={(function () { + account={(function() { // copy accounts and extract the fee if any let accountCopy = Object.assign({}, account); accountCopy.amount -= getActionFee(txType, params?.fee) ?? 0; @@ -672,23 +672,23 @@ function RenderTransactions({ account, state, setState }) { RECENT TRANSACTIONS - - {["Height", "Amount", "Recipient", "Type", "Hash", "Status"].map((k, i) => ( - - ))} - + + {["Height", "Amount", "Recipient", "Type", "Hash", "Status"].map((k, i) => ( + + ))} + - {account.combined.slice(0, 5).map((v, i) => ( - - - - - - - - - ))} + {account.combined.slice(0, 5).map((v, i) => ( + + + + + + + + + ))}
{k}
{k}
{v.height || "N/A"}{toCNPY(v.transaction.msg.amount) || toCNPY(v.transaction.msg.amountForSale) || "N/A"}{v.messageType || v.transaction.type}{v.status ?? ""}
{v.height || "N/A"}{toCNPY(v.transaction.msg.amount) || toCNPY(v.transaction.msg.amountForSale) || "N/A"}{v.messageType || v.transaction.type}{v.status ?? ""}
diff --git a/cmd/rpc/web/wallet/components/api.js b/cmd/rpc/web/wallet/components/api.js index 56a7c85f8c..4bd50ac974 100644 --- a/cmd/rpc/web/wallet/components/api.js +++ b/cmd/rpc/web/wallet/components/api.js @@ -516,7 +516,7 @@ export async function TxCreateOrder( newSellOrderTxRequest( address, chainId, - 0, + "", Number(sellAmount), Number(receiveAmount), receiveAddress, @@ -532,7 +532,7 @@ export async function TxLockOrder(address, receiveAddress, orderId, fee, passwor return POST( adminRPCURL, txLockOrder, - newLockOrderTxRequest(address, receiveAddress, Number(orderId), Number(fee), submit, password), + newLockOrderTxRequest(address, receiveAddress, orderId, Number(fee), submit, password), ); } @@ -540,7 +540,7 @@ export async function TxCloseOrder(address, orderId, fee, password, submit) { return POST( adminRPCURL, txCloseOrder, - newCloseOrderTxRequest(address, Number(orderId), Number(fee), submit, password), + newCloseOrderTxRequest(address, orderId, Number(fee), submit, password), ); } @@ -562,7 +562,7 @@ export async function TxEditOrder( newSellOrderTxRequest( address, chainId, - Number(orderId), + orderId, Number(sellAmount), Number(receiveAmount), receiveAddress, @@ -578,7 +578,7 @@ export async function TxDeleteOrder(address, chainId, orderId, memo, fee, passwo return POST( adminRPCURL, txDeleteOrder, - newSellOrderTxRequest(address, chainId, Number(orderId), 0, 0, "", memo, Number(fee), submit, password), + newSellOrderTxRequest(address, chainId, orderId, 0, 0, "", memo, Number(fee), submit, password), ); } diff --git a/cmd/rpc/web/wallet/components/util.js b/cmd/rpc/web/wallet/components/util.js index 679436f4ba..6ec09f972b 100644 --- a/cmd/rpc/web/wallet/components/util.js +++ b/cmd/rpc/web/wallet/components/util.js @@ -132,9 +132,9 @@ export function getFormInputs(type, keyGroup, account, validator, keyStore) { inputText: "order-id", feedback: "please input an order id", required: true, - type: "number", - minLength: 1, - maxLength: 100, + type: "text", + minLength: 40, + maxLength: 40, }, chainId: { placeholder: "the id of the committee / counter asset", diff --git a/fsm/genesis.go b/fsm/genesis.go index d86347abe7..eaf780e198 100644 --- a/fsm/genesis.go +++ b/fsm/genesis.go @@ -127,11 +127,11 @@ func (s *StateMachine) ValidateGenesisState(genesis *GenesisState) (err lib.Erro return InvalidSellOrder() } // ensure there's no duplicate order-ids within the book - deDuplicateIds := lib.NewDeDuplicator[uint64]() + deDuplicateIds := lib.NewDeDuplicator[string]() // for each order in the book for _, order := range orderBook.Orders { // check if order already found - if found := deDuplicateIds.Found(order.Id); found { + if found := deDuplicateIds.Found(lib.BytesToString(order.Id)); found { return InvalidSellOrder() } } diff --git a/fsm/genesis_test.go b/fsm/genesis_test.go index c1f8fa7085..06e200f62b 100644 --- a/fsm/genesis_test.go +++ b/fsm/genesis_test.go @@ -119,7 +119,7 @@ func TestNewFromGenesisFile(t *testing.T) { OrderBooks: []*lib.OrderBook{{ ChainId: lib.CanopyChainId, Orders: []*lib.SellOrder{{ - Id: 1, + Id: newTestOrderId(t, 1), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -127,7 +127,7 @@ func TestNewFromGenesisFile(t *testing.T) { BuyerReceiveAddress: newTestAddressBytes(t, 1), BuyerChainDeadline: 100, SellersSendAddress: newTestAddressBytes(t, 2), }, { - Id: 2, + Id: newTestOrderId(t, 2), Committee: 2, AmountForSale: 100, RequestedAmount: 100, @@ -147,7 +147,7 @@ func TestNewFromGenesisFile(t *testing.T) { OrderBooks: []*lib.OrderBook{{ ChainId: lib.CanopyChainId, Orders: []*lib.SellOrder{{ - Id: 1, + Id: newTestOrderId(t, 1), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -155,7 +155,7 @@ func TestNewFromGenesisFile(t *testing.T) { BuyerReceiveAddress: newTestAddressBytes(t, 1), BuyerChainDeadline: 100, SellersSendAddress: newTestAddressBytes(t, 2), }, { - Id: 2, + Id: newTestOrderId(t, 2), Committee: 2, AmountForSale: 100, RequestedAmount: 100, @@ -291,7 +291,7 @@ func TestNewStateFromGenesisFile(t *testing.T) { OrderBooks: []*lib.OrderBook{{ ChainId: lib.CanopyChainId, Orders: []*lib.SellOrder{{ - Id: 1, + Id: newTestOrderId(t, 1), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -300,7 +300,7 @@ func TestNewStateFromGenesisFile(t *testing.T) { BuyerChainDeadline: 100, SellersSendAddress: newTestAddressBytes(t, 2), }, { - Id: 2, + Id: newTestOrderId(t, 2), Committee: 2, AmountForSale: 100, RequestedAmount: 100, @@ -334,7 +334,7 @@ func TestNewStateFromGenesisFile(t *testing.T) { OrderBooks: []*lib.OrderBook{{ ChainId: lib.CanopyChainId, Orders: []*lib.SellOrder{{ - Id: 1, + Id: newTestOrderId(t, 1), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -342,7 +342,7 @@ func TestNewStateFromGenesisFile(t *testing.T) { BuyerReceiveAddress: newTestAddressBytes(t, 1), BuyerChainDeadline: 100, SellersSendAddress: newTestAddressBytes(t, 2), }, { - Id: 2, + Id: newTestOrderId(t, 2), Committee: 2, AmountForSale: 100, RequestedAmount: 100, @@ -461,7 +461,7 @@ func TestNewStateFromGenesisFile(t *testing.T) { OrderBooks: []*lib.OrderBook{{ ChainId: lib.CanopyChainId, Orders: []*lib.SellOrder{{ - Id: 1, + Id: newTestOrderId(t, 1), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -469,7 +469,7 @@ func TestNewStateFromGenesisFile(t *testing.T) { BuyerReceiveAddress: newTestAddressBytes(t, 1), BuyerChainDeadline: 100, SellersSendAddress: newTestAddressBytes(t, 2), }, { - Id: 2, + Id: newTestOrderId(t, 2), Committee: 1, AmountForSale: 100, RequestedAmount: 100, @@ -488,7 +488,7 @@ func TestNewStateFromGenesisFile(t *testing.T) { OrderBooks: []*lib.OrderBook{{ ChainId: lib.CanopyChainId, Orders: []*lib.SellOrder{{ - Id: 1, + Id: newTestOrderId(t, 1), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -496,7 +496,7 @@ func TestNewStateFromGenesisFile(t *testing.T) { BuyerReceiveAddress: newTestAddressBytes(t, 1), BuyerChainDeadline: 100, SellersSendAddress: newTestAddressBytes(t, 2), }, { - Id: 2, + Id: newTestOrderId(t, 2), Committee: 1, AmountForSale: 100, RequestedAmount: 100, @@ -621,7 +621,7 @@ func TestValidateGenesisState(t *testing.T) { ChainId: 0, Orders: []*lib.SellOrder{ { - Id: 1, + Id: newTestOrderId(t, 1), Committee: 0, AmountForSale: 100, SellersSendAddress: newTestAddressBytes(t), @@ -632,7 +632,7 @@ func TestValidateGenesisState(t *testing.T) { ChainId: 0, Orders: []*lib.SellOrder{ { - Id: 2, + Id: newTestOrderId(t, 2), Committee: 0, AmountForSale: 101, SellersSendAddress: newTestAddressBytes(t, 1), @@ -655,13 +655,13 @@ func TestValidateGenesisState(t *testing.T) { ChainId: 0, Orders: []*lib.SellOrder{ { - Id: 1, + Id: newTestOrderId(t, 1), Committee: 0, AmountForSale: 100, SellersSendAddress: newTestAddressBytes(t), }, { - Id: 1, + Id: newTestOrderId(t, 1), Committee: 0, AmountForSale: 101, SellersSendAddress: newTestAddressBytes(t, 2), @@ -712,7 +712,7 @@ func newTestGenesisState(t *testing.T) *GenesisState { OrderBooks: []*lib.OrderBook{{ ChainId: lib.CanopyChainId, Orders: []*lib.SellOrder{{ - Id: 1, + Id: newTestOrderId(t, 1), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -721,7 +721,7 @@ func newTestGenesisState(t *testing.T) *GenesisState { BuyerChainDeadline: 100, SellersSendAddress: newTestAddressBytes(t, 2), }, { - Id: 2, + Id: newTestOrderId(t, 2), Committee: 2, AmountForSale: 100, RequestedAmount: 100, @@ -759,7 +759,7 @@ func newTestValidateGenesisState(t *testing.T) *GenesisState { OrderBooks: []*lib.OrderBook{{ ChainId: lib.CanopyChainId, Orders: []*lib.SellOrder{{ - Id: 1, + Id: newTestOrderId(t, 1), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -768,7 +768,7 @@ func newTestValidateGenesisState(t *testing.T) *GenesisState { BuyerChainDeadline: 100, SellersSendAddress: newTestAddressBytes(t, 2), }, { - Id: 2, + Id: newTestOrderId(t, 2), Committee: 2, AmountForSale: 100, RequestedAmount: 100, diff --git a/fsm/key.go b/fsm/key.go index 7947a9d482..1b88e22346 100644 --- a/fsm/key.go +++ b/fsm/key.go @@ -56,12 +56,14 @@ func PausedPrefix(height uint64) []byte { return lib.JoinLenPrefix(pausedPrefix, func LastProposersPrefix() []byte { return lib.JoinLenPrefix(lastProposersPrefix) } func CommitteePrefix(id uint64) []byte { return lib.JoinLenPrefix(committeePrefix, formatUint64(id)) } func DelegatePrefix(id uint64) []byte { return lib.JoinLenPrefix(delegatePrefix, formatUint64(id)) } -func OrderBookPrefix() []byte { return lib.JoinLenPrefix(orderBookPrefix) } func CommitteesDataPrefix() []byte { return lib.JoinLenPrefix(committeesDataPrefix) } func RetiredCommitteesPrefix() []byte { return lib.JoinLenPrefix(retiredCommitteePrefix) } func KeyForPool(n uint64) []byte { return lib.JoinLenPrefix(poolPrefix, formatUint64(n)) } func KeyForNonSigner(a []byte) []byte { return lib.JoinLenPrefix(nonSignerPrefix, a) } -func KeyForOrderBook(id uint64) []byte { return lib.JoinLenPrefix(orderBookPrefix, formatUint64(id)) } +func OrderBookPrefix(cId uint64) []byte { return lib.JoinLenPrefix(orderBookPrefix, formatUint64(cId)) } +func KeyForOrder(chainId uint64, orderId []byte) []byte { + return append(OrderBookPrefix(chainId), lib.JoinLenPrefix(orderId)...) +} func KeyForUnstaking(height uint64, address crypto.AddressI) []byte { return append(UnstakingPrefix(height), lib.JoinLenPrefix(address.Bytes())...) } @@ -94,7 +96,7 @@ func AddressFromKey(k []byte) (crypto.AddressI, lib.ErrorI) { func IdFromKey(k []byte) (uint64, lib.ErrorI) { segments := lib.DecodeLengthPrefixed(k) - if len(segments) != 2 { + if len(segments) < 2 { return 0, ErrInvalidKey(k) } return binary.BigEndian.Uint64(segments[1]), nil diff --git a/fsm/message.go b/fsm/message.go index 69f4921a23..bc12222e97 100644 --- a/fsm/message.go +++ b/fsm/message.go @@ -371,14 +371,14 @@ func (s *StateMachine) HandleMessageCreateOrder(msg *MessageCreateOrder) (err li return } // save the order in state - _, err = s.CreateOrder(&lib.SellOrder{ + return s.SetOrder(&lib.SellOrder{ + Id: msg.Hash, Committee: msg.ChainId, AmountForSale: msg.AmountForSale, RequestedAmount: msg.RequestedAmount, SellerReceiveAddress: msg.SellerReceiveAddress, SellersSendAddress: msg.SellersSendAddress, }, msg.ChainId) - return } // HandleMessageEditOrder() is the proper handler for a `EditOrder` message @@ -387,9 +387,11 @@ func (s *StateMachine) HandleMessageEditOrder(msg *MessageEditOrder) (err lib.Er if err != nil { return } + // ensure the order isn't locked if order.BuyerReceiveAddress != nil { return lib.ErrOrderLocked() } + // get the validator params from state valParams, err := s.GetParamsVal() if err != nil { return @@ -398,7 +400,9 @@ func (s *StateMachine) HandleMessageEditOrder(msg *MessageEditOrder) (err lib.Er if msg.AmountForSale < valParams.MinimumOrderSize { return ErrMinimumOrderSize() } + // calculate the difference difference, address := int(msg.AmountForSale-order.AmountForSale), crypto.NewAddress(order.SellersSendAddress) + // if adding to the order if difference > 0 { amountDifference := uint64(difference) if err = s.AccountSub(address, amountDifference); err != nil { @@ -408,6 +412,7 @@ func (s *StateMachine) HandleMessageEditOrder(msg *MessageEditOrder) (err lib.Er if err = s.PoolAdd(msg.ChainId+uint64(EscrowPoolAddend), amountDifference); err != nil { return } + // if subtracting from the order } else if difference < 0 { amountDifference := uint64(difference * -1) // subtract from the committee escrow pool @@ -418,7 +423,8 @@ func (s *StateMachine) HandleMessageEditOrder(msg *MessageEditOrder) (err lib.Er return } } - err = s.EditOrder(&lib.SellOrder{ + // set the new order in state + return s.SetOrder(&lib.SellOrder{ Id: order.Id, Committee: msg.ChainId, AmountForSale: msg.AmountForSale, @@ -426,7 +432,6 @@ func (s *StateMachine) HandleMessageEditOrder(msg *MessageEditOrder) (err lib.Er SellerReceiveAddress: msg.SellerReceiveAddress, SellersSendAddress: order.SellersSendAddress, }, msg.ChainId) - return } // HandleMessageDeleteOrder() is the proper handler for a `DeleteOrder` message diff --git a/fsm/message.pb.go b/fsm/message.pb.go index 99b5526b86..95845b0899 100644 --- a/fsm/message.pb.go +++ b/fsm/message.pb.go @@ -826,6 +826,8 @@ type MessageCreateOrder struct { SellerReceiveAddress []byte `protobuf:"bytes,4,opt,name=SellerReceiveAddress,proto3" json:"sellerReceiveAddress"` // @gotags: json:"sellerReceiveAddress" // sellers_send_address: the Canopy address the seller is selling and signing from SellersSendAddress []byte `protobuf:"bytes,5,opt,name=SellersSendAddress,proto3" json:"sellersSendAddress"` // @gotags: json:"sellersSendAddress" + // hash: auto-populated by the state machine to assign the unique bytes to the order + Hash []byte `protobuf:"bytes,6,opt,name=hash,proto3" json:"hash,omitempty"` } func (x *MessageCreateOrder) Reset() { @@ -895,6 +897,13 @@ func (x *MessageCreateOrder) GetSellersSendAddress() []byte { return nil } +func (x *MessageCreateOrder) GetHash() []byte { + if x != nil { + return x.Hash + } + return nil +} + // MessageEditOrder modifies an un-claimed token swap 'sell order', token amount may be increased or decreased as well // as the recipient address // If an order is already 'claimed' or 'bought', the order may not be modified @@ -905,7 +914,7 @@ type MessageEditOrder struct { // order_id: is the number id that is unique to this committee to identify the order // not modifiable, used for order identification only - OrderId uint64 `protobuf:"varint,1,opt,name=OrderId,proto3" json:"orderID"` // @gotags: json:"orderID" + OrderId []byte `protobuf:"bytes,1,opt,name=OrderId,proto3" json:"orderID"` // @gotags: json:"orderID" // chain_id: the id of the committee that is responsible for the 'counter asset' the uCNPY will swapped for // not modifiable, used for order identification only ChainId uint64 `protobuf:"varint,2,opt,name=ChainId,proto3" json:"chainID"` // @gotags: json:"chainID" @@ -950,11 +959,11 @@ func (*MessageEditOrder) Descriptor() ([]byte, []int) { return file_message_proto_rawDescGZIP(), []int{11} } -func (x *MessageEditOrder) GetOrderId() uint64 { +func (x *MessageEditOrder) GetOrderId() []byte { if x != nil { return x.OrderId } - return 0 + return nil } func (x *MessageEditOrder) GetChainId() uint64 { @@ -993,7 +1002,7 @@ type MessageDeleteOrder struct { unknownFields protoimpl.UnknownFields // order_id: is the number id that is unique to this committee to identify the order - OrderId uint64 `protobuf:"varint,1,opt,name=OrderId,proto3" json:"orderID"` // @gotags: json:"orderID" + OrderId []byte `protobuf:"bytes,1,opt,name=OrderId,proto3" json:"orderID"` // @gotags: json:"orderID" // chain_id: the id of the committee that is responsible for the 'counter asset' the uCNPY will swapped for ChainId uint64 `protobuf:"varint,2,opt,name=ChainId,proto3" json:"chainID"` // @gotags: json:"chainID" } @@ -1030,11 +1039,11 @@ func (*MessageDeleteOrder) Descriptor() ([]byte, []int) { return file_message_proto_rawDescGZIP(), []int{12} } -func (x *MessageDeleteOrder) GetOrderId() uint64 { +func (x *MessageDeleteOrder) GetOrderId() []byte { if x != nil { return x.OrderId } - return 0 + return nil } func (x *MessageDeleteOrder) GetChainId() uint64 { @@ -1137,7 +1146,7 @@ var file_message_proto_rawDesc = []byte{ 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x6f, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x22, 0xe2, 0x01, 0x0a, 0x12, 0x4d, 0x65, 0x73, 0x73, + 0x06, 0x6f, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x22, 0xf6, 0x01, 0x0a, 0x12, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x41, 0x6d, 0x6f, 0x75, @@ -1151,28 +1160,29 @@ var file_message_proto_rawDesc = []byte{ 0x63, 0x65, 0x69, 0x76, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, - 0x73, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0xca, 0x01, 0x0a, - 0x10, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, 0x64, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x12, 0x18, 0x0a, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x43, - 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x43, 0x68, - 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x46, - 0x6f, 0x72, 0x53, 0x61, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x41, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x53, 0x61, 0x6c, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x41, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x14, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, - 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x14, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x63, 0x65, 0x69, - 0x76, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x48, 0x0a, 0x12, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, - 0x18, 0x0a, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x43, 0x68, 0x61, - 0x69, 0x6e, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x43, 0x68, 0x61, 0x69, - 0x6e, 0x49, 0x64, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x63, 0x61, 0x6e, 0x6f, 0x70, 0x79, 0x2d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x2f, 0x63, 0x61, 0x6e, 0x6f, 0x70, 0x79, 0x2f, 0x66, 0x73, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x73, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, + 0x22, 0xca, 0x01, 0x0a, 0x10, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, 0x64, 0x69, 0x74, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x18, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x07, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x53, 0x61, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0d, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x53, 0x61, 0x6c, 0x65, 0x12, + 0x28, 0x0a, 0x0f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x14, 0x53, 0x65, 0x6c, + 0x6c, 0x65, 0x72, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x53, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x52, + 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x48, 0x0a, + 0x12, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, + 0x07, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, + 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61, 0x6e, 0x6f, 0x70, 0x79, 0x2d, 0x6e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x63, 0x61, 0x6e, 0x6f, 0x70, 0x79, 0x2f, 0x66, 0x73, 0x6d, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/fsm/message_helpers.go b/fsm/message_helpers.go index d7c3395da8..c68eebb86e 100644 --- a/fsm/message_helpers.go +++ b/fsm/message_helpers.go @@ -643,7 +643,7 @@ func (x *MessageEditOrder) UnmarshalJSON(b []byte) (err error) { } type jsonMessageEditOrder struct { - OrderId uint64 `json:"orderID"` + OrderId lib.HexBytes `json:"orderID"` ChainId uint64 `json:"chainID"` AmountForSale uint64 `json:"amountForSale"` RequestedAmount uint64 `json:"requestedAmount"` @@ -681,8 +681,8 @@ func (x *MessageDeleteOrder) UnmarshalJSON(b []byte) (err error) { } type jsonMessageDeleteOrder struct { - OrderId uint64 `json:"orderID"` - ChainId uint64 `json:"chainID"` + OrderId lib.HexBytes `json:"orderID"` + ChainId uint64 `json:"chainID"` } // checkAmount() validates the amount sent in the Message @@ -790,14 +790,16 @@ func checkStartEndHeight(proposal GovProposal) lib.ErrorI { return nil } +// checkOrders() validates the (swap) orders within the transaction func checkOrders(orders *lib.Orders) lib.ErrorI { if orders != nil { - deDupe := lib.NewDeDuplicator[uint64]() + // ensure no duplicate lock orders + deDupe := lib.NewDeDuplicator[string]() for _, lockOrder := range orders.LockOrders { if lockOrder == nil { return ErrInvalidLockOrder() } - if found := deDupe.Found(lockOrder.OrderId); found { + if found := deDupe.Found(lib.BytesToString(lockOrder.OrderId)); found { return ErrDuplicateLockOrder() } if err := checkAddress(lockOrder.BuyerReceiveAddress); err != nil { @@ -807,15 +809,17 @@ func checkOrders(orders *lib.Orders) lib.ErrorI { return ErrInvalidBuyerDeadline() } } - deDupe = lib.NewDeDuplicator[uint64]() + // ensure no duplicate reset orders + deDupe = lib.NewDeDuplicator[string]() for _, resetOrder := range orders.ResetOrders { - if found := deDupe.Found(resetOrder); found { + if found := deDupe.Found(lib.BytesToString(resetOrder)); found { return ErrInvalidCloseOrder() } } - deDupe = lib.NewDeDuplicator[uint64]() + // ensure no duplicate close orders + deDupe = lib.NewDeDuplicator[string]() for _, closeOrder := range orders.CloseOrders { - if found := deDupe.Found(closeOrder); found { + if found := deDupe.Found(lib.BytesToString(closeOrder)); found { return ErrInvalidCloseOrder() } } diff --git a/fsm/message_test.go b/fsm/message_test.go index 02003df4b3..902ad50ecf 100644 --- a/fsm/message_test.go +++ b/fsm/message_test.go @@ -260,6 +260,7 @@ func TestHandleMessage(t *testing.T) { RequestedAmount: 1000, SellerReceiveAddress: newTestPublicKeyBytes(t), SellersSendAddress: newTestAddressBytes(t), + Hash: newTestOrderId(t, 0), }, validate: func(sm StateMachine) { // ensure the account was subtracted from @@ -271,7 +272,7 @@ func TestHandleMessage(t *testing.T) { require.NoError(t, e) require.Equal(t, amount, got) // ensure the order was created - order, e := sm.GetOrder(0, lib.CanopyChainId) + order, e := sm.GetOrder(newTestOrderId(t, 0), lib.CanopyChainId) require.NoError(t, e) require.Equal(t, amount, order.AmountForSale) }, @@ -292,7 +293,8 @@ func TestHandleMessage(t *testing.T) { // add to the pool require.NoError(t, sm.PoolAdd(lib.CanopyChainId+EscrowPoolAddend, amount)) // save the order in state - _, err = sm.CreateOrder(&lib.SellOrder{ + err = sm.SetOrder(&lib.SellOrder{ + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: amount, RequestedAmount: 1000, @@ -302,7 +304,7 @@ func TestHandleMessage(t *testing.T) { require.NoError(t, err) }, msg: &MessageEditOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), ChainId: lib.CanopyChainId, AmountForSale: amount * 2, RequestedAmount: 2000, @@ -318,7 +320,7 @@ func TestHandleMessage(t *testing.T) { require.NoError(t, e) require.Equal(t, amount*2, got) // ensure the order was edited - order, e := sm.GetOrder(0, lib.CanopyChainId) + order, e := sm.GetOrder(newTestOrderId(t, 0), lib.CanopyChainId) require.NoError(t, e) require.Equal(t, amount*2, order.AmountForSale) }, @@ -330,7 +332,8 @@ func TestHandleMessage(t *testing.T) { // add to the pool require.NoError(t, sm.PoolAdd(lib.CanopyChainId+EscrowPoolAddend, amount)) // save the order in state - _, err = sm.CreateOrder(&lib.SellOrder{ + err = sm.SetOrder(&lib.SellOrder{ + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: amount, RequestedAmount: 1000, @@ -340,7 +343,7 @@ func TestHandleMessage(t *testing.T) { require.NoError(t, err) }, msg: &MessageDeleteOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), ChainId: lib.CanopyChainId, }, validate: func(sm StateMachine) { @@ -353,7 +356,7 @@ func TestHandleMessage(t *testing.T) { require.NoError(t, e) require.Zero(t, got) // ensure the order was deleted - _, e = sm.GetOrder(0, lib.CanopyChainId) + _, e = sm.GetOrder(newTestOrderId(t, 0), lib.CanopyChainId) require.ErrorContains(t, e, "not found") }, }, @@ -595,7 +598,7 @@ func TestGetAuthorizedSignersFor(t *testing.T) { // preset a committee member require.NoError(t, sm.SetCommitteeMember(newTestAddress(t), lib.CanopyChainId, 100)) // preset an order - _, err := sm.CreateOrder(&lib.SellOrder{ + err := sm.SetOrder(&lib.SellOrder{ Committee: lib.CanopyChainId, SellersSendAddress: newTestAddressBytes(t), }, lib.CanopyChainId) @@ -1892,13 +1895,13 @@ func TestHandleMessageCertificateResults(t *testing.T) { }, Orders: &lib.Orders{ LockOrders: []*lib.LockOrder{{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), BuyerSendAddress: newTestAddressBytes(t), BuyerReceiveAddress: newTestAddressBytes(t), BuyerChainDeadline: 100, }}, - ResetOrders: []uint64{1}, - CloseOrders: []uint64{2}, + ResetOrders: [][]byte{newTestOrderId(t, 1)}, + CloseOrders: [][]byte{newTestOrderId(t, 2)}, }, Checkpoint: &lib.Checkpoint{Height: 1, BlockHash: crypto.Hash([]byte("block_hash"))}, } @@ -2059,7 +2062,8 @@ func TestHandleMessageCertificateResults(t *testing.T) { buyerAddress = newTestAddressBytes(t) } // upsert each order in state - _, err = sm.CreateOrder(&lib.SellOrder{ + err = sm.SetOrder(&lib.SellOrder{ + Id: newTestOrderId(t, i), Committee: lib.CanopyChainId + 1, BuyerReceiveAddress: buyerAddress, BuyerChainDeadline: 0, @@ -2078,7 +2082,7 @@ func TestHandleMessageCertificateResults(t *testing.T) { } // 1) validate the 'lock order' func() { - order, e := sm.GetOrder(0, lib.CanopyChainId+1) + order, e := sm.GetOrder(newTestOrderId(t, 0), lib.CanopyChainId+1) require.NoError(t, e) // convenience variable for lock order lockOrder := test.msg.Qc.Results.Orders.LockOrders[0] @@ -2089,7 +2093,7 @@ func TestHandleMessageCertificateResults(t *testing.T) { }() // 2) validate the 'reset order' func() { - order, e := sm.GetOrder(1, lib.CanopyChainId+1) + order, e := sm.GetOrder(newTestOrderId(t, 1), lib.CanopyChainId+1) require.NoError(t, e) // validate the receipt address was reset require.Len(t, order.BuyerReceiveAddress, 0) @@ -2099,8 +2103,8 @@ func TestHandleMessageCertificateResults(t *testing.T) { // 3) validate the 'close order' func() { - _, err = sm.GetOrder(2, lib.CanopyChainId+1) - require.ErrorContains(t, err, "order with id 2 not found") + _, err = sm.GetOrder(newTestOrderId(t, 2), lib.CanopyChainId+1) + require.ErrorContains(t, err, "not found") }() // 4) validate the 'checkpoint' service @@ -2229,6 +2233,7 @@ func TestMessageCreateOrder(t *testing.T) { RequestedAmount: 1, SellerReceiveAddress: newTestAddressBytes(t), SellersSendAddress: newTestAddressBytes(t), + Hash: newTestOrderId(t, 0), }, error: "minimum order size", }, @@ -2242,6 +2247,7 @@ func TestMessageCreateOrder(t *testing.T) { RequestedAmount: 1, SellerReceiveAddress: newTestAddressBytes(t), SellersSendAddress: newTestAddressBytes(t), + Hash: newTestOrderId(t, 0), }, error: "insufficient funds", }, @@ -2256,6 +2262,7 @@ func TestMessageCreateOrder(t *testing.T) { RequestedAmount: 1, SellerReceiveAddress: newTestAddressBytes(t), SellersSendAddress: newTestAddressBytes(t), + Hash: newTestOrderId(t, 0), }, }, } @@ -2293,10 +2300,11 @@ func TestMessageCreateOrder(t *testing.T) { // validate the addition to the pool require.Equal(t, test.msg.AmountForSale, got) // get the order in state - order, err := sm.GetOrder(0, test.msg.ChainId) + order, err := sm.GetOrder(newTestOrderId(t, 0), test.msg.ChainId) require.NoError(t, err) // validate the creation of the order require.EqualExportedValues(t, &lib.SellOrder{ + Id: newTestOrderId(t, 0), Committee: test.msg.ChainId, AmountForSale: test.msg.AmountForSale, RequestedAmount: test.msg.RequestedAmount, @@ -2322,7 +2330,7 @@ func TestHandleMessageEditOrder(t *testing.T) { name: "no order found", detail: "there exists no order", msg: &MessageEditOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), ChainId: lib.CanopyChainId, AmountForSale: 1, RequestedAmount: 0, @@ -2334,7 +2342,7 @@ func TestHandleMessageEditOrder(t *testing.T) { name: "order locked", detail: "a buyer has already accepted the order, thus it cannot be edited", preset: &lib.SellOrder{ - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 1, RequestedAmount: 0, @@ -2344,7 +2352,7 @@ func TestHandleMessageEditOrder(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, msg: &MessageEditOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), ChainId: lib.CanopyChainId, AmountForSale: 1, RequestedAmount: 0, @@ -2357,7 +2365,7 @@ func TestHandleMessageEditOrder(t *testing.T) { detail: "the edited order does not satisfy the minimum order size", minimumOrderSize: 2, preset: &lib.SellOrder{ - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 2, RequestedAmount: 0, @@ -2365,7 +2373,7 @@ func TestHandleMessageEditOrder(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, msg: &MessageEditOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), ChainId: lib.CanopyChainId, AmountForSale: 1, RequestedAmount: 0, @@ -2376,7 +2384,7 @@ func TestHandleMessageEditOrder(t *testing.T) { name: "insufficient funds", detail: "the account does not have the balance to cover the edit", preset: &lib.SellOrder{ - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 1, RequestedAmount: 0, @@ -2384,7 +2392,7 @@ func TestHandleMessageEditOrder(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, msg: &MessageEditOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), ChainId: lib.CanopyChainId, AmountForSale: 2, RequestedAmount: 0, @@ -2396,7 +2404,7 @@ func TestHandleMessageEditOrder(t *testing.T) { name: "edit receive address", detail: "the order simply updates the receive address but the amount stays the same", preset: &lib.SellOrder{ - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 1, RequestedAmount: 0, @@ -2404,14 +2412,14 @@ func TestHandleMessageEditOrder(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, msg: &MessageEditOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), ChainId: lib.CanopyChainId, AmountForSale: 1, RequestedAmount: 0, SellerReceiveAddress: newTestAddressBytes(t, 2), }, expected: &lib.SellOrder{ - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 1, SellerReceiveAddress: newTestAddressBytes(t, 2), @@ -2424,7 +2432,7 @@ func TestHandleMessageEditOrder(t *testing.T) { presetAccount: 1, minimumOrderSize: 0, preset: &lib.SellOrder{ - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 1, RequestedAmount: 0, @@ -2432,14 +2440,14 @@ func TestHandleMessageEditOrder(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, msg: &MessageEditOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), ChainId: lib.CanopyChainId, AmountForSale: 2, RequestedAmount: 0, SellerReceiveAddress: newTestAddressBytes(t, 2), }, expected: &lib.SellOrder{ - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 2, SellerReceiveAddress: newTestAddressBytes(t, 2), @@ -2450,7 +2458,7 @@ func TestHandleMessageEditOrder(t *testing.T) { name: "decrease sell amount", detail: "the order has a decreased the sell amount", preset: &lib.SellOrder{ - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 2, RequestedAmount: 0, @@ -2458,14 +2466,14 @@ func TestHandleMessageEditOrder(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, msg: &MessageEditOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), ChainId: lib.CanopyChainId, AmountForSale: 1, RequestedAmount: 0, SellerReceiveAddress: newTestAddressBytes(t, 2), }, expected: &lib.SellOrder{ - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 1, SellerReceiveAddress: newTestAddressBytes(t, 2), @@ -2490,13 +2498,8 @@ func TestHandleMessageEditOrder(t *testing.T) { require.NoError(t, sm.SetParamsVal(valParams)) // preset the account with tokens require.NoError(t, sm.AccountAdd(address, test.presetAccount)) - // get the proper order book - orderBook, err := sm.GetOrderBook(lib.CanopyChainId) - require.NoError(t, err) // preset the sell order - _ = orderBook.AddOrder(test.preset) - // set it back in state - require.NoError(t, sm.SetOrderBook(orderBook)) + require.NoError(t, sm.SetOrder(test.preset, lib.CanopyChainId)) // preset the pool with the amount to sell require.NoError(t, sm.PoolAdd(test.preset.Committee+EscrowPoolAddend, test.preset.AmountForSale)) } @@ -2519,7 +2522,7 @@ func TestHandleMessageEditOrder(t *testing.T) { // validate the subtraction/addition to/from the pool require.Equal(t, test.preset.AmountForSale-(test.preset.AmountForSale-test.msg.AmountForSale), got) // get the order in state - order, err := sm.GetOrder(0, test.msg.ChainId) + order, err := sm.GetOrder(newTestOrderId(t, 0), test.msg.ChainId) require.NoError(t, err) // validate the creation of the order require.EqualExportedValues(t, test.expected, order) @@ -2540,7 +2543,7 @@ func TestHandleMessageDelete(t *testing.T) { name: "no order found", detail: "there exists no order", msg: &MessageDeleteOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), ChainId: lib.CanopyChainId, }, error: "not found", @@ -2549,7 +2552,7 @@ func TestHandleMessageDelete(t *testing.T) { name: "order locked", detail: "a buyer has already accepted the order, thus it cannot be edited", preset: &lib.SellOrder{ - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 1, RequestedAmount: 0, @@ -2559,7 +2562,7 @@ func TestHandleMessageDelete(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, msg: &MessageDeleteOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), ChainId: lib.CanopyChainId, }, error: "order locked", @@ -2568,7 +2571,7 @@ func TestHandleMessageDelete(t *testing.T) { name: "successful delete", detail: "the order delete was successful", preset: &lib.SellOrder{ - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 2, RequestedAmount: 0, @@ -2576,7 +2579,7 @@ func TestHandleMessageDelete(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, msg: &MessageDeleteOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), ChainId: lib.CanopyChainId, }, }, @@ -2591,13 +2594,8 @@ func TestHandleMessageDelete(t *testing.T) { address = crypto.NewAddress(test.preset.SellersSendAddress) // preset the account with tokens require.NoError(t, sm.AccountAdd(address, test.presetAccount)) - // get the proper order book - orderBook, err := sm.GetOrderBook(lib.CanopyChainId) - require.NoError(t, err) // preset the sell order - _ = orderBook.AddOrder(test.preset) - // set it back in state - require.NoError(t, sm.SetOrderBook(orderBook)) + require.NoError(t, sm.SetOrder(test.preset, lib.CanopyChainId)) // preset the pool with the amount to sell require.NoError(t, sm.PoolAdd(test.preset.Committee+EscrowPoolAddend, test.preset.AmountForSale)) } @@ -2620,7 +2618,7 @@ func TestHandleMessageDelete(t *testing.T) { // validate the subtraction from the pool require.Zero(t, got) // validate the delete - _, err = sm.GetOrder(0, test.msg.ChainId) + _, err = sm.GetOrder(newTestOrderId(t, 0), test.msg.ChainId) require.ErrorContains(t, err, "not found") }) } diff --git a/fsm/swap.go b/fsm/swap.go index b74449a92b..3a15d68866 100644 --- a/fsm/swap.go +++ b/fsm/swap.go @@ -5,7 +5,7 @@ import ( "encoding/json" "github.com/canopy-network/canopy/lib" "github.com/canopy-network/canopy/lib/crypto" - "slices" + "sort" ) /* This file contains state machine changes related to 'token swapping' */ @@ -43,6 +43,8 @@ func (s *StateMachine) HandleCommitteeSwaps(orders *lib.Orders, chainId uint64) return } +// BUYER SIDE LOGIC + // ParseLockOrder() parses a transaction for an embedded lock order messages in the memo field func (s *StateMachine) ParseLockOrder(tx *lib.Transaction, deadlineBlocks uint64) (bo *lib.LockOrder, ok bool) { // check for valid json @@ -78,10 +80,10 @@ func (s *StateMachine) ParseCloseOrder(tx *lib.Transaction) (co *lib.CloseOrder, // ProcessRootChainOrderBook() processes the order book from the root-chain and cross-references blocks on this chain to determine // actions that warrant committee level changes to the root-chain order book like: ResetOrder and CloseOrder -func (s *StateMachine) ProcessRootChainOrderBook(book *lib.OrderBook, b *lib.BlockResult) (closeOrders, resetOrders []uint64) { +func (s *StateMachine) ProcessRootChainOrderBook(book *lib.OrderBook, b *lib.BlockResult) (closeOrders, resetOrders [][]byte) { // create a variable to track the 'close orders' // map structure is [orderId] -> amount sent - transferred := make(map[uint64]uint64) + transferred := make(map[string]uint64) // get all the 'Send' transactions from the block for _, tx := range b.Transactions { // ignore non-send, memo-less, or no valid json embedded @@ -108,7 +110,7 @@ func (s *StateMachine) ProcessRootChainOrderBook(book *lib.OrderBook, b *lib.Blo continue } // try to get the close order - order, err := book.GetOrder(int(closeOrder.OrderId)) + order, err := book.GetOrder(closeOrder.OrderId) // if an error occurred during the retrieval if err != nil { s.log.Warnf("An error occurred during the close order retrieval: %s", err.Error()) @@ -117,7 +119,7 @@ func (s *StateMachine) ProcessRootChainOrderBook(book *lib.OrderBook, b *lib.Blo // if the 'sent amount' is the same as the order amount if bytes.Equal(send.ToAddress, order.SellerReceiveAddress) && order.RequestedAmount == send.Amount { // update the transferred map using the [order_id] as the key and add send.Amount to the value - transferred[closeOrder.OrderId] += send.Amount + transferred[lib.BytesToString(closeOrder.OrderId)] += send.Amount } } // for each order in the book @@ -134,7 +136,7 @@ func (s *StateMachine) ProcessRootChainOrderBook(book *lib.OrderBook, b *lib.Blo continue } // check if the order was closed this block - if sent := transferred[order.Id]; sent == order.RequestedAmount { // double check on amount for sanity + if sent := transferred[lib.BytesToString(order.Id)]; sent == order.RequestedAmount { // double check on amount for sanity // add to the closed order list closeOrders = append(closeOrders, order.Id) // go to the next order @@ -161,9 +163,9 @@ func (s *StateMachine) ProcessRootChainOrderBook(book *lib.OrderBook, b *lib.Blo continue } // check if the 'close order' command was issued previously - if qc.Results.Orders != nil && slices.Contains(qc.Results.Orders.CloseOrders, order.Id) { + if qc.Results.Orders != nil && lib.ContainsByteSlice(qc.Results.Orders.CloseOrders, order.Id) { // ensure no already added to close order - if !slices.Contains(closeOrders, order.Id) { + if !lib.ContainsByteSlice(closeOrders, order.Id) { // if so, add it to the close orders closeOrders = append(closeOrders, order.Id) } @@ -187,7 +189,7 @@ func (s *StateMachine) ParseLockOrders(b *lib.BlockResult) (lockOrders []*lib.Lo // calculate the minimum lock order fee minFee := params.Fee.SendFee * params.Validator.LockOrderFeeMultiplier // ensure duplicate actions are skipped - deDupeLockOrders := lib.NewDeDuplicator[uint64]() + deDupeLockOrders := lib.NewDeDuplicator[string]() // for each transaction in the block for _, tx := range b.Transactions { // skip over any that doesn't have the minimum fee or isn't the correct type @@ -197,7 +199,7 @@ func (s *StateMachine) ParseLockOrders(b *lib.BlockResult) (lockOrders []*lib.Lo // parse the transaction for embedded 'lock orders' if lockOrder, ok := s.ParseLockOrder(tx.Transaction, params.Validator.BuyDeadlineBlocks); ok { // if not found (non-duplicate) - if found := deDupeLockOrders.Found(lockOrder.OrderId); !found { + if found := deDupeLockOrders.Found(lib.BytesToString(lockOrder.OrderId)); !found { // add to the 'lock orders' list lockOrders = append(lockOrders, lockOrder) } @@ -207,74 +209,40 @@ func (s *StateMachine) ParseLockOrders(b *lib.BlockResult) (lockOrders []*lib.Lo return } -// CreateOrder() adds an order to the order book for a committee in the state db -func (s *StateMachine) CreateOrder(order *lib.SellOrder, chainId uint64) (orderId uint64, err lib.ErrorI) { - // get the order book for a specific chainId from state - orderBook, err := s.GetOrderBook(chainId) - if err != nil { - return - } - // add the new sell order to the order book - orderId = orderBook.AddOrder(order) - // set the order book back in state - err = s.SetOrderBook(orderBook) - // exit - return -} - -// EditOrder() updates an existing order in the order book for a committee in the state db -func (s *StateMachine) EditOrder(order *lib.SellOrder, chainId uint64) (err lib.ErrorI) { - // get the order book for a specific chainId from state - orderBook, err := s.GetOrderBook(chainId) - if err != nil { - return - } - // update the order at a specific id - if err = orderBook.UpdateOrder(int(order.Id), order); err != nil { - return - } - // set the order book back in state - err = s.SetOrderBook(orderBook) - // exit - return -} - // LockOrder() adds a recipient and a deadline height to an existing order and saves it to the state -func (s *StateMachine) LockOrder(lockOrder *lib.LockOrder, chainId uint64) (err lib.ErrorI) { - // get the order book for a specific chainId from state - orderBook, err := s.GetOrderBook(chainId) +func (s *StateMachine) LockOrder(lock *lib.LockOrder, chainId uint64) (err lib.ErrorI) { + // get the order from state + order, err := s.GetOrder(lock.OrderId, chainId) if err != nil { return } - // 'reserve' the order in the book - if err = orderBook.LockOrder(int(lockOrder.OrderId), lockOrder.BuyerReceiveAddress, lockOrder.BuyerSendAddress, lockOrder.BuyerChainDeadline); err != nil { - return + // if the buyer's receive address isn't nil + if order.BuyerReceiveAddress != nil { + return lib.ErrOrderLocked() } + // set the buyer's receive, send, and deadline height in the order + order.BuyerReceiveAddress = lock.BuyerReceiveAddress + order.BuyerSendAddress = lock.BuyerSendAddress + order.BuyerChainDeadline = lock.BuyerChainDeadline // set the order book back in state - err = s.SetOrderBook(orderBook) - // exit - return + return s.SetOrder(order, chainId) } // ResetOrder() removes the recipient and deadline height from an existing order and saves it to the state -func (s *StateMachine) ResetOrder(orderId, chainId uint64) (err lib.ErrorI) { - // get the order book for a specific chainId from state - orderBook, err := s.GetOrderBook(chainId) +func (s *StateMachine) ResetOrder(orderId []byte, chainId uint64) (err lib.ErrorI) { + // get the order from state + order, err := s.GetOrder(orderId, chainId) if err != nil { return } - // reset the order in the book - if err = orderBook.ResetOrder(int(orderId)); err != nil { - return - } - // set the order book back in state - err = s.SetOrderBook(orderBook) - // exit - return + // reset the buyer's receive, send, and deadline height in the order + order.BuyerReceiveAddress, order.BuyerSendAddress, order.BuyerChainDeadline = nil, nil, 0 + // set the order back in state + return s.SetOrder(order, chainId) } // CloseOrder() sends the tokens from escrow to the 'buyer address' and deletes the order -func (s *StateMachine) CloseOrder(orderId, chainId uint64) (err lib.ErrorI) { +func (s *StateMachine) CloseOrder(orderId []byte, chainId uint64) (err lib.ErrorI) { // the order is 'closed' and the tokens are moved from escrow to the buyer order, err := s.GetOrder(orderId, chainId) if err != nil { @@ -296,32 +264,31 @@ func (s *StateMachine) CloseOrder(orderId, chainId uint64) (err lib.ErrorI) { return s.DeleteOrder(orderId, chainId) } -// DeleteOrder() deletes an existing order in the order book for a committee in the state db -func (s *StateMachine) DeleteOrder(orderId, chainId uint64) (err lib.ErrorI) { - // get the order book for a specific chainId from state - orderBook, err := s.GetOrderBook(chainId) +// SetOrder() sets the sell order in state +func (s *StateMachine) SetOrder(order *lib.SellOrder, chainId uint64) (err lib.ErrorI) { + // convert the order into proto bytes + protoBytes, err := s.marshalOrder(order) if err != nil { return } - // update the order with a 'nil' value (delete) - if err = orderBook.UpdateOrder(int(orderId), nil); err != nil { - return - } - // set the order book back in state - err = s.SetOrderBook(orderBook) - // exit - return + // set the order book in state + return s.Set(KeyForOrder(chainId, order.Id), protoBytes) +} + +// DeleteOrder() deletes an existing order in the order book for a committee in the state db +func (s *StateMachine) DeleteOrder(orderId []byte, chainId uint64) (err lib.ErrorI) { + return s.Delete(KeyForOrder(chainId, orderId)) } -// GetOrder() sets the order book for a committee in the state db -func (s *StateMachine) GetOrder(orderId uint64, chainId uint64) (order *lib.SellOrder, err lib.ErrorI) { - // get the order book for a specific chainId from state - orderBook, err := s.GetOrderBook(chainId) +// GetOrder() gets the sell order from state +func (s *StateMachine) GetOrder(orderId []byte, chainId uint64) (order *lib.SellOrder, err lib.ErrorI) { + // get the order proto bytes from the state + protoBytes, err := s.Get(KeyForOrder(chainId, orderId)) if err != nil { return } - // get the specific order from the book based on its ID - return orderBook.GetOrder(int(orderId)) + // convert the proto bytes into an order object + return s.unmarshalOrder(protoBytes) } // SetOrderBook() sets the order book for a committee in the state db @@ -332,7 +299,7 @@ func (s *StateMachine) SetOrderBook(b *lib.OrderBook) lib.ErrorI { return err } // set the order book in the store - return s.store.Set(KeyForOrderBook(b.ChainId), orderBookBz) + return s.store.Set(OrderBookPrefix(b.ChainId), orderBookBz) } // SetOrderBooks() sets a series of OrderBooks in the state db @@ -343,25 +310,22 @@ func (s *StateMachine) SetOrderBooks(list *lib.OrderBooks, supply *Supply) lib.E } // for each book in the order books list for _, book := range list.OrderBooks { - // convert the order book into bytes - orderBookBz, err := lib.Marshal(book) - if err != nil { - return err - } - // get the state 'key' for the order book - key := KeyForOrderBook(book.ChainId) - // write the order book for the committee to state under the 'key' - if err = s.store.Set(key, orderBookBz); err != nil { - return err + // ensure non nil book + if book == nil { + continue } - // properly mint to the supply pool + // for each order in the book for _, order := range book.Orders { + // set the order in state + if err := s.SetOrder(order, book.ChainId); err != nil { + return err + } // update the 'supply' tracker supply.Total += order.AmountForSale // calculate the escrow pool id for a specific chainId escrowPoolId := book.ChainId + uint64(EscrowPoolAddend) // add to the 'escrow' pool for the specific id - if err = s.PoolAdd(escrowPoolId, order.AmountForSale); err != nil { + if err := s.PoolAdd(escrowPoolId, order.AmountForSale); err != nil { return err } } @@ -376,14 +340,24 @@ func (s *StateMachine) GetOrderBook(chainId uint64) (b *lib.OrderBook, err lib.E b = new(lib.OrderBook) // update the orders and chainId of the newly created object ref b.Orders, b.ChainId = make([]*lib.SellOrder, 0), chainId - // get order book bytes from the state using the order book key for a specific chainId - bz, err := s.Get(KeyForOrderBook(chainId)) + // iterate through the order book prefix + it, err := s.Iterator(OrderBookPrefix(chainId)) if err != nil { return } - // convert order book bytes into the order book variable - err = lib.Unmarshal(bz, b) - // exit + defer it.Close() + // for each order under this prefix + for ; it.Valid(); it.Next() { + // get the order from the iterator value bytes + order, e := s.unmarshalOrder(it.Value()) + if e != nil { + // shouldn't happen + s.log.Error(e.Error()) + // defensive + continue + } + b.Orders = append(b.Orders, order) + } return } @@ -392,10 +366,12 @@ func (s *StateMachine) GetOrderBooks() (b *lib.OrderBooks, err lib.ErrorI) { // get the order books from the state b = new(lib.OrderBooks) // create an iterator over the OrderBookPrefix - it, err := s.Iterator(OrderBookPrefix()) + it, err := s.Iterator(lib.JoinLenPrefix(orderBookPrefix)) if err != nil { return } + // deduplicate committees + deDupe := lib.NewDeDuplicator[uint64]() // memory cleanup the iterator defer it.Close() // for each item under the OrderBookPrefix @@ -405,6 +381,10 @@ func (s *StateMachine) GetOrderBooks() (b *lib.OrderBooks, err lib.ErrorI) { if e != nil { return nil, e } + // skip duplicates + if deDupe.Found(id) { + continue + } // get the specific order book for the chainId book, e := s.GetOrderBook(id) if e != nil { @@ -413,6 +393,31 @@ func (s *StateMachine) GetOrderBooks() (b *lib.OrderBooks, err lib.ErrorI) { // add the book to the list b.OrderBooks = append(b.OrderBooks, book) } + // sort by chain id + sort.Slice(b.OrderBooks, func(i, j int) bool { + return b.OrderBooks[i].ChainId < b.OrderBooks[j].ChainId + }) // exit return } + +// marshalOrder() converts the Validator object to bytes +func (s *StateMachine) marshalOrder(order *lib.SellOrder) ([]byte, lib.ErrorI) { + // convert the object ref into bytes + return lib.Marshal(order) +} + +// unmarshalOrder() converts bytes into a SellOrder object +func (s *StateMachine) unmarshalOrder(protoBytes []byte) (*lib.SellOrder, lib.ErrorI) { + if protoBytes == nil { + return nil, lib.ErrOrderNotFound() + } + // create a new SellOrder object reference to ensure a non-nil result + order := new(lib.SellOrder) + // populate the object reference with validator bytes + if err := lib.Unmarshal(protoBytes, order); err != nil { + return nil, err + } + // return the object ref + return order, nil +} diff --git a/fsm/swap_test.go b/fsm/swap_test.go index e0f2975457..bfa45fb35e 100644 --- a/fsm/swap_test.go +++ b/fsm/swap_test.go @@ -1,6 +1,8 @@ package fsm import ( + "bytes" + "fmt" "github.com/canopy-network/canopy/lib" "github.com/canopy-network/canopy/lib/crypto" "github.com/stretchr/testify/require" @@ -22,6 +24,7 @@ func TestHandleCommitteeSwaps(t *testing.T) { detail: "the lock order cannot be claimed as its already reserved", preset: []*lib.SellOrder{ { + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -32,7 +35,7 @@ func TestHandleCommitteeSwaps(t *testing.T) { orders: &lib.Orders{ LockOrders: []*lib.LockOrder{ { - OrderId: 0, + OrderId: newTestOrderId(t, 0), BuyerReceiveAddress: newTestAddressBytes(t, 1), BuyerChainDeadline: 100, }, @@ -53,7 +56,7 @@ func TestHandleCommitteeSwaps(t *testing.T) { }, }, orders: &lib.Orders{ - ResetOrders: []uint64{1}, + ResetOrders: [][]byte{newTestOrderId(t, 1)}, }, notFound: true, }, @@ -62,6 +65,7 @@ func TestHandleCommitteeSwaps(t *testing.T) { detail: "can't close an order that doesn't have a buyer", preset: []*lib.SellOrder{ { + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -69,7 +73,7 @@ func TestHandleCommitteeSwaps(t *testing.T) { }, }, orders: &lib.Orders{ - CloseOrders: []uint64{0}, + CloseOrders: [][]byte{newTestOrderId(t, 0)}, }, noBuyer: true, }, @@ -78,12 +82,14 @@ func TestHandleCommitteeSwaps(t *testing.T) { detail: "test buy, reset, and sell without error", preset: []*lib.SellOrder{ { + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, SellersSendAddress: newTestAddressBytes(t), }, { + Id: newTestOrderId(t, 1), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -91,6 +97,7 @@ func TestHandleCommitteeSwaps(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, { + Id: newTestOrderId(t, 2), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -101,13 +108,13 @@ func TestHandleCommitteeSwaps(t *testing.T) { orders: &lib.Orders{ LockOrders: []*lib.LockOrder{ { - OrderId: 0, + OrderId: newTestOrderId(t, 0), BuyerReceiveAddress: newTestAddressBytes(t, 1), BuyerChainDeadline: 100, }, }, - ResetOrders: []uint64{1}, - CloseOrders: []uint64{2}, + ResetOrders: [][]byte{newTestOrderId(t, 1)}, + CloseOrders: [][]byte{newTestOrderId(t, 2)}, }, }, } @@ -118,7 +125,7 @@ func TestHandleCommitteeSwaps(t *testing.T) { sm := newTestStateMachine(t) // preset the sell orders for _, preset := range test.preset { - _, err := sm.CreateOrder(preset, lib.CanopyChainId) + err := sm.SetOrder(preset, lib.CanopyChainId) require.NoError(t, err) // simulate the escrow supply escrowPoolBalance += preset.AmountForSale @@ -157,8 +164,6 @@ func TestHandleCommitteeSwaps(t *testing.T) { var balanceRemovedFromPool uint64 // validate the close orders for _, closeOrder := range test.orders.CloseOrders { - // define convenience variable for order - order := test.preset[closeOrder] // validate the deletion of the order _, e := sm.GetOrder(closeOrder, lib.CanopyChainId) // if order no buyer to close @@ -166,11 +171,15 @@ func TestHandleCommitteeSwaps(t *testing.T) { require.NoError(t, e) } else { require.ErrorContains(t, e, "not found") - // validate the addition of funds to the buyer - accountBalance, e := sm.GetAccountBalance(crypto.NewAddress(order.BuyerReceiveAddress)) - require.NoError(t, e) - require.Equal(t, order.AmountForSale, accountBalance) - balanceRemovedFromPool += order.AmountForSale + for _, order := range test.preset { + if bytes.Equal(order.Id, closeOrder) { + // validate the addition of funds to the buyer + accountBalance, e := sm.GetAccountBalance(crypto.NewAddress(order.BuyerReceiveAddress)) + require.NoError(t, e) + require.Equal(t, order.AmountForSale, accountBalance) + balanceRemovedFromPool += order.AmountForSale + } + } } } // validate the removal of funds from the escrow pool @@ -181,7 +190,7 @@ func TestHandleCommitteeSwaps(t *testing.T) { } } -func TestCreateOrder(t *testing.T) { +func TestSetOrder(t *testing.T) { tests := []struct { name string detail string @@ -192,7 +201,7 @@ func TestCreateOrder(t *testing.T) { detail: "create sell order", expected: []*lib.SellOrder{ { - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -206,7 +215,7 @@ func TestCreateOrder(t *testing.T) { detail: "create sell order for another committee", expected: []*lib.SellOrder{ { - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -214,7 +223,7 @@ func TestCreateOrder(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, { - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId + 1, AmountForSale: 100, RequestedAmount: 100, @@ -228,7 +237,7 @@ func TestCreateOrder(t *testing.T) { detail: "test the id creation order", expected: []*lib.SellOrder{ { - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -236,7 +245,7 @@ func TestCreateOrder(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, { - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId + 1, AmountForSale: 100, RequestedAmount: 100, @@ -244,7 +253,7 @@ func TestCreateOrder(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, { - Id: 1, // only used for validation + Id: newTestOrderId(t, 1), // only used for validation Committee: lib.CanopyChainId + 1, AmountForSale: 100, RequestedAmount: 100, @@ -252,7 +261,7 @@ func TestCreateOrder(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, { - Id: 1, // only used for validation + Id: newTestOrderId(t, 1), // only used for validation Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -268,7 +277,7 @@ func TestCreateOrder(t *testing.T) { sm := newTestStateMachine(t) for _, expected := range test.expected { // execute the function call - _, err := sm.CreateOrder(expected, expected.Committee) + err := sm.SetOrder(expected, expected.Committee) require.NoError(t, err) // get the order got, err := sm.GetOrder(expected.Id, expected.Committee) @@ -288,19 +297,6 @@ func TestEditOrder(t *testing.T) { expected *lib.SellOrder error string }{ - { - name: "order not found", - detail: "order not preset so no order id is found", - expected: &lib.SellOrder{ - Id: 0, - Committee: lib.CanopyChainId, - AmountForSale: 101, - RequestedAmount: 100, - SellerReceiveAddress: newTestAddressBytes(t), - SellersSendAddress: newTestAddressBytes(t), - }, - error: "not found", - }, { name: "update amount", detail: "update the amount for sale without error", @@ -326,11 +322,11 @@ func TestEditOrder(t *testing.T) { sm := newTestStateMachine(t) // preset the order if test.preset != nil { - _, err := sm.CreateOrder(test.preset, test.preset.Committee) + err := sm.SetOrder(test.preset, test.preset.Committee) require.NoError(t, err) } // execute the function call - err := sm.EditOrder(test.expected, lib.CanopyChainId) + err := sm.SetOrder(test.expected, lib.CanopyChainId) // validate the expected error require.Equal(t, test.error != "", err != nil, err) if err != nil { @@ -359,7 +355,7 @@ func TestLockOrder(t *testing.T) { detail: "the lock order cannot be found", order: &lib.LockOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), BuyerReceiveAddress: newTestAddressBytes(t, 1), BuyerChainDeadline: 100, }, @@ -369,6 +365,7 @@ func TestLockOrder(t *testing.T) { name: "lock order locked", detail: "the lock order cannot be claimed as its already reserved", preset: &lib.SellOrder{ + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -377,7 +374,7 @@ func TestLockOrder(t *testing.T) { }, order: &lib.LockOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), BuyerReceiveAddress: newTestAddressBytes(t, 1), BuyerChainDeadline: 100, }, @@ -387,13 +384,14 @@ func TestLockOrder(t *testing.T) { name: "lock order", detail: "successful lock order without error", preset: &lib.SellOrder{ + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, SellersSendAddress: newTestAddressBytes(t), }, order: &lib.LockOrder{ - OrderId: 0, + OrderId: newTestOrderId(t, 0), BuyerReceiveAddress: newTestAddressBytes(t, 1), BuyerChainDeadline: 100, }, @@ -405,7 +403,7 @@ func TestLockOrder(t *testing.T) { sm := newTestStateMachine(t) // preset the order if test.preset != nil { - _, err := sm.CreateOrder(test.preset, lib.CanopyChainId) + err := sm.SetOrder(test.preset, lib.CanopyChainId) require.NoError(t, err) } // execute the function call @@ -431,26 +429,27 @@ func TestResetOrder(t *testing.T) { name string detail string preset *lib.SellOrder - order uint64 + order []byte error string }{ { name: "reset order not found", detail: "the buy reset cannot be found", - order: 0, + order: newTestOrderId(t, 0), error: "not found", }, { name: "reset order", detail: "successful reset order without error", preset: &lib.SellOrder{ + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, BuyerReceiveAddress: newTestAddressBytes(t), SellersSendAddress: newTestAddressBytes(t), }, - order: 0, + order: newTestOrderId(t, 0), }, } for _, test := range tests { @@ -459,7 +458,7 @@ func TestResetOrder(t *testing.T) { sm := newTestStateMachine(t) // preset the order if test.preset != nil { - _, err := sm.CreateOrder(test.preset, lib.CanopyChainId) + err := sm.SetOrder(test.preset, lib.CanopyChainId) require.NoError(t, err) } // execute the function call @@ -485,32 +484,34 @@ func TestCloseOrder(t *testing.T) { name string detail string preset *lib.SellOrder - order uint64 + order []byte error string }{ { name: "close order not already accepted", detail: "there's no existing buyer for the close order", preset: &lib.SellOrder{ + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, SellersSendAddress: newTestAddressBytes(t), }, - order: 0, + order: newTestOrderId(t, 0), error: "lock order invalid", }, { name: "close order", detail: "successful reset order without error", preset: &lib.SellOrder{ + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, BuyerReceiveAddress: newTestAddressBytes(t), SellersSendAddress: newTestAddressBytes(t), }, - order: 0, + order: newTestOrderId(t, 0), }, } for _, test := range tests { @@ -519,7 +520,7 @@ func TestCloseOrder(t *testing.T) { sm := newTestStateMachine(t) // preset the order if test.preset != nil { - _, err := sm.CreateOrder(test.preset, lib.CanopyChainId) + err := sm.SetOrder(test.preset, lib.CanopyChainId) require.NoError(t, err) require.NoError(t, sm.PoolAdd(lib.CanopyChainId+EscrowPoolAddend, test.preset.AmountForSale)) } @@ -556,24 +557,12 @@ func TestDeleteOrder(t *testing.T) { toDelete []*lib.SellOrder error string }{ - { - name: "order not found", - detail: "order not found because it wasn't preset", - preset: []*lib.SellOrder{}, - toDelete: []*lib.SellOrder{ - { - Id: 0, - Committee: 0, - }, - }, - error: "not found", - }, { name: "delete sell order", detail: "delete sell order", preset: []*lib.SellOrder{ { - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -587,7 +576,7 @@ func TestDeleteOrder(t *testing.T) { detail: "delete sell order for another committee", preset: []*lib.SellOrder{ { - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId, AmountForSale: 100, RequestedAmount: 100, @@ -595,7 +584,7 @@ func TestDeleteOrder(t *testing.T) { SellersSendAddress: newTestAddressBytes(t), }, { - Id: 0, + Id: newTestOrderId(t, 0), Committee: lib.CanopyChainId + 1, AmountForSale: 100, RequestedAmount: 100, @@ -611,7 +600,7 @@ func TestDeleteOrder(t *testing.T) { sm := newTestStateMachine(t) for _, expected := range test.preset { // execute the function call - _, err := sm.CreateOrder(expected, expected.Committee) + err := sm.SetOrder(expected, expected.Committee) require.NoError(t, err) // get the order got, err := sm.GetOrder(expected.Id, expected.Committee) @@ -652,16 +641,16 @@ func TestGetSetOrderBooks(t *testing.T) { ChainId: 0, Orders: []*lib.SellOrder{ { - Id: 1, - Committee: 2, + Id: newTestOrderId(t, 0), + Committee: 0, AmountForSale: 100, RequestedAmount: 100, SellerReceiveAddress: newTestAddressBytes(t, 1), SellersSendAddress: newTestAddressBytes(t), }, { - Id: 0, - Committee: 1, + Id: newTestOrderId(t, 1), + Committee: 0, AmountForSale: 100, RequestedAmount: 100, SellerReceiveAddress: newTestAddressBytes(t, 1), @@ -673,15 +662,15 @@ func TestGetSetOrderBooks(t *testing.T) { ChainId: 1, Orders: []*lib.SellOrder{ { - Id: 1, - Committee: 2, + Id: newTestOrderId(t, 2), + Committee: 1, AmountForSale: 100, RequestedAmount: 100, SellerReceiveAddress: newTestAddressBytes(t, 1), SellersSendAddress: newTestAddressBytes(t), }, { - Id: 0, + Id: newTestOrderId(t, 3), Committee: 1, AmountForSale: 100, RequestedAmount: 100, @@ -720,3 +709,7 @@ func TestGetSetOrderBooks(t *testing.T) { }) } } + +func newTestOrderId(_ *testing.T, variant int) []byte { + return []byte(fmt.Sprintf("%d", variant)) +} diff --git a/fsm/transaction.go b/fsm/transaction.go index 6f2c1583a7..3dd720be26 100644 --- a/fsm/transaction.go +++ b/fsm/transaction.go @@ -1,6 +1,7 @@ package fsm import ( + "fmt" "github.com/canopy-network/canopy/lib" "github.com/canopy-network/canopy/lib/crypto" "google.golang.org/protobuf/types/known/anypb" @@ -112,21 +113,28 @@ func (s *StateMachine) CheckSignature(msg lib.MessageI, tx *lib.Transaction, txH for _, authorized := range authorizedSigners { // if the address that signed the transaction matches one of the authorized signers if address.Equals(crypto.NewAddressFromBytes(authorized)) { - // populate the signer field for stake - if stake, ok := msg.(*MessageStake); ok { - stake.Signer = authorized - } - // populate the signer field for edit-stake - if editStake, ok := msg.(*MessageEditStake); ok { - editStake.Signer = authorized - } - // populate the proposal hash for change parameter - if changeParam, ok := msg.(*MessageChangeParameter); ok { - changeParam.ProposalHash = txHash - } - // populate the proposal hash for dao transfer - if daoTransfer, ok := msg.(*MessageDAOTransfer); ok { - daoTransfer.ProposalHash = txHash + // handle special fields for transactions + switch x := msg.(type) { + case *MessageStake: + // populate the signer field for stake + x.Signer = authorized + case *MessageEditStake: + // populate the signer field for edit-stake + x.Signer = authorized + case *MessageChangeParameter: + // populate the proposal hash for change parameter + x.ProposalHash = txHash + case *MessageDAOTransfer: + // populate the proposal hash for dao transfer + x.ProposalHash = txHash + case *MessageCreateOrder: + // populate the order id for the create order + hash, err := lib.StringToBytes(txHash) + if err != nil { + return nil, err + } + fmt.Println("SETTING ID", lib.BytesToString(hash[:20])) + x.Hash = hash[:20] // first 20 bytes of the transaction hash } // return the signer address return address, nil @@ -351,9 +359,13 @@ func NewCreateOrderTx(from crypto.PrivateKeyI, sellAmount, requestAmount, commit } // NewEditOrderTx() creates an EditOrderTransaction object in the interface form of TransactionI -func NewEditOrderTx(from crypto.PrivateKeyI, orderId, sellAmount, requestAmount, committeeId uint64, receiveAddress []byte, networkId, chainId, fee, height uint64, memo string) (lib.TransactionI, lib.ErrorI) { +func NewEditOrderTx(from crypto.PrivateKeyI, orderId string, sellAmount, requestAmount, committeeId uint64, receiveAddress []byte, networkId, chainId, fee, height uint64, memo string) (lib.TransactionI, lib.ErrorI) { + oId, err := lib.StringToBytes(orderId) + if err != nil { + return nil, err + } return NewTransaction(from, &MessageEditOrder{ - OrderId: orderId, + OrderId: oId, ChainId: committeeId, AmountForSale: sellAmount, RequestedAmount: requestAmount, @@ -362,9 +374,13 @@ func NewEditOrderTx(from crypto.PrivateKeyI, orderId, sellAmount, requestAmount, } // NewDeleteOrderTx() creates an DeleteOrderTransaction object in the interface form of TransactionI -func NewDeleteOrderTx(from crypto.PrivateKeyI, orderId, committeeId uint64, networkId, chainId, fee, height uint64, memo string) (lib.TransactionI, lib.ErrorI) { +func NewDeleteOrderTx(from crypto.PrivateKeyI, orderId string, committeeId uint64, networkId, chainId, fee, height uint64, memo string) (lib.TransactionI, lib.ErrorI) { + oId, err := lib.StringToBytes(orderId) + if err != nil { + return nil, err + } return NewTransaction(from, &MessageDeleteOrder{ - OrderId: orderId, + OrderId: oId, ChainId: committeeId, }, networkId, chainId, fee, height, memo) } diff --git a/lib/.proto/certificate.proto b/lib/.proto/certificate.proto index bc801b00f1..47055b0aa4 100644 --- a/lib/.proto/certificate.proto +++ b/lib/.proto/certificate.proto @@ -84,16 +84,16 @@ message Orders { repeated LockOrder lock_orders = 1; // @gotags: json:"lockOrders" // reset_orders: a list of orders where no funds were sent before the deadline, // signaling to Canopy to 'un-claim' the order - repeated uint64 reset_orders = 2; // @gotags: json:"resetOrders" + repeated bytes reset_orders = 2; // @gotags: json:"resetOrders" // close_orders: a list of orders where funds were sent, // signaling Canopy to transfer escrowed tokens to the buyer's Canopy address - repeated uint64 close_orders = 3; // @gotags: json:"closeOrders" + repeated bytes close_orders = 3; // @gotags: json:"closeOrders" } // LockOrder is a buyer expressing an intent to purchase an order, often referred to as 'claiming' the order message LockOrder { // order_id: is the number id that is unique to this committee to identify the order - uint64 order_id = 1; // @gotags: json:"orderID" + bytes order_id = 1; // @gotags: json:"orderID" // buyer_receive_address: the Canopy address where the tokens may be received bytes buyer_receive_address = 2; // @gotags: json:"buyerReceiveAddress" // buyer_send_address: the 'counter asset' address where the tokens will be sent from @@ -105,10 +105,10 @@ message LockOrder { // CloseOrder is a buyer completing the purchase of an order, often referred to as 'buying' the order message CloseOrder { - // close_order: is the tag to represent the intent to embed a close order - bool close_order = 1; //@gotags: json:"closeOrder" // order_id: is the number id that is unique to this committee to identify the order - uint64 order_id = 2; // @gotags: json:"orderID" + bytes order_id = 1; // @gotags: json:"orderID" + // close_order: is the tag to represent the intent to embed a close order + bool close_order = 2; //@gotags: json:"closeOrder" } // Checkpoint is 3rd party chain information that allows Canopy to provide Checkpointing-as-a-Service for the 3rd party diff --git a/lib/.proto/message.proto b/lib/.proto/message.proto index 5219c51701..dc28c6fd34 100644 --- a/lib/.proto/message.proto +++ b/lib/.proto/message.proto @@ -180,6 +180,8 @@ message MessageCreateOrder { bytes SellerReceiveAddress = 4; // @gotags: json:"sellerReceiveAddress" // sellers_send_address: the Canopy address the seller is selling and signing from bytes SellersSendAddress = 5; // @gotags: json:"sellersSendAddress" + // hash: auto-populated by the state machine to assign the unique bytes to the order + bytes hash = 6; } // MessageEditOrder modifies an un-claimed token swap 'sell order', token amount may be increased or decreased as well @@ -188,7 +190,7 @@ message MessageCreateOrder { message MessageEditOrder { // order_id: is the number id that is unique to this committee to identify the order // not modifiable, used for order identification only - uint64 OrderId = 1; // @gotags: json:"orderID" + bytes OrderId = 1; // @gotags: json:"orderID" // chain_id: the id of the committee that is responsible for the 'counter asset' the uCNPY will swapped for // not modifiable, used for order identification only uint64 ChainId = 2; // @gotags: json:"chainID" @@ -205,7 +207,7 @@ message MessageEditOrder { // If an order is already 'claimed' or 'bought', the order may not be deleted message MessageDeleteOrder { // order_id: is the number id that is unique to this committee to identify the order - uint64 OrderId = 1; // @gotags: json:"orderID" + bytes OrderId = 1; // @gotags: json:"orderID" // chain_id: the id of the committee that is responsible for the 'counter asset' the uCNPY will swapped for uint64 ChainId = 2; // @gotags: json:"chainID" } \ No newline at end of file diff --git a/lib/.proto/swap.proto b/lib/.proto/swap.proto index 67078c0b25..9cd7c9699b 100644 --- a/lib/.proto/swap.proto +++ b/lib/.proto/swap.proto @@ -43,7 +43,7 @@ option go_package = "github.com/canopy-network/canopy/lib"; // then populated by an 'intent to buy', and finally closed when the committee witnesses the transfer of funds. message SellOrder { // id: the unique identifier of the order - uint64 Id = 1; // @gotags: json:"id" + bytes Id = 1; // @gotags: json:"id" // committee: the id of the committee that is in-charge of escrow for the swap uint64 Committee = 2; // @gotags: json:"committee" // amount_for_sale: amount of CNPY for sale diff --git a/lib/certificate.go b/lib/certificate.go index d1ce61e3de..0d24ca723b 100644 --- a/lib/certificate.go +++ b/lib/certificate.go @@ -629,21 +629,21 @@ func (x *Orders) CheckBasic() (err ErrorI) { } } // ensure no duplicates in the resets - deDuplicator := NewDeDuplicator[uint64]() + deDuplicator := NewDeDuplicator[string]() // for each reset order for _, reset := range x.ResetOrders { // if a duplicate found - if deDuplicator.Found(reset) { + if deDuplicator.Found(BytesToString(reset)) { // exit with the duplicate reset order return ErrDuplicateResetOrder() } } // ensure no duplicates in the closes - deDuplicator = NewDeDuplicator[uint64]() + deDuplicator = NewDeDuplicator[string]() // for each close order for _, reset := range x.CloseOrders { // if a duplicate found - if deDuplicator.Found(reset) { + if deDuplicator.Found(BytesToString(reset)) { // exit with the duplicate close order return ErrDuplicateCloseOrder() } @@ -665,12 +665,12 @@ func (x *Orders) Equals(y *Orders) bool { return false } // if the close orders lists are not equal - if !slices.Equal(x.CloseOrders, y.CloseOrders) { + if !EqualByteSlices(x.CloseOrders, y.CloseOrders) { // exit with 'unequal' return false } // if the reset orders lists are not equal - if !slices.Equal(x.ResetOrders, y.ResetOrders) { + if !EqualByteSlices(x.ResetOrders, y.ResetOrders) { // exit with 'unequal' return false } @@ -714,7 +714,7 @@ func (x *LockOrder) Equals(y *LockOrder) bool { return false } // if the order ids are not the same - if x.OrderId != y.OrderId { + if !bytes.Equal(x.OrderId, y.OrderId) { // exit with 'unequal' return false } @@ -725,14 +725,14 @@ func (x *LockOrder) Equals(y *LockOrder) bool { // lockOrderJSON implements the json.Marshaller & json.Unmarshaler interfaces for LockOrder type lockOrderJSON struct { // order_id: is the number id that is unique to this committee to identify the order - OrderId uint64 `json:"order_id,omitempty"` + OrderId HexBytes `json:"orderId,omitempty"` // buyers_send_address: the Canopy address where the tokens may be received - BuyersSendAddress HexBytes `json:"buyers_send_address,omitempty"` + BuyersSendAddress HexBytes `json:"buyerSendAddress,omitempty"` // buyer_receive_address: the Canopy address where the tokens may be received - BuyerReceiveAddress HexBytes `json:"buyer_receive_address,omitempty"` + BuyerReceiveAddress HexBytes `json:"buyerReceiveAddress,omitempty"` // buyer_chain_deadline: the 'counter asset' chain height at which the buyer must send the 'counter asset' by // or the 'intent to buy' will be voided - BuyerChainDeadline uint64 `json:"buyer_chain_deadline,omitempty"` + BuyerChainDeadline uint64 `json:"buyerChainDeadline,omitempty"` } // MarshalJSON() implements the json.Marshaller interface for LockOrder @@ -766,6 +766,41 @@ func (x *LockOrder) UnmarshalJSON(jsonBytes []byte) (err error) { return } +// closeOrderJSON implements the json.Marshaller & json.Unmarshaler interfaces for LockOrder +type closeOrderJSON struct { + // order_id: is the number id that is unique to this committee to identify the order + OrderId HexBytes `json:"orderId,omitempty"` + // close_order: is the tag to represent the intent to embed a close order + CloseOrder bool `json:"closeOrder,omitempty"` +} + +// MarshalJSON() implements the json.Marshaller interface for CloseOrder +func (x CloseOrder) MarshalJSON() ([]byte, error) { + // convert the lock order to json bytes using the json object + return json.Marshal(&closeOrderJSON{ + OrderId: x.OrderId, + CloseOrder: x.CloseOrder, + }) +} + +// UnmarshalJSON() implements the json.Unmarshaler interface for CloseOrder +func (x *CloseOrder) UnmarshalJSON(jsonBytes []byte) (err error) { + // create a new json object reference to ensure a non nil result + j := new(closeOrderJSON) + // populate the json object ref with json bytes + if err = json.Unmarshal(jsonBytes, j); err != nil { + // exit with error + return + } + // populate the underlying structure using the json object + *x = CloseOrder{ + OrderId: j.OrderId, + CloseOrder: j.CloseOrder, + } + // exit + return +} + // CHECKPOINT CODE BELOW // CheckBasic() performs stateless validation on a Checkpoint object diff --git a/lib/certificate.pb.go b/lib/certificate.pb.go index 7ad2536940..794148c982 100644 --- a/lib/certificate.pb.go +++ b/lib/certificate.pb.go @@ -353,10 +353,10 @@ type Orders struct { LockOrders []*LockOrder `protobuf:"bytes,1,rep,name=lock_orders,json=lockOrders,proto3" json:"lockOrders"` // @gotags: json:"lockOrders" // reset_orders: a list of orders where no funds were sent before the deadline, // signaling to Canopy to 'un-claim' the order - ResetOrders []uint64 `protobuf:"varint,2,rep,packed,name=reset_orders,json=resetOrders,proto3" json:"resetOrders"` // @gotags: json:"resetOrders" + ResetOrders [][]byte `protobuf:"bytes,2,rep,name=reset_orders,json=resetOrders,proto3" json:"resetOrders"` // @gotags: json:"resetOrders" // close_orders: a list of orders where funds were sent, // signaling Canopy to transfer escrowed tokens to the buyer's Canopy address - CloseOrders []uint64 `protobuf:"varint,3,rep,packed,name=close_orders,json=closeOrders,proto3" json:"closeOrders"` // @gotags: json:"closeOrders" + CloseOrders [][]byte `protobuf:"bytes,3,rep,name=close_orders,json=closeOrders,proto3" json:"closeOrders"` // @gotags: json:"closeOrders" } func (x *Orders) Reset() { @@ -398,14 +398,14 @@ func (x *Orders) GetLockOrders() []*LockOrder { return nil } -func (x *Orders) GetResetOrders() []uint64 { +func (x *Orders) GetResetOrders() [][]byte { if x != nil { return x.ResetOrders } return nil } -func (x *Orders) GetCloseOrders() []uint64 { +func (x *Orders) GetCloseOrders() [][]byte { if x != nil { return x.CloseOrders } @@ -419,7 +419,7 @@ type LockOrder struct { unknownFields protoimpl.UnknownFields // order_id: is the number id that is unique to this committee to identify the order - OrderId uint64 `protobuf:"varint,1,opt,name=order_id,json=orderId,proto3" json:"orderID"` // @gotags: json:"orderID" + OrderId []byte `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"orderID"` // @gotags: json:"orderID" // buyer_receive_address: the Canopy address where the tokens may be received BuyerReceiveAddress []byte `protobuf:"bytes,2,opt,name=buyer_receive_address,json=buyerReceiveAddress,proto3" json:"buyerReceiveAddress"` // @gotags: json:"buyerReceiveAddress" // buyer_send_address: the 'counter asset' address where the tokens will be sent from @@ -461,11 +461,11 @@ func (*LockOrder) Descriptor() ([]byte, []int) { return file_certificate_proto_rawDescGZIP(), []int{5} } -func (x *LockOrder) GetOrderId() uint64 { +func (x *LockOrder) GetOrderId() []byte { if x != nil { return x.OrderId } - return 0 + return nil } func (x *LockOrder) GetBuyerReceiveAddress() []byte { @@ -495,10 +495,10 @@ type CloseOrder struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // close_order: is the tag to represent the intent to embed a close order - CloseOrder bool `protobuf:"varint,1,opt,name=close_order,json=closeOrder,proto3" json:"closeOrder"` //@gotags: json:"closeOrder" // order_id: is the number id that is unique to this committee to identify the order - OrderId uint64 `protobuf:"varint,2,opt,name=order_id,json=orderId,proto3" json:"orderID"` // @gotags: json:"orderID" + OrderId []byte `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"orderID"` // @gotags: json:"orderID" + // close_order: is the tag to represent the intent to embed a close order + CloseOrder bool `protobuf:"varint,2,opt,name=close_order,json=closeOrder,proto3" json:"closeOrder"` //@gotags: json:"closeOrder" } func (x *CloseOrder) Reset() { @@ -533,18 +533,18 @@ func (*CloseOrder) Descriptor() ([]byte, []int) { return file_certificate_proto_rawDescGZIP(), []int{6} } -func (x *CloseOrder) GetCloseOrder() bool { +func (x *CloseOrder) GetOrderId() []byte { if x != nil { - return x.CloseOrder + return x.OrderId } - return false + return nil } -func (x *CloseOrder) GetOrderId() uint64 { +func (x *CloseOrder) GetCloseOrder() bool { if x != nil { - return x.OrderId + return x.CloseOrder } - return 0 + return false } // Checkpoint is 3rd party chain information that allows Canopy to provide Checkpointing-as-a-Service for the 3rd party @@ -933,12 +933,12 @@ var file_certificate_proto_rawDesc = []byte{ 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4c, 0x6f, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x0a, 0x6c, 0x6f, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74, 0x4f, + 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0b, 0x63, 0x6c, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x22, 0xba, 0x01, 0x0a, 0x09, 0x4c, 0x6f, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x62, 0x75, 0x79, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x62, 0x75, 0x79, 0x65, 0x72, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x41, 0x64, @@ -949,10 +949,10 @@ var file_certificate_proto_rawDesc = []byte{ 0x69, 0x6e, 0x5f, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x62, 0x75, 0x79, 0x65, 0x72, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x48, 0x0a, 0x0a, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x22, + 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, + 0x0a, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x43, 0x0a, 0x0a, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, diff --git a/lib/certificate_test.go b/lib/certificate_test.go index 6bb10ee8e7..7d24da2c8c 100644 --- a/lib/certificate_test.go +++ b/lib/certificate_test.go @@ -3,6 +3,7 @@ package lib import ( "bytes" "encoding/json" + "fmt" "testing" "github.com/canopy-network/canopy/lib/crypto" @@ -322,7 +323,7 @@ func TestCertificateResultsCheckBasic(t *testing.T) { Orders: &Orders{ LockOrders: []*LockOrder{ { - OrderId: 0, + OrderId: newTestOrderId(t, 0), BuyerSendAddress: nil, BuyerReceiveAddress: nil, BuyerChainDeadline: 0, @@ -346,7 +347,7 @@ func TestCertificateResultsCheckBasic(t *testing.T) { Orders: &Orders{ LockOrders: []*LockOrder{ { - OrderId: 0, + OrderId: newTestOrderId(t, 0), BuyerSendAddress: newTestAddressBytes(t), BuyerReceiveAddress: nil, BuyerChainDeadline: 0, @@ -401,13 +402,13 @@ func TestCheckpointHash(t *testing.T) { Orders: &Orders{ LockOrders: []*LockOrder{ { - OrderId: 0, + OrderId: newTestOrderId(t, 0), BuyerReceiveAddress: newTestAddressBytes(t), BuyerChainDeadline: 0, }, }, - ResetOrders: []uint64{0}, - CloseOrders: []uint64{1}, + ResetOrders: [][]byte{newTestOrderId(t, 0)}, + CloseOrders: [][]byte{newTestOrderId(t, 1)}, }, Checkpoint: &Checkpoint{ Height: 1, @@ -423,3 +424,7 @@ func TestCheckpointHash(t *testing.T) { // compare got vs expected require.Equal(t, expected, got) } + +func newTestOrderId(_ *testing.T, variant int) []byte { + return []byte(fmt.Sprintf("%d", variant)) +} diff --git a/lib/consensus.go b/lib/consensus.go index 2e09462079..0db50db6e8 100644 --- a/lib/consensus.go +++ b/lib/consensus.go @@ -100,17 +100,17 @@ func (vs *ValidatorSet) GetValidatorAndIdx(targetPublicKey []byte) (val *Consens // RootChainClient executes 'on-demand' calls to the root-chain type RCManagerI interface { - Publish(chainId uint64, info *RootChainInfo) // publish the root chain info to nested chain listeners - ChainIds() []uint64 // get the list of chain ids of the nested chain subscribers - GetHeight(rootChainId uint64) uint64 // get the height of the root chain - GetRootChainInfo(rootChainId, chainId uint64) (rootChainInfo *RootChainInfo, err ErrorI) // get root-chain info 'on-demand' - GetValidatorSet(rootChainId, height, id uint64) (ValidatorSet, ErrorI) // get the validator set for a chain id using the RPC API - GetLotteryWinner(rootChainId, height, id uint64) (p *LotteryWinner, err ErrorI) // get the delegate 'lottery winner' for a chain id - GetOrders(rootChainId, rootHeight, id uint64) (*OrderBook, ErrorI) // get the order book for a specific 'chain id' - GetOrder(rootChainId, height, orderId, chainId uint64) (*SellOrder, ErrorI) // get a specific order from the order book - IsValidDoubleSigner(rootChainId, height uint64, address string) (p *bool, err ErrorI) // check if a double signer is valid for an address for a specific 'double sign height' - GetCheckpoint(rootChainId, height, id uint64) (blockHash HexBytes, i ErrorI) // get a checkpoint at a height and chain id combination - Transaction(rootChainId uint64, tx TransactionI) (hash *string, err ErrorI) // submit a transaction to the 'root chain' + Publish(chainId uint64, info *RootChainInfo) // publish the root chain info to nested chain listeners + ChainIds() []uint64 // get the list of chain ids of the nested chain subscribers + GetHeight(rootChainId uint64) uint64 // get the height of the root chain + GetRootChainInfo(rootChainId, chainId uint64) (rootChainInfo *RootChainInfo, err ErrorI) // get root-chain info 'on-demand' + GetValidatorSet(rootChainId, height, id uint64) (ValidatorSet, ErrorI) // get the validator set for a chain id using the RPC API + GetLotteryWinner(rootChainId, height, id uint64) (p *LotteryWinner, err ErrorI) // get the delegate 'lottery winner' for a chain id + GetOrders(rootChainId, rootHeight, id uint64) (*OrderBook, ErrorI) // get the order book for a specific 'chain id' + GetOrder(rootChainId, height uint64, orderId string, chainId uint64) (*SellOrder, ErrorI) // get a specific order from the order book + IsValidDoubleSigner(rootChainId, height uint64, address string) (p *bool, err ErrorI) // check if a double signer is valid for an address for a specific 'double sign height' + GetCheckpoint(rootChainId, height, id uint64) (blockHash HexBytes, i ErrorI) // get a checkpoint at a height and chain id combination + Transaction(rootChainId uint64, tx TransactionI) (hash *string, err ErrorI) // submit a transaction to the 'root chain' } // CheckBasic() validates the basic structure and length of the AggregateSignature diff --git a/lib/error.go b/lib/error.go index 9b0b7fa8e9..1204ccfbb5 100644 --- a/lib/error.go +++ b/lib/error.go @@ -728,8 +728,8 @@ func ErrOrderLocked() ErrorI { return NewError(CodeOrderLocked, StateMachineModule, "order locked") } -func ErrOrderNotFound(id int) ErrorI { - return NewError(CodeOrderNotFound, StateMachineModule, fmt.Sprintf("order with id %d not found", id)) +func ErrOrderNotFound() ErrorI { + return NewError(CodeOrderNotFound, StateMachineModule, "order not found") } func ErrPanic() ErrorI { diff --git a/lib/swap.go b/lib/swap.go index f79b905f77..866ae419d2 100644 --- a/lib/swap.go +++ b/lib/swap.go @@ -1,120 +1,26 @@ package lib import ( + "bytes" "encoding/json" ) /* This file implements 'sell order book' logic for token swaps that is used throughout the app */ -// AddOrder() adds a sell order to the OrderBook -func (x *OrderBook) AddOrder(order *SellOrder) (id uint64) { - // if there's an empty slot, fill it with the sell order - for i, slot := range x.Orders { - // if a slot is empty - if slot.Empty() { - // set the order id to index - id = uint64(i) - // set the structure order id to 'id' - order.Id = id - // set the 'order' in the book - x.Orders[i] = order - // exit - return - } - } - // if there's no empty slots, add the sell order to the end - id = uint64(len(x.Orders)) - // set the structure order id to 'id' - order.Id = id - // add to the end of the book - x.Orders = append(x.Orders, order) - // exit - return -} - -// LockOrder() adds a 'buyer' recipient and send address and deadline height to the order to 'reserve' the order and prevent others from 'reserving it' -func (x *OrderBook) LockOrder(orderId int, buyersReceiveAddress, buyersSendAddress []byte, buyerChainDeadlineHeight uint64) (err ErrorI) { - // get the order from the order book using the 'order id' - order, err := x.GetOrder(orderId) - // if an error occurred during retrieval - if err != nil { - // exit with error - return - } - // if the buyer's receive address isn't nil - if order.BuyerReceiveAddress != nil { - // exit with 'order already locked' error - return ErrOrderLocked() - } - // set the buyer's receive, send, and deadline height in the order - order.BuyerReceiveAddress, order.BuyerSendAddress, order.BuyerChainDeadline = buyersReceiveAddress, buyersSendAddress, buyerChainDeadlineHeight - // update the order in the order book - x.Orders[orderId] = order - // exit - return -} - -// ResetOrder() removes a 'lock' from the order to 'un-reserve' the order -func (x *OrderBook) ResetOrder(orderId int) (err ErrorI) { - // get the order from the order book using the 'order id' - order, err := x.GetOrder(orderId) - // if an error occurred during retrieval - if err != nil { - // exit with error - return - } - // reset the buyer's receive, send, and deadline height in the order - order.BuyerReceiveAddress, order.BuyerSendAddress, order.BuyerChainDeadline = nil, nil, 0 - // update the order in the order book - x.Orders[orderId] = order - // exit - return -} - -// UpdateOrder() updates a sell order to the OrderBook, passing a nil `order` is effectively a delete operation -func (x *OrderBook) UpdateOrder(orderId int, order *SellOrder) (err ErrorI) { - // create a variable to track the 'max index' of order slots - maxIdx := len(x.Orders) - 1 - // if the order id exceeds the max index - if orderId > maxIdx { - // exit with 'not found' error - return ErrOrderNotFound(orderId) +// GetOrder() retrieves a sell order from the OrderBook +func (x *OrderBook) GetOrder(orderId []byte) (order *SellOrder, err ErrorI) { + // ensure non-nil + if x == nil { + return nil, ErrEmptyOrderBook() } - // if deleting from the end, shrink the slice - if order == nil && orderId == maxIdx { - // remove the final order from the slice - x.Orders = x.Orders[:maxIdx] - // continue shrinking the slice if nil entries are at the end - for i := maxIdx - 1; i >= 0; i-- { - // if the order slot is not empty - if !x.Orders[i].Empty() { - // exit the loop - break - } - // shrink the slice by 1 - x.Orders = x.Orders[:i] + // for each order in the book + for _, order = range x.Orders { + // if order is found + if bytes.Equal(order.Id, orderId) { + // exit with order + return } - // exit - return - } - // if not deleting from the end of the slice, simply replace the order - x.Orders[orderId] = order - // exit - return -} - -// GetOrder() retrieves a sell order from the OrderBook -func (x *OrderBook) GetOrder(orderId int) (order *SellOrder, err ErrorI) { - // create a variable to track the 'max index' of order slots - maxIdx := len(x.Orders) - 1 - // if the order id exceeds the max index - if orderId > maxIdx || x.Orders[orderId].Empty() { - // exit with 'not found' error - return nil, ErrOrderNotFound(orderId) } - // return the order at the index - order = x.Orders[orderId] - // exit return } @@ -125,7 +31,7 @@ func (x *SellOrder) Empty() bool { // jsonSellOrder is the json.Marshaller and json.Unmarshaler implementation for the SellOrder object type jsonSellOrder struct { - Id uint64 `json:"id,omitempty"` // the unique identifier of the order + Id HexBytes `json:"id,omitempty"` // the unique identifier of the order Committee uint64 `json:"committee,omitempty"` // the id of the committee that is in-charge of escrow for the swap AmountForSale uint64 `json:"amountForSale,omitempty"` // amount of CNPY for sale RequestedAmount uint64 `json:"requestedAmount,omitempty"` // amount of 'token' to receive diff --git a/lib/swap.pb.go b/lib/swap.pb.go index 18f0cd7f68..2d36fdb46e 100644 --- a/lib/swap.pb.go +++ b/lib/swap.pb.go @@ -63,7 +63,7 @@ type SellOrder struct { unknownFields protoimpl.UnknownFields // id: the unique identifier of the order - Id uint64 `protobuf:"varint,1,opt,name=Id,proto3" json:"id"` // @gotags: json:"id" + Id []byte `protobuf:"bytes,1,opt,name=Id,proto3" json:"id"` // @gotags: json:"id" // committee: the id of the committee that is in-charge of escrow for the swap Committee uint64 `protobuf:"varint,2,opt,name=Committee,proto3" json:"committee"` // @gotags: json:"committee" // amount_for_sale: amount of CNPY for sale @@ -114,11 +114,11 @@ func (*SellOrder) Descriptor() ([]byte, []int) { return file_swap_proto_rawDescGZIP(), []int{0} } -func (x *SellOrder) GetId() uint64 { +func (x *SellOrder) GetId() []byte { if x != nil { return x.Id } - return 0 + return nil } func (x *SellOrder) GetCommittee() uint64 { @@ -289,7 +289,7 @@ var File_swap_proto protoreflect.FileDescriptor var file_swap_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x73, 0x77, 0x61, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0xfb, 0x02, 0x0a, 0x09, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x49, + 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x53, 0x61, 0x6c, 0x65, diff --git a/lib/swap_test.go b/lib/swap_test.go deleted file mode 100644 index 67e85af4bd..0000000000 --- a/lib/swap_test.go +++ /dev/null @@ -1,317 +0,0 @@ -package lib - -import ( - "github.com/stretchr/testify/require" - "testing" -) - -func TestAddOrder(t *testing.T) { - tests := []struct { - name string - initialOrders []*SellOrder - newOrder *SellOrder - expectedID uint64 - expectedOrders []*SellOrder - }{ - { - name: "Add to empty OrderBook", - initialOrders: []*SellOrder{}, - newOrder: &SellOrder{}, - expectedID: 0, - expectedOrders: []*SellOrder{ - {Id: 0}, - }, - }, - { - name: "Add to OrderBook with empty slot", - initialOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t)}, - nil, - {Id: 2, SellersSendAddress: newTestAddressBytes(t)}, - }, - newOrder: &SellOrder{SellersSendAddress: newTestAddressBytes(t)}, - expectedID: 1, - expectedOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t)}, - {Id: 1, SellersSendAddress: newTestAddressBytes(t)}, - {Id: 2, SellersSendAddress: newTestAddressBytes(t)}, - }, - }, - { - name: "Add to OrderBook with no empty slots", - initialOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t)}, - {Id: 1, SellersSendAddress: newTestAddressBytes(t)}, - }, - newOrder: &SellOrder{SellersSendAddress: newTestAddressBytes(t)}, - expectedID: 2, - expectedOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t)}, - {Id: 1, SellersSendAddress: newTestAddressBytes(t)}, - {Id: 2, SellersSendAddress: newTestAddressBytes(t)}, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // initialize the OrderBook - orderBook := &OrderBook{Orders: test.initialOrders} - // add the order - require.Equal(t, test.expectedID, orderBook.AddOrder(test.newOrder)) - require.Equal(t, test.expectedOrders, orderBook.Orders) - }) - } -} - -func TestLockOrder(t *testing.T) { - tests := []struct { - name string - initialOrders []*SellOrder - orderId int - buyersReceiveAddress []byte - buyersSendAddress []byte - buyerChainDeadlineHeight uint64 - error string - expectedOrder *SellOrder - }{ - { - name: "Order already claimed", - initialOrders: []*SellOrder{ - { - Id: 0, - SellersSendAddress: newTestAddressBytes(t), - BuyerReceiveAddress: []byte("existing_receive"), - }, - }, - orderId: 0, - buyersReceiveAddress: []byte("buyer_receive"), - buyersSendAddress: []byte("buyer_send"), - buyerChainDeadlineHeight: 200, - error: "order locked", - expectedOrder: &SellOrder{ - Id: 0, - SellersSendAddress: newTestAddressBytes(t), - BuyerReceiveAddress: []byte("existing_receive"), - }, - }, - { - name: "Order not found", - initialOrders: []*SellOrder{{Id: 0, SellersSendAddress: newTestAddressBytes(t)}}, - orderId: 99, - buyersReceiveAddress: []byte("buyer_receive"), - buyersSendAddress: []byte("buyer_send"), - buyerChainDeadlineHeight: 300, - error: "not found", - expectedOrder: nil, - }, - { - name: "Successful order claim", - initialOrders: []*SellOrder{{Id: 0, SellersSendAddress: newTestAddressBytes(t), BuyerReceiveAddress: nil}}, - orderId: 0, - buyersReceiveAddress: []byte("buyer_receive"), - buyersSendAddress: []byte("buyer_send"), - buyerChainDeadlineHeight: 100, - expectedOrder: &SellOrder{ - Id: 0, - SellersSendAddress: newTestAddressBytes(t), - BuyerReceiveAddress: []byte("buyer_receive"), - BuyerSendAddress: []byte("buyer_send"), - BuyerChainDeadline: 100, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // init the OrderBook - orderBook := &OrderBook{Orders: test.initialOrders} - // execute the function call - err := orderBook.LockOrder( - test.orderId, - test.buyersReceiveAddress, - test.buyersSendAddress, - test.buyerChainDeadlineHeight, - ) - // check the error - if test.error != "" { - require.ErrorContains(t, err, test.error) - } - // check the state of the order - if test.expectedOrder != nil { - require.Equal(t, test.expectedOrder, orderBook.Orders[test.orderId]) - } - }) - } -} - -func TestResetOrder(t *testing.T) { - tests := []struct { - name string - initialOrders []*SellOrder - orderId int - error string - expectedOrder *SellOrder - }{ - { - name: "Order not found", - initialOrders: []*SellOrder{ - { - Id: 1, - SellersSendAddress: newTestAddressBytes(t), - BuyerReceiveAddress: []byte("buyer_receive"), - BuyerChainDeadline: 200, - }, - }, - orderId: 99, // Non-existent order - error: "not found", - expectedOrder: nil, - }, - { - name: "Unclaimed order reset", - initialOrders: []*SellOrder{ - { - Id: 0, - SellersSendAddress: newTestAddressBytes(t), - BuyerReceiveAddress: nil, - BuyerChainDeadline: 0, - }, - }, - orderId: 0, - expectedOrder: &SellOrder{ - Id: 0, - SellersSendAddress: newTestAddressBytes(t), - BuyerReceiveAddress: nil, - BuyerChainDeadline: 0, - }, - }, - { - name: "Successful order reset", - initialOrders: []*SellOrder{ - { - Id: 0, - SellersSendAddress: newTestAddressBytes(t), - BuyerReceiveAddress: []byte("buyer_receive"), - BuyerChainDeadline: 100, - }, - }, - orderId: 0, - expectedOrder: &SellOrder{ - Id: 0, - BuyerReceiveAddress: nil, - SellersSendAddress: newTestAddressBytes(t), - BuyerChainDeadline: 0, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // init the OrderBook - orderBook := &OrderBook{ - Orders: test.initialOrders, - } - // execute the function call - err := orderBook.ResetOrder(test.orderId) - if test.error != "" { - require.ErrorContains(t, err, test.error) - } - // verify the state of the order (if it exists) - if test.expectedOrder != nil { - require.Equal(t, test.expectedOrder, orderBook.Orders[test.orderId]) - } - }) - } -} - -func TestUpdateOrder(t *testing.T) { - tests := []struct { - name string - initialOrders []*SellOrder - orderId int - newOrder *SellOrder - error ErrorI - expectedOrders []*SellOrder - }{ - { - name: "Update an existing order", - initialOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 100}, - {Id: 1, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 200}, - }, - orderId: 1, - newOrder: &SellOrder{ - Id: 1, - SellersSendAddress: newTestAddressBytes(t), - AmountForSale: 300, - }, - expectedOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 100}, - {Id: 1, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 300}, - }, - }, - { - name: "Delete an order from the middle", - initialOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 100}, - {Id: 1, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 200}, - {Id: 2, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 300}, - }, - orderId: 1, - newOrder: nil, - expectedOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 100}, - nil, - {Id: 2, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 300}, - }, - }, - { - name: "Delete the last order and shrink slice", - initialOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 100}, - {Id: 1, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 200}, - }, - orderId: 1, - newOrder: nil, - expectedOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 100}, - }, - }, - { - name: "Order not found", - initialOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 100}, - }, - orderId: 2, - newOrder: &SellOrder{Id: 2, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 400}, - error: ErrOrderNotFound(2), - expectedOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 100}, - }, - }, - { - name: "Shrink slice with trailing nils", - initialOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 100}, - nil, - nil, - }, - orderId: 2, - newOrder: nil, - expectedOrders: []*SellOrder{ - {Id: 0, SellersSendAddress: newTestAddressBytes(t), AmountForSale: 100}, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // init the OrderBook - orderBook := &OrderBook{Orders: test.initialOrders} - // execute the function call - require.Equal(t, test.error, orderBook.UpdateOrder(test.orderId, test.newOrder)) - // verify the state of the order book - require.Equal(t, test.expectedOrders, orderBook.Orders) - }) - } -} diff --git a/lib/util.go b/lib/util.go index 6017ba2a6a..ba66781bf1 100644 --- a/lib/util.go +++ b/lib/util.go @@ -1,6 +1,7 @@ package lib import ( + "bytes" "encoding/hex" "encoding/json" "fmt" @@ -761,3 +762,26 @@ func Append(a, b []byte) []byte { copy(out[len(a):], b) return out } + +// EqualByteSlices() performs equality check on two byte slices +func EqualByteSlices(a, b [][]byte) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if !bytes.Equal(a[i], b[i]) { + return false + } + } + return true +} + +// ContainsByteSlice() checks to see if the byte slice is within the list +func ContainsByteSlice(list [][]byte, target []byte) (found bool) { + for _, item := range list { + if bytes.Equal(item, target) { + return + } + } + return +} diff --git a/store/store_test.go b/store/store_test.go index b14a819698..425f1b52ac 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -143,18 +143,18 @@ func TestPartitionHeight(t *testing.T) { }, { name: "less than partition frequency", - height: 9999, + height: 999, expected: 1, }, { name: "greater than partition frequency", - height: 10001, - expected: 10000, + height: 1001, + expected: 1000, }, { name: "2x partition frequency", - height: 27894, - expected: 20000, + height: 2789, + expected: 2000, }, } for _, test := range tests { @@ -551,18 +551,18 @@ func TestHistoricalPrefix(t *testing.T) { }, { name: "less than partition frequency", - height: 9999, + height: 999, expected: append([]byte(historicStatePrefix), binary.BigEndian.AppendUint64(nil, 1)...), }, { name: "greater than partition frequency", - height: 10001, - expected: append([]byte(historicStatePrefix), binary.BigEndian.AppendUint64(nil, 10000)...), + height: 1001, + expected: append([]byte(historicStatePrefix), binary.BigEndian.AppendUint64(nil, 1000)...), }, { name: "2x partition frequency", - height: 27894, - expected: append([]byte(historicStatePrefix), binary.BigEndian.AppendUint64(nil, 20000)...), + height: 2794, + expected: append([]byte(historicStatePrefix), binary.BigEndian.AppendUint64(nil, 2000)...), }, } for _, test := range tests {