store: remove the taken-order code

This commit is contained in:
shum committed 2026-10-22 12:02:34 +00:00
1 parent e6fb909d61
commit f08763924c
16 files changed
+59 -466

No files matched your search

@@ -1,6 +1,5 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
module StoreService.Names.Abi
( AbiValue (..),
@@ -21,7 +20,6 @@ module StoreService.Names.Abi
decodeBytes32,
decodeBytes,
decodeString,
decodePrices,
)
where
@@ -126,15 +124,6 @@ decodeBytes ret = offsetAt 0 ret >>= bytesAt ret
decodeString :: ByteString -> Either AbiError Text
decodeString ret = decodeBytes ret >>= first (const AENotUtf8) . decodeUtf8'
-- | Decodes @(uint256, (uint256, uint256)[])@, the oracle's base price and its prices by label length.
decodePrices :: ByteString -> Either AbiError (Integer, [(Integer, Integer)])
decodePrices ret = do
base <- uintAt 0 ret
items <- offsetAt abiWordSize ret
n <- offsetAt items ret
let pairAt i = let o = items + abiWordSize * (1 + 2 * i) in (,) <$> uintAt o ret <*> uintAt (o + abiWordSize) ret
(base,) <$> mapM pairAt [0 .. n - 1]
wordAt :: Int -> ByteString -> Either AbiError ByteString
wordAt o ret
| o + abiWordSize <= B.length ret = Right $ B.take abiWordSize $ B.drop o ret
@@ -14,7 +14,6 @@ module StoreService.Names.Contracts
nameRecords,
textRecord,
chainPriceCents,
codeLengthFor,
sponsorNonce,
resolverNonce,
confirmedTag,
@@ -33,12 +32,10 @@ import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.Functor.Compose (Compose (..))
import Data.IORef (IORef, atomicWriteIORef, newIORef, readIORef)
import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Text.Encoding (decodeLatin1, encodeUtf8)
import Data.Word (Word64)
import Simplex.Chat.Badges.Code (maxNameLength, minNameLength)
import Simplex.Messaging.Eth.Address (Address)
import Simplex.Messaging.SimplexName (LabelHash (..), SimplexTLD)
import Simplex.Messaging.Util (tshow)
@@ -135,19 +132,6 @@ chainPriceCents ct len years = call ct (controller ct) ("rentPriceUSD(string,uin
where
args = abiTuple [Dynamic $ abiBytes $ B.replicate len 'a', Static $ uint256 $ toInteger years * secondsPerYear]
-- | The shortest length from the shortest label such that every length up to the longest costs at most these cents
-- for these years, from one read of the oracle's whole price list, which need not fall with the length.
codeLengthFor :: Contracts -> Int -> Int -> IO (Either ChainError (Maybe Int))
codeLengthFor ct cents years = runExceptT $ do
oracle <- ExceptT $ call ct (controller ct) ("prices()", "") decodeAddress
(basePerYear, exceptions) <- ExceptT $ call ct oracle ("prices()", "") decodePrices
let exceptionPrices = M.fromList exceptions
perYear len = M.findWithDefault basePerYear (toInteger len) exceptionPrices
covered len = perYear len * toInteger years <= toInteger cents
pure $ case takeWhile covered [maxNameLength, maxNameLength - 1 .. minNameLength] of
[] -> Nothing
lens -> Just $ last lens
sponsorNonce :: Contracts -> Address -> IO (Either ChainError Word64)
sponsorNonce ct owner = call ct (cSponsor $ chainConfig ct) ("nonces(address)", addressWord owner) decodeWord64
@@ -31,16 +31,12 @@ module StoreService.Names.Orders
checkNameFree,
checkPrice,
movable,
issuable,
orderFieldsOf,
checkMoveMeets,
checkMovePrice,
retargetOrder,
versionWritten,
confirmLanded,
issuedCodeHash,
checkTakenCodeLength,
issueTakenCode,
)
where
@@ -60,7 +56,6 @@ import Data.Time.Clock (UTCTime, getCurrentTime)
import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
import Data.Word (Word64)
import Simplex.Chat.Badges.Code (maxNameLength, minNameLength)
import Simplex.Chat.Badges.Types (BadgeCodePaymentStatus (..))
import Simplex.Messaging.Agent.Store.Common (DBStore, withTransaction)
import qualified Simplex.Messaging.Agent.Store.DB as DB
import Simplex.Messaging.Crypto.Secp256k1 (RecoverableSignature)
@@ -70,12 +65,12 @@ import Simplex.Messaging.Util (tshow)
import StoreService.Config (NamesConfig (..))
import StoreService.Log (logError, logWarn)
import StoreService.Names.Codes (CodeRefusal (..), codeRefusal, reserveCode, reserveRefusal, spendLandedCode)
import StoreService.Names.Contracts (Contracts, NameStatus (..), chainPriceCents, codeLengthFor, confirmedTag, nameExpires, nameStatus, ownerOfToken, secondsPerYear)
import StoreService.Names.Contracts (Contracts, NameStatus (..), chainPriceCents, confirmedTag, nameExpires, nameStatus, ownerOfToken, secondsPerYear)
import StoreService.Names.Env (NamesEnv (..))
import StoreService.Names.Phases (Phases, RegistrationId (..))
import StoreService.Names.Abi (bytes32FromHex, hex)
import StoreService.Names.Signatures (OrderFields (..), orderMessage, parseChecksumAddress, parseSignature, personalDigest)
import StoreService.Names.Store (CodeHashTaken, LiveOrder (..), NamePhase (..), NameRegistration (..), NewPrepaidCode (..), NewRegistration (..), PrepaidCode (..), RegistrationPayment (..), RegistrationRefused (..), NameTransaction (..), TxFor (..), TxStatus (..), VersionWrite (..), closeOverdue, createCodeRegistration, getLiveOrders, getPrepaidCode, getPrepaidCodeById, getRegistrationByOrderHash, insertPrepaidCode, isLabelRegistering, registrationTransactions, retargetRegistration, setIssuedCode, updateRegistration)
import StoreService.Names.Store (LiveOrder (..), NamePhase (..), NameRegistration (..), NewRegistration (..), PrepaidCode (..), RegistrationPayment (..), RegistrationRefused (..), NameTransaction (..), TxFor (..), TxStatus (..), VersionWrite (..), closeOverdue, createCodeRegistration, getLiveOrders, getPrepaidCode, getPrepaidCodeById, getRegistrationByOrderHash, isLabelRegistering, registrationTransactions, retargetRegistration, updateRegistration)
import StoreService.Names.Turns (Turns, withCodeTurn)
data OrdersEnv = OrdersEnv
@@ -108,10 +103,6 @@ data OrderRefusal
| ORCatalogChanged Int
| ORCode CodeRefusal
| ORPriceTooHigh Int
| ORCodeLengthChanged Int
| ORCodeUnavailable
| ORCodeConflict
| ORProviderUnavailable
deriving (Eq, Show)
newtype OrderRefused = OrderRefused OrderRefusal
@@ -134,11 +125,6 @@ data WriteRefusal = WRChanged | WRRefused OrderRefusal
data Landing = LNotLanded | LRegistered Int64 | LChanged | LUnread
deriving (Eq, Show)
newtype TakenCodeConflict = TakenCodeConflict CodeHashTaken
deriving (Show)
instance E.Exception TakenCodeConflict
maxSealedChars :: Int
maxSealedChars = 11095
@@ -285,12 +271,6 @@ movable NameRegistration {nrPhase, nrPayment, nrLinksRefusedAt} = case nrPhase o
PCommitted -> isJust nrLinksRefusedAt
_ -> False
-- | Only a paid order that lost its name turns into a code.
issuable :: NameRegistration -> Bool
issuable NameRegistration {nrPhase, nrPayment} = case (nrPhase, nrPayment) of
(PTaken, RPInvoice {}) -> True
_ -> False
orderFieldsOf :: NameRegistration -> OrderFields
orderFieldsOf NameRegistration {nrLabelHash, nrLength, nrDuration, nrCommitment, nrSealed} =
OrderFields {ofLabelHash = nrLabelHash, ofLength = nrLength, ofDuration = nrDuration, ofCommitment = nrCommitment, ofSealed = nrSealed}
@@ -372,35 +352,3 @@ confirmLanded OrdersEnv {oeStore, oeContracts, oeTurns, oeNow} r@NameRegistratio
<> ", which was released and then spent, revoked or expired: the name was paid with it"
pure v
ridText (RegistrationId t) = t
-- | The hash of the code a taken order was turned into.
issuedCodeHash :: DBStore -> NameRegistration -> IO (Maybe ByteString)
issuedCodeHash st NameRegistration {nrIssuedCodeId} = case nrIssuedCodeId of
Just codeId -> fmap pcCodeHash <$> withTransaction st (`getPrepaidCodeById` codeId)
Nothing -> pure Nothing
-- | The code's minLength for the order's price and years must be the length the page derived the code with.
checkTakenCodeLength :: Contracts -> NameRegistration -> Int -> IO (Either OrderRefusal ())
checkTakenCodeLength ct NameRegistration {nrPrice, nrYears} sent =
codeLengthFor ct nrPrice nrYears >>= \case
Left e -> Left ORProviderUnavailable <$ logWarn ("names: a taken code's length read failed: " <> tshow e)
Right Nothing -> pure $ Left ORCodeUnavailable
Right (Just len)
| len /= sent -> pure $ Left $ ORCodeLengthChanged len
| otherwise -> pure $ Right ()
-- | The order becomes code_issued by a compare-and-set on the version read, then its code is inserted and linked, in one
-- transaction, so the registration's row is locked before the code's. A hash another code has rolls the order's write back.
issueTakenCode :: OrdersEnv -> NameRegistration -> ByteString -> Int -> IO (Either WriteRefusal Int64)
issueTakenCode OrdersEnv {oeStore, oeNow} r@NameRegistration {nrId, nrYears} codeHash minLength = do
now <- oeNow
let code = NewPrepaidCode {npcCodeHash = codeHash, npcMinLength = minLength, npcYears = nrYears, npcPaymentStatus = CPSPaid, npcExpiresAt = Nothing}
issue db =
updateRegistration db r {nrPhase = PCodeIssued} now >>= \case
Written v ->
insertPrepaidCode db code now >>= \case
Right codeId -> Right v <$ setIssuedCode db nrId codeId
Left taken -> E.throwIO $ TakenCodeConflict taken
-- a code_issued order is not live, so only a changed version refuses the write
_ -> pure $ Left WRChanged
either (\(TakenCodeConflict _) -> Left $ WRRefused ORCodeConflict) id <$> E.try (withTransaction oeStore issue)
@@ -10,7 +10,6 @@ module StoreService.Names.Signatures
orderMessage,
findMessage,
retargetMessage,
takenCodeMessage,
parseChecksumAddress,
parseAnyCaseAddress,
)
@@ -100,9 +99,6 @@ findMessage owner timestamp = message ["find", hex $ unAddress owner, tshow time
retargetMessage :: RegistrationId -> OrderFields -> Word64 -> ByteString
retargetMessage (RegistrationId rid) order timestamp = message $ ["name", rid] <> orderWords order <> [tshow timestamp]
takenCodeMessage :: RegistrationId -> Text -> Int -> Word64 -> ByteString
takenCodeMessage (RegistrationId rid) codeHash codeLength timestamp = message ["code", rid, codeHash, tshow codeLength, tshow timestamp]
message :: [Text] -> ByteString
message = encodeUtf8 . T.unwords . ("simplex.domains" :)
@@ -41,7 +41,6 @@ module StoreService.Names.Store
closeOverdue,
updateRegistration,
retargetRegistration,
setIssuedCode,
findRegistrations,
isLabelRegistering,
activeRegistrations,
@@ -121,7 +120,6 @@ data NamePhase
| PRegistered
| PTaken
| PFailed
| PCodeIssued
deriving (Eq, Show, Enum, Bounded)
instance TextEncoding NamePhase where
@@ -134,7 +132,6 @@ instance TextEncoding NamePhase where
PRegistered -> "registered"
PTaken -> "taken"
PFailed -> "failed"
PCodeIssued -> "code_issued"
textDecode t = lookup t [(textEncode p, p) | p <- [minBound .. maxBound]]
instance FromField NamePhase where fromField = fromTextField_ textDecode
@@ -179,7 +176,6 @@ data NameRegistration = NameRegistration
nrRevealAfter :: Maybe Word64,
nrRevealBefore :: Maybe Word64,
nrExpiresAt :: Maybe Word64,
nrIssuedCodeId :: Maybe Int64,
nrRegisteredAt :: Maybe UTCTime,
nrFailedAt :: Maybe UTCTime,
nrError :: Maybe Text,
@@ -401,7 +397,7 @@ registrationColumns =
[sql|
SELECT r.registration_id, r.version, r.label_hash, r.length, r.duration, r.owner, r.commitment, r.sealed, r.phase, r.years,
r.price, r.method, ni.invoice_id, r.name_prepaid_code_id, r.label, r.secret, r.contact_link, r.channel_link, r.intent_nonce, r.intent_deadline,
r.intent_signature, r.commit_tx, r.commit_block, r.register_tx, r.register_block, r.reveal_after, r.reveal_before, r.expires_at, r.issued_code_id, r.registered_at,
r.intent_signature, r.commit_tx, r.commit_block, r.register_tx, r.register_block, r.reveal_after, r.reveal_before, r.expires_at, r.registered_at,
r.failed_at, r.error, r.live, r.order_hash, r.moves, r.links_refused_at, r.created_at, r.updated_at
|]
@@ -419,14 +415,14 @@ selectRegistrations = registrationColumns <> registrationFrom
type RegistrationRow =
(Text, Int64, Binary ByteString, Int, Int64, Binary ByteString, Binary ByteString, Text, NamePhase, Int)
:. (Int, Maybe Text, Maybe Text, Maybe Int64, Maybe Text, Maybe (Binary ByteString), Maybe Text, Maybe Text, Maybe Int64, Maybe Int64)
:. (Maybe (Binary ByteString), Maybe (Binary ByteString), Maybe Int64, Maybe (Binary ByteString), Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime)
:. (Maybe (Binary ByteString), Maybe (Binary ByteString), Maybe Int64, Maybe (Binary ByteString), Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime)
:. (Maybe UTCTime, Maybe Text, BoolInt, Binary ByteString, Int, Maybe UTCTime, UTCTime, UTCTime)
toRegistration :: RegistrationRow -> Either Text NameRegistration
toRegistration
( (rid, nrVersion, Binary lh, nrLength, duration, Binary owner, Binary nrCommitment, nrSealed, nrPhase, nrYears)
:. (nrPrice, method_, invoiceId_, codeId_, label_, secret_, nrContactLink, nrChannelLink, nonce_, deadline_)
:. (signature_, commitTx, commitBlock, registerTx, registerBlock, revealAfter, revealBefore, expiresAt, nrIssuedCodeId, nrRegisteredAt)
:. (signature_, commitTx, commitBlock, registerTx, registerBlock, revealAfter, revealBefore, expiresAt, nrRegisteredAt)
:. (nrFailedAt, nrError, BI nrLive, Binary nrOrderHash, nrMoves, nrLinksRefusedAt, nrCreatedAt, nrUpdatedAt)
) = do
nrOwner <- decodeAddress owner
@@ -462,7 +458,6 @@ toRegistration
nrRevealAfter = word64 <$> revealAfter,
nrRevealBefore = word64 <$> revealBefore,
nrExpiresAt = word64 <$> expiresAt,
nrIssuedCodeId,
nrRegisteredAt,
nrFailedAt,
nrError,
@@ -552,7 +547,6 @@ orderLive NameRegistration {nrPhase, nrPayment, nrLive} = case nrPhase of
RPInvoice {} -> True
RPCode _ -> False
PRegistered -> False
PCodeIssued -> False
-- | Writes every changeable field of a registration read at its nrVersion, unless it changed since, with live as orderLive decides.
-- A closed order is never made live again, and a write that would make the order meet another live one changes nothing.
@@ -566,7 +560,7 @@ updateRegistration db r now = do
SET version = version + 1, label_hash = ?, length = ?, duration = ?, commitment = ?, sealed = ?, phase = ?, price = ?,
label = ?, secret = ?, contact_link = ?, channel_link = ?, intent_nonce = ?, intent_deadline = ?, intent_signature = ?,
commit_tx = ?, commit_block = ?, register_tx = ?, register_block = ?, reveal_after = ?, reveal_before = ?, expires_at = ?,
issued_code_id = ?, registered_at = ?, failed_at = ?, error = ?, live = ?, moves = ?, links_refused_at = ?, updated_at = ?
registered_at = ?, failed_at = ?, error = ?, live = ?, moves = ?, links_refused_at = ?, updated_at = ?
WHERE registration_id = ? AND version = ?
AND (? = 0 OR (live = 1 AND NOT EXISTS (
SELECT 1 FROM sx_badge_service_name_registrations o
@@ -576,14 +570,14 @@ updateRegistration db r now = do
( (Binary lh, nrLength, int64 nrDuration, Binary nrCommitment, nrSealed, nrPhase, nrPrice)
:. (rvLabel <$> nrReveal, Binary . rvSecret <$> nrReveal, rvContactLink =<< nrReveal, rvChannelLink =<< nrReveal, int64 . rvNonce <$> nrReveal, int64 . rvDeadline <$> nrReveal, Binary . serializeSignature . rvSignature <$> nrReveal)
:. (txHashField <$> nrCommitTx, int64 <$> nrCommitBlock, txHashField <$> nrRegisterTx, int64 <$> nrRegisterBlock, int64 <$> nrRevealAfter, int64 <$> nrRevealBefore, int64 <$> nrExpiresAt)
:. (nrIssuedCodeId, truncateToSecond <$> nrRegisteredAt, truncateToSecond <$> nrFailedAt, nrError, BI live, nrMoves, truncateToSecond <$> nrLinksRefusedAt, truncateToSecond now)
:. (truncateToSecond <$> nrRegisteredAt, truncateToSecond <$> nrFailedAt, nrError, BI live, nrMoves, truncateToSecond <$> nrLinksRefusedAt, truncateToSecond now)
:. (rid, nrVersion, BI live, rid, Binary nrCommitment, Binary $ unAddress nrOwner, Binary lh)
)
if changed > 0 then pure $ Written (nrVersion + 1) else refusal
where
NameRegistration {nrId = RegistrationId rid, nrVersion, nrLabelHash = LabelHash lh, nrLength, nrDuration, nrOwner, nrCommitment, nrSealed, nrPhase, nrPrice, nrReveal} = r
NameRegistration {nrCommitTx, nrCommitBlock, nrRegisterTx, nrRegisterBlock, nrRevealAfter, nrRevealBefore, nrExpiresAt} = r
NameRegistration {nrIssuedCodeId, nrRegisteredAt, nrFailedAt, nrError, nrMoves, nrLinksRefusedAt} = r
NameRegistration {nrRegisteredAt, nrFailedAt, nrError, nrMoves, nrLinksRefusedAt} = r
live = orderLive r
refusal =
DB.query db "SELECT version, live FROM sx_badge_service_name_registrations WHERE registration_id = ?" (Only rid) >>= \case
@@ -617,10 +611,6 @@ retargetRegistration db r OrderFields {ofLabelHash, ofLength, ofDuration, ofComm
nrError = Nothing
}
-- | Links the code a taken order was turned into, in the transaction that wrote its code_issued phase and version.
setIssuedCode :: DB.Connection -> RegistrationId -> Int64 -> IO ()
setIssuedCode db (RegistrationId rid) codeId = DB.execute db "UPDATE sx_badge_service_name_registrations SET issued_code_id = ? WHERE registration_id = ?" (codeId, rid)
-- | Newest first, at most findLimit.
findRegistrations :: DB.Connection -> Address -> IO [NameRegistration]
findRegistrations db owner =
@@ -33,39 +33,32 @@ import Simplex.Messaging.Util (safeDecodeUtf8)
import StoreService.Config (ChainConfig (..), NamesConfig (..))
import StoreService.Names.Abi (hex)
import StoreService.Names.Chain (TxHash (..))
import StoreService.Names.Contracts (Contracts, codeLengthFor, sponsorNonce)
import StoreService.Names.Contracts (Contracts, sponsorNonce)
import StoreService.Names.Phases (RegistrationId (..))
import StoreService.Names.Store (NamePhase (..), NameRegistration (..), NameReveal (..), PrepaidCode (..), RegistrationPayment (..), getPrepaidCodeById, getRegistration)
import StoreService.Names.Store (NamePhase (..), NameRegistration (..), NameReveal (..), getRegistration)
-- | The view's chain reads; a field whose read failed is null and named in vrUnread.
data ViewReads = ViewReads
{ vrIntentNonce :: Maybe Word64,
vrCodeLength :: Maybe Int,
vrUnread :: [Text]
}
deriving (Eq, Show)
registrationView :: DBStore -> NamesConfig -> Contracts -> NameRegistration -> IO J.Value
registrationView st cfg ct r = viewJSON cfg r <$> readViewChain st ct r
registrationView _ cfg ct r = viewJSON cfg r <$> readViewChain ct r
-- | The intent nonce only in committed; a code length for a paid order in taken, and the issued code's in code_issued.
readViewChain :: DBStore -> Contracts -> NameRegistration -> IO ViewReads
readViewChain st ct NameRegistration {nrPhase, nrOwner, nrPayment, nrPrice, nrYears, nrIssuedCodeId} = case nrPhase of
-- | The intent nonce only in committed.
readViewChain :: Contracts -> NameRegistration -> IO ViewReads
readViewChain ct NameRegistration {nrPhase, nrOwner} = case nrPhase of
PCommitted -> sponsorNonce ct nrOwner >>= \case
Right nonce -> pure none {vrIntentNonce = Just nonce}
Left _ -> pure none {vrUnread = ["intentNonce"]}
PTaken | RPInvoice {} <- nrPayment -> codeLengthFor ct nrPrice nrYears >>= \case
Right len -> pure none {vrCodeLength = len}
Left _ -> pure none {vrUnread = ["codeLength"]}
PCodeIssued -> do
code <- maybe (pure Nothing) (\codeId -> withTransaction st (`getPrepaidCodeById` codeId)) nrIssuedCodeId
pure none {vrCodeLength = pcMinLength <$> code}
_ -> pure none
where
none = ViewReads {vrIntentNonce = Nothing, vrCodeLength = Nothing, vrUnread = []}
none = ViewReads {vrIntentNonce = Nothing, vrUnread = []}
viewJSON :: NamesConfig -> NameRegistration -> ViewReads -> J.Value
viewJSON NamesConfig {nTLD, nChain = ChainConfig {cChainId, cResolver, cSponsor}} r ViewReads {vrIntentNonce, vrCodeLength, vrUnread} =
viewJSON NamesConfig {nTLD, nChain = ChainConfig {cChainId, cResolver, cSponsor}} r ViewReads {vrIntentNonce, vrUnread} =
J.object
[ "id" .= rid,
"version" .= nrVersion,
@@ -84,7 +77,6 @@ viewJSON NamesConfig {nTLD, nChain = ChainConfig {cChainId, cResolver, cSponsor}
"years" .= nrYears,
"duration" .= nrDuration,
"price" .= nrPrice,
"codeLength" .= vrCodeLength,
"commitTx" .= (txText <$> nrCommitTx),
"registerTx" .= (txText <$> nrRegisterTx),
"revealAfter" .= nrRevealAfter,
@@ -12,7 +12,7 @@ import StoreService.Names.Orders (ordersEnv)
import StoreService.Web.Http (HttpEnv, Limit, Respond, codeLimit, createLimit, findLimit, holdMicros, limited, only, previewLimit, readLimit, recordsLimit, searchLimit)
import StoreService.Web.Names.Codes (codeCheckHandler)
import StoreService.Web.Names.Orders (codeOrderHandler)
import StoreService.Web.Names.Owner (findHandler, retargetHandler, takenCodeHandler)
import StoreService.Web.Names.Owner (findHandler, retargetHandler)
import StoreService.Web.Names.Records (recordsHandler)
import StoreService.Web.Names.Registration (registrationHandler, revealHandler)
import StoreService.Web.Names.Search (previewHandler, searchHandler)
@@ -35,7 +35,6 @@ namesRoutes env@NamesEnv {neConfig, neStore, neContracts, neProfiles, neRecords,
["api", "registration", "find"] -> Just Route {rMethod = "POST", rLimit = findLimit, rHandler = findHandler (ordersEnv env)}
["api", "registration", rid] -> Just Route {rMethod = "GET", rLimit = readLimit, rHandler = registrationHandler (ordersEnv env) holdMicros rid}
["api", "registration", rid, "name"] -> Just Route {rMethod = "POST", rLimit = createLimit, rHandler = retargetHandler (ordersEnv env) rid}
["api", "registration", rid, "code"] -> Just Route {rMethod = "POST", rLimit = createLimit, rHandler = takenCodeHandler (ordersEnv env) rid}
["api", "registration", rid, "reveal"] -> Just Route {rMethod = "POST", rLimit = readLimit, rHandler = revealHandler (ordersEnv env) rid}
_ -> Nothing
@@ -45,7 +45,7 @@ import StoreService.Orders (publishRegistration)
import StoreService.Providers (OrderDraft (..), Provider (..), ProviderError (..), ProviderInvoice (..), expiresItself)
import StoreService.Store.Invoices (InvoiceRow (..), NewNameInvoice (..), getInvoice, insertNameInvoice, newInvoiceId, truncateToSecond)
import StoreService.Waiters (Waiters, publish)
import StoreService.Web.Http (Respond, badRequest, codeConflict, confirmationsFor, internalError, invalidName, invoiceAt, invoicePairs, invoiceWindow, jsonError, jsonResponse, nameBodyBytes, providerFor, providerNamed, providerUnavailable, readBoundedBody)
import StoreService.Web.Http (Respond, badRequest, confirmationsFor, internalError, invalidName, invoiceAt, invoicePairs, invoiceWindow, jsonError, jsonResponse, nameBodyBytes, providerFor, providerNamed, providerUnavailable, readBoundedBody)
import StoreService.Web.Names.Codes (codeRefusalResponse)
data InvoiceOrder = InvoiceOrder
@@ -236,7 +236,3 @@ orderRefusalResponse = \case
ORCatalogChanged price -> jsonResponse status409 $ J.object ["error" .= ("catalog_changed" :: Text), "price" .= price]
ORCode refusal -> codeRefusalResponse refusal
ORPriceTooHigh price -> jsonResponse status409 $ J.object ["error" .= ("price_too_high" :: Text), "price" .= price]
ORCodeLengthChanged len -> jsonResponse status409 $ J.object ["error" .= ("code_length_changed" :: Text), "codeLength" .= len]
ORCodeUnavailable -> jsonError status409 "code_unavailable"
ORCodeConflict -> codeConflict
ORProviderUnavailable -> providerUnavailable
@@ -2,14 +2,13 @@
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
-- | The requests only the owner code can sign: find, another name, and a code for a taken order.
module StoreService.Web.Names.Owner (findHandler, retargetHandler, takenCodeHandler) where
-- | The requests only the owner code can sign: find and another name.
module StoreService.Web.Names.Owner (findHandler, retargetHandler) where
import Control.Concurrent.STM (atomically)
import Data.Aeson ((.=))
import qualified Data.Aeson as J
import qualified Data.Aeson.KeyMap as KM
import Data.ByteString (ByteString)
import Data.Int (Int64)
import Data.Text (Text)
import Data.Word (Word64)
@@ -27,11 +26,11 @@ import StoreService.Log (logError, logInfo)
import StoreService.Names.Abi (hex)
import StoreService.Names.Orders
import StoreService.Names.Phases (RegistrationId (..), publish)
import StoreService.Names.Signatures (OrderFields (..), findMessage, parseAnyCaseAddress, parseSignature, retargetMessage, takenCodeMessage)
import StoreService.Names.Signatures (OrderFields (..), findMessage, parseAnyCaseAddress, parseSignature, retargetMessage)
import StoreService.Names.Store (NamePhase (..), NameRegistration (..), RegistrationPayment (..), findRegistrations, getRegistration)
import StoreService.Names.Turns (withLabelTurn, withLabelTurns)
import StoreService.Names.Turns (withLabelTurns)
import StoreService.Names.View (registrationAt, registrationView)
import StoreService.Web.Http (Respond, internalError, jsonResponse, maxBodyBytes, nameBodyBytes, notFound, parseCodeHash, readBoundedBody)
import StoreService.Web.Http (Respond, internalError, jsonResponse, maxBodyBytes, nameBodyBytes, notFound, readBoundedBody)
import StoreService.Web.Names.Orders (Answer (..), answerNow, firstRefusal, orderRefusalResponse, sendAnswer)
import StoreService.Web.Names.Registration (registrationChangedResponse, registrationRefusalResponse)
@@ -41,14 +40,6 @@ data FindRequest = FindRequest
frSignature :: RecoverableSignature
}
data TakenCodeRequest = TakenCodeRequest
{ tcHashText :: Text,
tcHash :: ByteString,
tcLength :: Int,
tcTimestamp :: Word64,
tcSignature :: RecoverableSignature
}
-- | POST /api/registration/find, the owner's orders newest first.
findHandler :: OrdersEnv -> Request -> Respond -> IO ResponseReceived
findHandler OrdersEnv {oeStore, oeNow} req respond =
@@ -144,56 +135,6 @@ retargetHandler env@OrdersEnv {oeStore, oeConfig, oeContracts, oeTurns, oeNow} r
changed = answerNow <$> registrationChangedResponse env rid
refuse = refused "POST /api/registration/:id/name"
-- | POST /api/registration/:id/code, a paid taken order turned into a prepaid code, under its name's turn.
takenCodeHandler :: OrdersEnv -> Text -> Request -> Respond -> IO ResponseReceived
takenCodeHandler env@OrdersEnv {oeStore, oeContracts, oeTurns, oeNow} ridText req respond =
readBoundedBody maxBodyBytes req >>= requested >>= sendAnswer respond
where
rid = RegistrationId ridText
requested = \case
Just body | Just (J.Object o) <- J.decode body, Just request <- parseTakenCode o -> signed request
_ -> refuse ORBadRequest
signed request@TakenCodeRequest {tcHashText, tcLength, tcTimestamp, tcSignature} =
withTransaction oeStore (`getRegistration` rid) >>= \case
Nothing -> pure $ answerNow notFound
Just NameRegistration {nrOwner, nrLabelHash} ->
checkSigner nrOwner (takenCodeMessage rid tcHashText tcLength tcTimestamp) tcSignature >>= \case
Left refusal -> refuse refusal
Right () -> withLabelTurn oeTurns nrLabelHash $ inTurn nrLabelHash request
inTurn held request@TakenCodeRequest {tcHash} = do
now <- oeNow
withTransaction oeStore (`getRegistration` rid) >>= \case
Nothing -> pure $ answerNow notFound
Just r
| nrLabelHash r /= held -> changed
| otherwise ->
issuedCodeHash oeStore r >>= \case
Just issued | issued == tcHash -> pure $ answerNow codeIssued
_ -> issue now r request
issue now r TakenCodeRequest {tcHash, tcLength, tcTimestamp}
| Left refusal <- checkTimestamp now tcTimestamp = refuse refusal
| not (issuable r) = refuse ORBadRequest
| otherwise =
checkTakenCodeLength oeContracts r tcLength >>= \case
Left refusal -> refuse refusal
Right () ->
issueTakenCode env r tcHash tcLength >>= \case
Right v -> pure $ written env rid v codeIssued
Left WRChanged -> changed
Left (WRRefused refusal) -> refuse refusal
codeIssued = jsonResponse status200 $ J.object []
changed = answerNow <$> registrationChangedResponse env rid
refuse = refused "POST /api/registration/:id/code"
parseTakenCode :: J.Object -> Maybe TakenCodeRequest
parseTakenCode o = do
tcHashText <- textField o "codeHash"
tcHash <- parseCodeHash tcHashText
tcLength <- integerField =<< KM.lookup "codeLength" o
tcTimestamp <- integerField =<< KM.lookup "timestamp" o
tcSignature <- eitherToMaybe . parseSignature =<< textField o "signature"
pure TakenCodeRequest {tcHashText, tcHash, tcLength, tcTimestamp, tcSignature}
-- | Published after the turn, as the answer is sent.
written :: OrdersEnv -> RegistrationId -> Int64 -> Response -> Answer
written OrdersEnv {oePhases} rid v aResponse = Answer {aResponse, aAfter = atomically $ publish oePhases rid v}
+4 -21
View File
@@ -66,8 +66,7 @@ badgeTests = do
describe "StoreService names: codes" $ do
it "encodes an owner code's entropy and its checksum" testNameOwnerCodeVectors
it "rejects an owner code with a wrong character" testNameOwnerCodeChecksum
it "reads the webapp's prepaid codes: length, canonical form, display and hash" testNamePrepaidCodeVectors
it "reads a taken order's code derived by the webapp, and refuses its lengths outside 6 to 63" testTakenOrderCodeVectors
it "reads the webapp's prepaid codes: length, canonical form, display and hash, and refuses lengths outside 6 to 63" testNamePrepaidCodeVectors
it "never reads an owner code as prepaid, nor the reverse" testNameCodeKinds
it "reads random codes of both kinds back, as typed" testNameCodeRoundTrip
it "refuses a prepaid code with one character changed or two adjacent swapped, except 0 and Z" testNamePrepaidCodeCheck
@@ -374,6 +373,9 @@ testNamePrepaidCodeVectors = do
convertToBase Base64URLUnpadded (namePrepaidCodeHash code) `shouldBe` (hash :: ByteString)
-- the webapp's typo vector
parseNameCode "SN7-4K2P7-TQ9M1-ZX3RB-8HJ5X" `shouldBe` Nothing
-- their check characters are valid, but no name length is outside 6 to 63
parseNameCode "SN5-N0N9J-5D292-HPSYA-R911Q" `shouldBe` Nothing
parseNameCode "SN64-N0N9J-5D292-HPSYA-R911C" `shouldBe` Nothing
-- typed code, length, canonical form, display and the webapp's codeHash
namePrepaidCodeVectors :: [(T.Text, Int, T.Text, T.Text, ByteString)]
@@ -383,25 +385,6 @@ namePrepaidCodeVectors =
("SN8-25X6F-6JDNM-8C59E-HDHJD", 8, "SN825X6F6JDNM8C59EHDHJD", "SN8-25X6F-6JDNM-8C59E-HDHJD", "cOnJDSzWBEZiY2z1I-HMGW2gylyx_13V8sOpMDeJOvU")
]
-- takenOrderCode(seed of the webapp's fixture entropy, "t2Jk8RmQ4vXw9NcP3yLb6g", length), computed by the webapp's code.ts
testTakenOrderCodeVectors :: IO ()
testTakenOrderCodeVectors = do
forM_ takenOrderCodeVectors $ \(display, len, hash) -> do
Just (NCPrepaid code) <- pure $ parseNameCode display
(namePrepaidCodeLength code, formatNameCode (NCPrepaid code)) `shouldBe` (len, display)
convertToBase Base64URLUnpadded (namePrepaidCodeHash code) `shouldBe` (hash :: ByteString)
-- the webapp reads these, but no name length is outside 6 to 63
parseNameCode "SN5-N0N9J-5D292-HPSYA-R911Q" `shouldBe` Nothing
parseNameCode "SN64-N0N9J-5D292-HPSYA-R911C" `shouldBe` Nothing
takenOrderCodeVectors :: [(T.Text, Int, ByteString)]
takenOrderCodeVectors =
[ ("SN6-N0N9J-5D292-HPSYA-R911P", 6, "SKKFjAl5K21B6HRIgQcYQ3AaZ67nVxl2xszsSkZ97cA"),
("SN7-N0N9J-5D292-HPSYA-R911N", 7, "pXhWV9oSfjH0oaQ78HPSGKTg1lScZhNGi-YZnqt-GyQ"),
("SN12-N0N9J-5D292-HPSYA-R911R", 12, "FozeCQ3A_sUsZF9_OwQDp42kdHRMsoJazuGvOkTS-FQ"),
("SN63-N0N9J-5D292-HPSYA-R911D", 63, "U-Nh_Kw3-xDlQuFJffkuCAyolK5m9EfN3QuYiJ28A0A")
]
testNameCodeKinds :: IO ()
testNameCodeKinds = do
drg <- C.newRandom
@@ -137,7 +137,7 @@ badgeGroupIntegrationTests = do
it "leaves a tracker it cannot edit alone rather than publishing the code again" testUneditableTrackerLeftAlone
namesBootTests :: SpecWith TestParams
namesBootTests = it "boots with [names] and [chain], routing fixed /api/name paths ahead of a search, find ahead of registrations, registrations to GET, another name, a code and a reveal to POST, a path no route knows to 404, and code checks and orders under one limit" testNamesBoot
namesBootTests = it "boots with [names] and [chain], routing fixed /api/name paths ahead of a search, find ahead of registrations, registrations to GET, another name and a reveal to POST, a path no route knows and a taken order's code to 404, and code checks and orders under one limit" testNamesBoot
testNamesBoot :: HasCallStack => TestParams -> IO ()
testNamesBoot ps = do
@@ -165,9 +165,10 @@ testNamesBoot ps = do
answer found {method = "POST", requestBody = RequestBodyLBS "{}"} `shouldReturn` refused "bad_request"
foundByGet <- httpLbs found mgr
(statusCode $ responseStatus foundByGet, lookup "Allow" $ responseHeaders foundByGet) `shouldBe` (405, Just "POST")
forM_ ["name", "code"] $ \action -> do
owned <- request $ "/api/registration/t2Jk8RmQ4vXw9NcP3yLb6g/" <> action
(action,) <$> answer owned {method = "POST", requestBody = RequestBodyLBS "{}"} `shouldReturn` (action, refused "bad_request")
owned <- request "/api/registration/t2Jk8RmQ4vXw9NcP3yLb6g/name"
answer owned {method = "POST", requestBody = RequestBodyLBS "{}"} `shouldReturn` refused "bad_request"
coded <- request "/api/registration/t2Jk8RmQ4vXw9NcP3yLb6g/code"
answer coded {method = "POST", requestBody = RequestBodyLBS "{}"} `shouldReturn` (404, Just $ J.object ["error" J..= ("not_found" :: Text)])
revealed <- request "/api/registration/t2Jk8RmQ4vXw9NcP3yLb6g/reveal"
answer revealed {method = "POST", requestBody = RequestBodyLBS "{}"} `shouldReturn` (404, Just $ J.object ["error" J..= ("not_found" :: Text)])
revealedByGet <- httpLbs revealed mgr
+2 -47
View File
@@ -20,10 +20,6 @@ module Bots.StoreService.NamesContractsTests
contactTextCall,
channelTextCall,
noncesCall,
pricesCall,
launchPricesAnswer,
pricesAnswer,
oracle,
alice,
availableCall,
minCommitmentAgeCall,
@@ -70,9 +66,6 @@ namesContractsTests = describe "StoreService names: contracts" $ do
it "reads no records from another resolver" $ testOtherResolver g
describe "prices" $ do
it "quotes 6, 7 and 12 letters at the launch list for 2 years" $ testLaunchPrices g
it "finds the code length for each launch price" $ testLaunchCodeLengths g
it "requires every longer length to be covered when the list is not monotone" $ testNonMonotone g
it "reads the oracle from the controller on each call" $ testOracleReplaced g
describe "other reads" $ do
it "reads the sponsor's and the resolver's nonces from their own contracts" $ testNonces g
it "reads ownerOf's revert without data as nobody" $ testOwnerOfNobody g
@@ -164,32 +157,6 @@ testLaunchPrices g = withContracts g $ \node ct -> do
answerCall node fakeController (rentPriceCall 12) $ Returns $ rentPriceAnswer 10000
mapM (\len -> chainPriceCents ct len 2) [6, 7, 12] `shouldReturn` [Right 40000, Right 20000, Right 10000]
testLaunchCodeLengths :: TVar ChaChaDRG -> IO ()
testLaunchCodeLengths g = withContracts g $ \node ct -> do
answerCall node fakeController pricesCall $ Returns $ addressWord oracle
answerCall node oracle pricesCall $ Returns launchPricesAnswer
mapM (\cents -> codeLengthFor ct cents 2) [40000, 20000, 10000, 9999]
`shouldReturn` [Right (Just 6), Right (Just 7), Right (Just 8), Right Nothing]
codeLengthFor ct 20000 4 `shouldReturn` Right (Just 8)
testNonMonotone :: TVar ChaChaDRG -> IO ()
testNonMonotone g = withContracts g $ \node ct -> do
answerCall node fakeController pricesCall $ Returns $ addressWord oracle
answerCall node oracle pricesCall $ Returns $ pricesAnswer 5000 [(6, 15000), (7, 20000)]
mapM (\cents -> codeLengthFor ct cents 2) [30000, 40000] `shouldReturn` [Right (Just 8), Right (Just 6)]
answerCall node oracle pricesCall $ Returns $ pricesAnswer 5000 [(63, 30000)]
mapM (\cents -> codeLengthFor ct cents 2) [59999, 60000] `shouldReturn` [Right Nothing, Right (Just 6)]
testOracleReplaced :: TVar ChaChaDRG -> IO ()
testOracleReplaced g = withContracts g $ \node ct -> do
let newOracle = address "0x8a791620dd6260079bf849dc5567adc3f2fdc318"
answerCall node fakeController pricesCall $ Returns $ addressWord oracle
answerCall node oracle pricesCall $ Returns launchPricesAnswer
answerCall node newOracle pricesCall $ Returns $ pricesAnswer 10000 []
codeLengthFor ct 20000 2 `shouldReturn` Right (Just 7)
answerCall node fakeController pricesCall $ Returns $ addressWord newOracle
codeLengthFor ct 20000 2 `shouldReturn` Right (Just 6)
testNonces :: TVar ChaChaDRG -> IO ()
testNonces g = withContracts g $ \node ct -> do
answerCall node fakeSponsor noncesCall $ Returns $ uint256 (5 :: Int)
@@ -265,9 +232,6 @@ testMalformed g = withContracts g $ \node ct -> do
nameExpires ct Latest alice `shouldReturn` Left (ChainUnavailable "eth_call: nameExpires(uint256) answered AEShort")
answerCall node fakeSponsor noncesCall $ Returns $ uint256 (toInteger (maxBound :: Word64) + 1)
sponsorNonce ct owner `shouldReturn` Left (ChainUnavailable "eth_call: nonces(address) answered AERange")
answerCall node fakeController pricesCall $ Returns $ addressWord oracle
answerCall node oracle pricesCall $ Returns $ B.dropEnd 32 launchPricesAnswer
codeLengthFor ct 40000 2 `shouldReturn` Left (ChainUnavailable "eth_call: prices() answered AEShort")
testStatusDeadline :: TVar ChaChaDRG -> IO ()
testStatusDeadline g = withContracts g $ \node ct -> do
@@ -347,13 +311,6 @@ resolverOf = cResolver . fakeChainConfig
rentPriceAnswer :: Integer -> ByteString
rentPriceAnswer cents = uint256 (cents * 10 ^ (16 :: Int)) <> uint256 (0 :: Int)
pricesAnswer :: Integer -> [(Integer, Integer)] -> ByteString
pricesAnswer base exceptions = abiTuple [Static $ uint256 base, Dynamic $ uint256 (length exceptions) <> B.concat [uint256 len <> uint256 cents | (len, cents) <- exceptions]]
-- viem: prices() answering the launch list, base 5000 and [{7, 10000}, {6, 20000}]
launchPricesAnswer :: ByteString
launchPricesAnswer = unhex "0000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000004e20"
-- viem: text answering "https://smp8.simplex.im/a#Y8m3"
contactTextAnswer :: ByteString
contactTextAnswer = unhex "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e68747470733a2f2f736d70382e73696d706c65782e696d2f612359386d330000"
@@ -376,10 +333,9 @@ aliceNode = unhex "0f4104919a21c22f566d9c7947c7c7d8addbf546c973851a7e449276ea54e
commitment :: ByteString
commitment = unhex "f8096c3f3cfbadc9f3a108d2c586ccc816c0510711c8d6228aa4fa507324d30c"
owner, registry, oracle, otherResolver :: Address
owner, registry, otherResolver :: Address
owner = address "0x014f9c09cceedef1b713110d04d3a07d5b5d9b73"
registry = address "0x5fbdb2315678afecb367f032d93f642f64180aa3"
oracle = address "0xa513e6e4b8f2a923d98304ec87f64353c4d5c853"
otherResolver = address "0x4826533b4897376654bb4d4ad88b7fafd0c98528"
-- viem's calldata for the reads
@@ -407,8 +363,7 @@ rentPriceCall = \case
12 -> unhex "9ff7ec0900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000003c26700000000000000000000000000000000000000000000000000000000000000000c6161616161616161616161610000000000000000000000000000000000000000"
len -> error $ "no viem vector for length " <> show len
pricesCall, commitmentsCall, availableCall, minCommitmentAgeCall, maxCommitmentAgeCall :: ByteString
pricesCall = unhex "d3419bf3"
commitmentsCall, availableCall, minCommitmentAgeCall, maxCommitmentAgeCall :: ByteString
commitmentsCall = unhex "839df945f8096c3f3cfbadc9f3a108d2c586ccc816c0510711c8d6228aa4fa507324d30c"
availableCall = unhex "aeb8ce9b0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b616c69636562616b657279000000000000000000000000000000000000000000"
minCommitmentAgeCall = unhex "8d839ffe"
+19 -29
View File
@@ -34,8 +34,8 @@ where
import Bots.StoreService.FakeBTCPay (FakeBTCPay (..), setInvoiceState, withFakeBTCPay)
import Bots.StoreService.FakeEthNode
import Bots.StoreService.NamesContractsTests (alice, answerRegistered, answerRegistrar, launchPricesAnswer, nameExpiresCall, noncesCall, oracle, pricesCall, registrarOf, rentPriceAnswer, rentPriceCall, reservedNamesCall, unhex)
import Bots.StoreService.NamesStoreTests (issue, order, ownerA, ownerB, placeCode, placePaid, t0, usable)
import Bots.StoreService.NamesContractsTests (alice, answerRegistered, answerRegistrar, nameExpiresCall, noncesCall, registrarOf, rentPriceAnswer, rentPriceCall, reservedNamesCall, unhex)
import Bots.StoreService.NamesStoreTests (order, ownerA, ownerB, placeCode, placePaid, t0)
import Bots.StoreService.WebTests
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (async, concurrently, wait)
@@ -78,7 +78,7 @@ import Simplex.Messaging.Eth.EIP712 (serializeSignature)
import Simplex.Messaging.SimplexName (LabelHash (..), SimplexTLD (..), labelHash)
import Simplex.Messaging.Util (safeDecodeUtf8, tshow)
import StoreService.Config (ChainConfig (..), NamesConfig (..), ServiceConfig (..))
import StoreService.Names.Abi (addressWord, hex, uint256)
import StoreService.Names.Abi (hex, uint256)
import StoreService.Names.Chain (newChainClient)
import StoreService.Names.Contracts (newContracts)
import StoreService.Names.Codes (CodeRefusal (..))
@@ -168,8 +168,8 @@ namesOrdersTests = describe "StoreService names: orders" $ do
it "agrees between the sweep and the overdue close on which invoices hold money" testFundedAgreement
describe "registration view" $ do
it "has the contract's fields" testViewFields
it "reads the intent nonce in committed and the code length in taken and code_issued" testViewReads
it "leaves a failed intentNonce or codeLength read null and named in unread" testViewUnread
it "reads the intent nonce in committed and nothing in taken" testViewReads
it "leaves a failed intentNonce read null and named in unread, reading nothing in taken" testViewUnread
-- fixtures
@@ -1344,11 +1344,11 @@ testViewFields :: IO ()
testViewFields = withServiceStore $ \st -> withFakeEthNode $ \node -> do
Right () <- placePaid st "r1" ownerA (order "alicebakery" 1) (addUTCTime 3600 t0)
r <- withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId "r1")
let v = objectOf $ viewJSON (testNamesConfig node) r ViewReads {vrIntentNonce = Nothing, vrCodeLength = Nothing, vrUnread = []}
let v = objectOf $ viewJSON (testNamesConfig node) r ViewReads {vrIntentNonce = Nothing, vrUnread = []}
sort (KM.keys v)
`shouldBe` sort
[ "id", "version", "moves", "linksRefused", "phase", "labelHash", "label", "owner", "commitment", "sealed", "resolver", "sponsor", "chainId", "tld",
"years", "duration", "price", "codeLength", "commitTx", "registerTx", "revealAfter", "revealBefore", "intentNonce", "unread", "expiresAt", "error"
"years", "duration", "price", "commitTx", "registerTx", "revealAfter", "revealBefore", "intentNonce", "unread", "expiresAt", "error"
]
map (`field` v) ["id", "phase", "owner", "tld", "chainId", "linksRefused", "label", "unread", "sponsor"]
`shouldBe` map
@@ -1369,26 +1369,16 @@ testViewReads = withServiceStore $ \st -> withFakeEthNode $ \node -> do
g <- C.newRandom
ct <- newContracts TLDSimplex =<< newChainClient g (fakeChainConfig node)
answerCall node fakeSponsor noncesCall $ Returns $ uint256 (4 :: Int)
answerCall node fakeController pricesCall $ Returns $ addressWord oracle
answerCall node oracle pricesCall $ Returns launchPricesAnswer
Right () <- placePaid st "paid" ownerA (order "alicebakery" 1) (addUTCTime 3600 t0)
code <- issue st 7 usable
Right () <- placeCode st "code" ownerB (order "alicebakery" 2) code
let inPhase rid phase = do
r <- withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId rid)
let inPhase phase = do
r <- withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId "paid")
Written _ <- withTransaction st $ \db -> updateRegistration db r {nrPhase = phase} t0
withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId rid)
committed <- inPhase "paid" PCommitted
readViewChain st ct committed `shouldReturn` ViewReads (Just 4) Nothing []
taken <- inPhase "paid" PTaken
-- worth 20000 cents for 2 years at the launch list: 7 letters and longer
readViewChain st ct taken `shouldReturn` ViewReads Nothing (Just 7) []
takenCode <- inPhase "code" PTaken
readViewChain st ct takenCode `shouldReturn` ViewReads Nothing Nothing []
issued <- issue st 8 usable {npcMinLength = 9}
Written _ <- withTransaction st $ \db -> updateRegistration db taken {nrPhase = PCodeIssued, nrIssuedCodeId = Just (pcId issued)} t0
codeIssued <- withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId "paid")
readViewChain st ct codeIssued `shouldReturn` ViewReads Nothing (Just 9) []
withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId "paid")
committed <- inPhase PCommitted
readViewChain ct committed `shouldReturn` ViewReads (Just 4) []
-- the oracle's prices are not answered, so any read in taken would be named in unread
taken <- inPhase PTaken
readViewChain ct taken `shouldReturn` ViewReads Nothing []
testViewUnread :: IO ()
testViewUnread = withServiceStore $ \st -> withFakeEthNode $ \node -> do
@@ -1397,7 +1387,7 @@ testViewUnread = withServiceStore $ \st -> withFakeEthNode $ \node -> do
setFault node "eth_call" HttpFailure
Right () <- placePaid st "paid" ownerA (order "alicebakery" 1) (addUTCTime 3600 t0)
r <- withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId "paid")
readViewChain st ct r {nrPhase = PCommitted} `shouldReturn` ViewReads Nothing Nothing ["intentNonce"]
readViewChain st ct r {nrPhase = PTaken} `shouldReturn` ViewReads Nothing Nothing ["codeLength"]
let v = objectOf $ viewJSON (testNamesConfig node) r {nrPhase = PTaken} (ViewReads Nothing Nothing ["codeLength"])
(field "codeLength" v, field "unread" v) `shouldBe` (Just J.Null, Just (J.toJSON ["codeLength" :: Text]))
readViewChain ct r {nrPhase = PCommitted} `shouldReturn` ViewReads Nothing ["intentNonce"]
readViewChain ct r {nrPhase = PTaken} `shouldReturn` ViewReads Nothing []
let v = objectOf $ viewJSON (testNamesConfig node) r {nrPhase = PCommitted} (ViewReads Nothing ["intentNonce"])
(field "intentNonce" v, field "unread" v) `shouldBe` (Just J.Null, Just (J.toJSON ["intentNonce" :: Text]))
+5 -159
View File
@@ -26,7 +26,7 @@ module Bots.StoreService.NamesOwnerTests
where
import Bots.StoreService.FakeEthNode
import Bots.StoreService.NamesContractsTests (launchPricesAnswer, oracle, pricesAnswer, pricesCall, registrarOf, rentPriceAnswer, rentPriceCall, unhex)
import Bots.StoreService.NamesContractsTests (registrarOf, unhex)
import Bots.StoreService.NamesOrdersTests
import Bots.StoreService.NamesStoreTests (order, ownerA, t0)
import Bots.StoreService.WebTests (capturingStderr, digestFixture, jsonObject, statusOf, webGet)
@@ -40,8 +40,6 @@ import Control.Monad (forM, forM_, void)
import qualified Data.Aeson as J
import qualified Data.Aeson.KeyMap as KM
import Data.ByteString (ByteString)
import qualified Data.ByteString.Base64.URL as B64U
import qualified Data.ByteString.Char8 as BC
import Data.IORef (readIORef)
import Data.Foldable (toList)
import Data.List (find, sort, sortOn)
@@ -53,28 +51,26 @@ import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
import Data.Word (Word64, Word8)
import Network.HTTP.Client (defaultManagerSettings, newManager)
import qualified Network.Wai.Handler.Warp as Warp
import Simplex.Chat.Badges.Types (BadgeCodePaymentStatus (..))
import Simplex.Chat.PaymentService.Types (InvoiceId (..), InvoiceStatus (..))
import Simplex.Messaging.Agent.Store.Common (withTransaction)
import qualified Simplex.Messaging.Agent.Store.DB as DB
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.Secp256k1 (Secp256k1PrivateKey, signRecoverable)
import Simplex.Messaging.Eth.Address (Address)
import Simplex.Messaging.Eth.EIP712 (serializeSignature)
import Simplex.Messaging.SimplexName (LabelHash (..), labelHash)
import Simplex.Messaging.Util (safeDecodeUtf8, tshow)
import Simplex.Messaging.Util (tshow)
import StoreService.Config (NamesConfig (..))
import StoreService.Names.Abi (addressWord, hex, uint256)
import StoreService.Names.Chain (TxHash (..))
import StoreService.Names.Orders (OrdersEnv (..), signedRequestSeconds)
import StoreService.Names.Phases (RegistrationId (..), phasesWaiting)
import StoreService.Names.Signatures (OrderFields (..), findMessage, parseAnyCaseAddress, personalDigest, retargetMessage, takenCodeMessage)
import StoreService.Names.Signatures (OrderFields (..), findMessage, parseAnyCaseAddress, personalDigest, retargetMessage)
import StoreService.Names.Store hiding (findLimit)
import StoreService.Names.Turns (withLabelTurn)
import StoreService.Store (RevokeResult (..))
import StoreService.Web.Http (createLimit, findLimit, holdMicros, readLimit)
import StoreService.Web.Names (Route (..), namesApp)
import StoreService.Web.Names.Owner (findHandler, retargetHandler, takenCodeHandler)
import StoreService.Web.Names.Owner (findHandler, retargetHandler)
import StoreService.Web.Names.Registration (registrationHandler)
import StoreService.Web.Server (WebEnv (..), webApp)
import System.Timeout (timeout)
@@ -102,16 +98,8 @@ namesOwnerTests = describe "StoreService names: owner" $ do
it "refuses another signer's signature and a stale timestamp, and answers an unknown order 404" testMoveRefusedSigner
it "answers registration_changed with the current view when the order changed during the checks, writing nothing" testMoveLostRace
it "moves A to B while another order moves B to A, in the turns' hash order, without deadlock" testMovesCross
describe "a code instead" $ do
it "issues a code for a paid taken order, its minLength 6, 7 and 8 for 6, 7 and 12 letters at the launch prices, kept in the view" testTakenCode
it "answers an identical repeat 200 whatever its codeLength and timestamp, even after a price change" testTakenCodeRepeat
it "answers code_length_changed with the current length after a price change, issuing nothing" testCodeLengthChanged
it "answers code_unavailable when no length qualifies, and 503 provider_unavailable when the prices cannot be read" testCodeUnavailable
it "refuses a code for an order that is not a paid taken one, another signer, a stale timestamp and a malformed hash" testTakenCodeRefused
it "answers code_conflict for a hash another code has, changing nothing" testCodeConflict
it "answers registration_changed with the current view when the order changed during the length read, issuing nothing" testCodeLostRace
describe "polls" $
it "wakes a held registration poll by another name and by a code" testPollsWoken
it "wakes a held registration poll by another name" testPollsWoken
-- fixtures
@@ -135,7 +123,6 @@ ownerRoutes orders = \case
["api", "registration", "find"] -> Just Route {rMethod = "POST", rLimit = findLimit, rHandler = findHandler orders}
["api", "registration", rid] -> Just Route {rMethod = "GET", rLimit = readLimit, rHandler = registrationHandler orders holdMicros rid}
["api", "registration", rid, "name"] -> Just Route {rMethod = "POST", rLimit = createLimit, rHandler = retargetHandler orders rid}
["api", "registration", rid, "code"] -> Just Route {rMethod = "POST", rLimit = createLimit, rHandler = takenCodeHandler orders rid}
_ -> Nothing
signWith :: Ctx -> Secp256k1PrivateKey -> ByteString -> IO Text
@@ -196,12 +183,6 @@ answerReserved Ctx {cNode} label reason = do
where
LabelHash lh = labelHash label
-- | The oracle's price list, as codeLengthFor reads it.
answerPrices :: Ctx -> ByteString -> IO ()
answerPrices Ctx {cNode} prices = do
answerCall cNode fakeController pricesCall $ Returns $ addressWord oracle
answerCall cNode oracle pricesCall $ Returns prices
-- | A paid order, settled, then written as the worker would write it.
paidIn :: Ctx -> Order -> (NameRegistration -> NameRegistration) -> IO NameRegistration
paidIn ctx o change = do
@@ -294,23 +275,6 @@ findBody ctx key owner timestamp = do
postFind :: Ctx -> J.Object -> IO (Int, Maybe J.Object)
postFind ctx = post ctx "/api/registration/find" . J.encode
-- | A code hash as the page sends it: unpadded base64url of a SHA-256.
takenCodeHash :: Int -> (Text, ByteString)
takenCodeHash n = (T.filter (/= '=') $ safeDecodeUtf8 $ B64U.encode h, h)
where
h = C.sha256Hash $ "taken code " <> BC.pack (show n)
codeBodyAt :: Ctx -> Secp256k1PrivateKey -> RegistrationId -> Text -> Int -> Word64 -> IO J.Object
codeBodyAt ctx key rid codeHash codeLength timestamp = do
sig <- signWith ctx key $ takenCodeMessage rid codeHash codeLength timestamp
pure $ KM.fromList [("codeHash", J.String codeHash), ("codeLength", J.toJSON codeLength), ("timestamp", J.toJSON timestamp), ("signature", J.String sig)]
postCode :: Ctx -> RegistrationId -> J.Object -> IO (Int, Maybe J.Object)
postCode ctx rid = post ctx (registrationPath rid <> "/code") . J.encode
takeCode :: Ctx -> Secp256k1PrivateKey -> NameRegistration -> Text -> Int -> IO (Int, Maybe J.Object)
takeCode ctx key NameRegistration {nrId} codeHash codeLength = clockSeconds ctx >>= codeBodyAt ctx key nrId codeHash codeLength >>= postCode ctx nrId
currentFields :: NameRegistration -> OrderFields
currentFields NameRegistration {nrLabelHash, nrLength, nrDuration, nrCommitment, nrSealed} =
OrderFields {ofLabelHash = nrLabelHash, ofLength = nrLength, ofDuration = nrDuration, ofCommitment = nrCommitment, ofSealed = nrSealed}
@@ -318,9 +282,6 @@ currentFields NameRegistration {nrLabelHash, nrLength, nrDuration, nrCommitment,
fieldsIn :: J.Object -> [(Text, J.Value)] -> Expectation
fieldsIn v = mapM_ (\(k, value) -> (k, field k v) `shouldBe` (k, Just value))
answered :: Maybe J.Object
answered = Just KM.empty
refusedWithView :: Text -> J.Object -> (Int, Maybe J.Object)
refusedWithView tag view = (409, Just $ KM.fromList [("error", J.String tag), ("registration", J.Object view)])
@@ -329,9 +290,6 @@ labelHashText label = J.String $ hex lh
where
LabelHash lh = labelHash label
issuedCode :: Ctx -> NameRegistration -> IO PrepaidCode
issuedCode Ctx {cStore} NameRegistration {nrIssuedCodeId} = withTransaction cStore $ \db -> fromJust <$> getPrepaidCodeById db (fromJust nrIssuedCodeId)
codeRow :: Ctx -> PrepaidCode -> IO PrepaidCode
codeRow Ctx {cStore} PrepaidCode {pcId} = withTransaction cStore $ \db -> fromJust <$> getPrepaidCodeById db pcId
@@ -728,113 +686,6 @@ testMovesCross = withOwnerAs (\cfg -> cfg {nMaxOrderMoves = 100}) $ \ctx -> do
where
hashOf label = (\(LabelHash h) -> h) (labelHash label)
testTakenCode :: IO ()
testTakenCode = withOwner $ \ctx@Ctx {cNode} -> do
answerPrices ctx launchPricesAnswer
answerCall cNode fakeController (rentPriceCall 6) $ Returns $ rentPriceAnswer 40000
answerCall cNode fakeController (rentPriceCall 12) $ Returns $ rentPriceAnswer 10000
forM_ (zip [1 :: Int ..] [("bakery", 40000, 6), ("dynamis", 20000, 7), ("alicebakery2", 10000, 8)]) $ \(n, (label, price, len)) -> do
answerFree ctx label
r <- paidIn ctx aliceOrder {oLabel = label, oPrice = price, oCommitment = commitmentOf (70 + n)} takenPhase
field "codeLength" <$> getView ctx r `shouldReturn` Just (J.toJSON len)
let (hashText, hash) = takenCodeHash n
takeCode ctx keyA r hashText len `shouldReturn` (200, answered)
written <- stored ctx r
(nrPhase written, nrLive written, nrVersion written) `shouldBe` (PCodeIssued, False, nrVersion r + 1)
PrepaidCode {pcCodeHash, pcMinLength, pcYears, pcPaymentStatus, pcReservedBy, pcSpentAt, pcRevokedAt, pcExpiresAt} <- issuedCode ctx written
(pcCodeHash, pcMinLength, pcYears, pcPaymentStatus) `shouldBe` (hash, len, 2, CPSPaid)
(pcReservedBy, pcSpentAt, pcRevokedAt, pcExpiresAt) `shouldBe` (Nothing, Nothing, Nothing, Nothing)
v <- getView ctx r
(field "phase" v, field "codeLength" v) `shouldBe` (Just "code_issued", Just (J.toJSON len))
-- | 12 letters paid 10000 cents: with these prices 8 letters and longer cost that or less.
takenTwelve :: Ctx -> IO NameRegistration
takenTwelve ctx@Ctx {cNode} = do
answerPrices ctx launchPricesAnswer
answerCall cNode fakeController (rentPriceCall 12) $ Returns $ rentPriceAnswer 10000
answerFree ctx "alicebakery2"
paidIn ctx aliceOrder {oLabel = "alicebakery2", oPrice = 10000} takenPhase
testTakenCodeRepeat :: IO ()
testTakenCodeRepeat = withOwner $ \ctx -> do
r <- takenTwelve ctx
let (hashText, _) = takenCodeHash 1
body <- clockSeconds ctx >>= codeBodyAt ctx keyA (nrId r) hashText 8
postCode ctx (nrId r) body `shouldReturn` (200, answered)
issued <- stored ctx r
-- 9 letters and longer now cost 10000 cents, so a fresh computation would answer 9
answerPrices ctx $ pricesAnswer 5000 [(8, 6000), (7, 10000), (6, 20000)]
advance ctx 3600
postCode ctx (nrId r) body `shouldReturn` (200, answered)
takeCode ctx keyA r hashText 9 `shouldReturn` (200, answered)
stored ctx r `shouldReturn` issued
field "codeLength" <$> getView ctx r `shouldReturn` Just (J.Number 8)
takeCode ctx keyA r (fst $ takenCodeHash 2) 9 `shouldReturn` refusedWith 400 "bad_request"
testCodeLengthChanged :: IO ()
testCodeLengthChanged = withOwner $ \ctx@Ctx {cStore} -> do
r <- takenTwelve ctx
field "codeLength" <$> getView ctx r `shouldReturn` Just (J.Number 8)
answerPrices ctx $ pricesAnswer 5000 [(8, 6000), (7, 10000), (6, 20000)]
let (hashText, hash) = takenCodeHash 1
takeCode ctx keyA r hashText 8 `shouldReturn` (409, Just $ KM.fromList [("error", "code_length_changed"), ("codeLength", J.Number 9)])
stored ctx r `shouldReturn` r
withTransaction cStore (`getPrepaidCode` hash) `shouldReturn` Nothing
takeCode ctx keyA r hashText 9 `shouldReturn` (200, answered)
pcMinLength <$> (issuedCode ctx =<< stored ctx r) `shouldReturn` 9
testCodeUnavailable :: IO ()
testCodeUnavailable = withOwner $ \ctx@Ctx {cNode} -> do
r <- takenTwelve ctx
answerPrices ctx $ pricesAnswer 6000 []
let (hashText, _) = takenCodeHash 1
takeCode ctx keyA r hashText 8 `shouldReturn` refusedWith 409 "code_unavailable"
field "codeLength" <$> getView ctx r `shouldReturn` Just J.Null
setFault cNode "eth_call" HttpFailure
takeCode ctx keyA r hashText 8 `shouldReturn` refusedWith 503 "provider_unavailable"
stored ctx r `shouldReturn` r
testTakenCodeRefused :: IO ()
testTakenCodeRefused = withOwner $ \ctx -> do
r <- takenTwelve ctx
let (hashText, _) = takenCodeHash 1
takeCode ctx keyB r hashText 8 `shouldReturn` refusedWith 400 "bad_request"
ts <- clockSeconds ctx
(codeBodyAt ctx keyA (nrId r) hashText 8 (ts - signedWindow - 1) >>= postCode ctx (nrId r)) `shouldReturn` refusedWith 400 "bad_request"
body <- codeBodyAt ctx keyA (nrId r) hashText 8 ts
forM_ [KM.insert "codeHash" (J.String $ T.drop 1 hashText) body, KM.insert "codeLength" "8" body, KM.delete "timestamp" body] $ \malformed ->
postCode ctx (nrId r) malformed `shouldReturn` refusedWith 400 "bad_request"
stored ctx r `shouldReturn` r
(codeOrder, _) <- codeIn ctx 8 aliceOrder {oCommitment = commitmentOf 5} takenPhase
committedOrder <- paidIn ctx dynamisOrder committedPhase
forM_ [codeOrder, committedOrder] $ \o -> do
takeCode ctx keyA o hashText 8 `shouldReturn` refusedWith 400 "bad_request"
stored ctx o `shouldReturn` o
let unknown = RegistrationId "t2Jk8RmQ4vXw9NcP3yLb6g"
(codeBodyAt ctx keyA unknown hashText 8 ts >>= postCode ctx unknown) `shouldReturn` refusedWith 404 "not_found"
testCodeConflict :: IO ()
testCodeConflict = withOwner $ \ctx@Ctx {cStore} -> do
r <- takenTwelve ctx
let (hashText, hash) = takenCodeHash 1
Right _ <- withTransaction cStore $ \db -> insertPrepaidCode db NewPrepaidCode {npcCodeHash = hash, npcMinLength = 8, npcYears = 2, npcPaymentStatus = CPSPaid, npcExpiresAt = Nothing} orderTime
takeCode ctx keyA r hashText 8 `shouldReturn` refusedWith 409 "code_conflict"
stored ctx r `shouldReturn` r
testCodeLostRace :: IO ()
testCodeLostRace = withOwner $ \ctx@Ctx {cNode, cStore} -> do
r <- takenTwelve ctx
let (hashText, hash) = takenCodeHash 1
-- held at the length read, inside the turn
(issuing, gate) <- pausedAt cNode "eth_call" $ takeCode ctx keyA r hashText 8
changed <- rewrite ctx r id
releaseGate gate
answer <- within "the code" $ wait issuing
view <- getView ctx changed
answer `shouldBe` refusedWithView "registration_changed" view
stored ctx r `shouldReturn` changed
withTransaction cStore (`getPrepaidCode` hash) `shouldReturn` Nothing
testPollsWoken :: IO ()
testPollsWoken = withOwner $ \ctx -> do
answerFree ctx "alicebakers"
@@ -844,8 +695,3 @@ testPollsWoken = withOwner $ \ctx -> do
void $ moved ctx keyA r to
(status, v) <- woken poll
(status, field "version" v, field "phase" v) `shouldBe` (200, Just (J.toJSON $ nrVersion r + 1), Just "queued")
t <- takenTwelve ctx
poll' <- held ctx t
takeCode ctx keyA t (fst $ takenCodeHash 1) 8 `shouldReturn` (200, answered)
(status', v') <- woken poll'
(status', field "version" v', field "phase" v') `shouldBe` (200, Just (J.toJSON $ nrVersion t + 1), Just "code_issued")
@@ -54,12 +54,6 @@ namesSignatureTests = describe "StoreService names: signatures" $ do
"simplex.domains name t2Jk8RmQ4vXw9NcP3yLb6g 0x5bfbbf75311ec002834031d73fa30bfe8bdf675f4a3df72443841490c4b49d02 7 69259500 0x9cf04ddef2eedfb02e6e7ba58f879c0636c584b6acc67ffe9f97a06bfb016ac1 0xb1971beb567c8dc1b2f01f0d823754c6168d8d6830db2221702a7678e1804674 1792574100"
"0x8bc79aa98fbd53013e8961db27554ecd17139e14ec168b86afbaf2fc910bfc52"
"0xe3d680bcc62255179d5260beeb920319c86bd919f6b01927bee01509cf6c1fe3121151947fb9e3a310e6652c0c9a9bf7ceb48e8d903774a38a3162d916e8b4591b"
it "signs a taken order's code, with its length" $
testMessage
(takenCodeMessage registrationId "7hg6ED6EotRVm21uS1KOZ-CKEzkJK5hvG4W5atfcn_Q" 7 timestamp)
"simplex.domains code t2Jk8RmQ4vXw9NcP3yLb6g 7hg6ED6EotRVm21uS1KOZ-CKEzkJK5hvG4W5atfcn_Q 7 1792574100"
"0x1f61297cd585c7c94218f8f87f30ea20e9b0631d36890460f24ee9bca83a2a19"
"0x897173292eb63ac3587d3c900af08dee71f296af8718ec045dd49ce953bb0f666eefe546bea0d3623022c7cc7f03cc572a498bb0ceac26bb46ed37c9e99ecdb51b"
describe "signature format" $ do
it "refuses a high s, though recovery accepts it" testHighS
it "refuses v other than 27 or 28" $
@@ -93,9 +87,6 @@ namesSignatureTests = describe "StoreService names: signatures" $ do
decodeString retString `shouldBe` Right "https://smp8.simplex.im/a#contact"
decodeString (hx "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000") `shouldBe` Right ""
decodeBytes (hx "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000010410f13a8c833cc73a7eae1ddee67ec8845ec3d39799406c7bc87fab7c504a7e0ab5cb89bb000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000f73696d706c65782e636f6e746163740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002168747470733a2f2f736d70382e73696d706c65782e696d2f6123636f6e746163740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") `shouldBe` Right (hx "10f13a8c833cc73a7eae1ddee67ec8845ec3d39799406c7bc87fab7c504a7e0ab5cb89bb000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000f73696d706c65782e636f6e746163740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002168747470733a2f2f736d70382e73696d706c65782e696d2f6123636f6e7461637400000000000000000000000000000000000000000000000000000000000000")
it "decodes the oracle's prices" $ do
decodePrices retPrices `shouldBe` Right (5000, [(7, 10000), (6, 20000)])
decodePrices (hx "000000000000000000000000000000000000000000000000000000000000138800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000") `shouldBe` Right (5000, [])
it "refuses data that does not hold the type" $ do
decodeUint256 (B.replicate 31 0) `shouldBe` Left AEShort
decodeAddress (B.singleton 1 <> B.drop 1 (hx "000000000000000000000000014f9c09cceedef1b713110d04d3a07d5b5d9b73")) `shouldBe` Left AENotAddress
@@ -103,7 +94,6 @@ namesSignatureTests = describe "StoreService names: signatures" $ do
decodeString (uint256 (4096 :: Int) <> B.drop 32 retString) `shouldBe` Left AEOffset
decodeString (B.take 80 retString) `shouldBe` Left AEShort
decodeString (abiTuple [Dynamic $ abiBytes "\xff"]) `shouldBe` Left AENotUtf8
decodePrices (B.dropEnd 32 retPrices) `shouldBe` Left AEShort
testMessage :: ByteString -> ByteString -> Text -> Text -> IO ()
testMessage msg expected digest signature = do
@@ -169,9 +159,6 @@ orderSignature = "0x73e32be461e99e64b093a4ad09b04b4d810e5623a1b0e9c9fdb4701ec2a6
retString :: ByteString
retString = hx "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002168747470733a2f2f736d70382e73696d706c65782e696d2f6123636f6e7461637400000000000000000000000000000000000000000000000000000000000000"
retPrices :: ByteString
retPrices = hx "0000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000004e20"
owner :: Address
owner = either error id $ strDecode $ encodeUtf8 ownerText
+1 -5
View File
@@ -252,7 +252,6 @@ testCreatePaid = withServiceStore $ \st -> do
nrRevealAfter = Nothing,
nrRevealBefore = Nothing,
nrExpiresAt = Nothing,
nrIssuedCodeId = Nothing,
nrRegisteredAt = Nothing,
nrFailedAt = Nothing,
nrError = Nothing,
@@ -386,7 +385,6 @@ testVersionBumps = withServiceStore $ \st -> do
testRoundTrip :: IO ()
testRoundTrip = withServiceStore $ \st -> do
code <- issue st 1 usable
issued <- issue st 2 usable
placeCode st "r1" ownerA (order "alicebakery" 1) code `shouldReturn` Right ()
r <- reg st "r1"
let changed =
@@ -400,7 +398,6 @@ testRoundTrip = withServiceStore $ \st -> do
nrRevealAfter = Just 1792828800,
nrRevealBefore = Just 1792915140,
nrExpiresAt = Just 1861833600,
nrIssuedCodeId = Just (pcId issued),
nrRegisteredAt = Just (at 3),
nrFailedAt = Just (at 2),
nrError = Just "The name was not signed in time",
@@ -425,7 +422,6 @@ testLiveFollowsPhase = withServiceStore $ \st -> do
step "r1" PTaken True
step "r1" PFailed True
step "r1" PTaken True
step "r1" PCodeIssued False
step "r2" PRegistering True
step "r2" PTaken False
placePaid st "r3" ownerA (order "carolsbakery" 3) (at 60) `shouldReturn` Right ()
@@ -440,7 +436,7 @@ testOrderLive = withServiceStore $ \st -> do
let code = paid {nrPayment = RPCode 1}
liveIn r phase = (phase, orderLive r {nrPhase = phase})
map (liveIn paid) [minBound .. maxBound]
`shouldBe` [(PUnpaid, True), (PQueued, True), (PCommitting, True), (PCommitted, True), (PRegistering, True), (PRegistered, False), (PTaken, True), (PFailed, True), (PCodeIssued, False)]
`shouldBe` [(PUnpaid, True), (PQueued, True), (PCommitting, True), (PCommitted, True), (PRegistering, True), (PRegistered, False), (PTaken, True), (PFailed, True)]
map (liveIn code) [PTaken, PFailed, PRegistered] `shouldBe` [(PTaken, False), (PFailed, True), (PRegistered, False)]
orderLive paid {nrLive = False} `shouldBe` False
orderLive paid {nrLive = False, nrPhase = PQueued} `shouldBe` True