mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-27 07:58:55 -07:00
[econ] bypass purchase offers for contributors
This commit is contained in:
@@ -4500,7 +4500,9 @@ const app = new Hono<App>({ strict: false })
|
|||||||
'drew it is refused rather than sold at numbers the player did not see.',
|
'drew it is refused rather than sold at numbers the player did not see.',
|
||||||
'',
|
'',
|
||||||
'The tokens are paid to the room’s OWNER (its `CreatorAccountId`), as a room-key sale',
|
'The tokens are paid to the room’s OWNER (its `CreatorAccountId`), as a room-key sale',
|
||||||
'is; an owner buying from their own shop, and a free offer, move no tokens.',
|
'is. The room’s owner and co-owners (the `canManageRoom` set) take from their own shop',
|
||||||
|
'for free: the client shows them the offer at no cost and posts `RequestedPrice=0`, so',
|
||||||
|
'their posted price is not checked and no tokens move. A free offer moves none either.',
|
||||||
'',
|
'',
|
||||||
'Answers the `{ Value, Success, Error, error_id }` envelope the room-currency writes',
|
'Answers the `{ Value, Success, Error, error_id }` envelope the room-currency writes',
|
||||||
'use. `Value` is BOTH balances the purchase moved, each a RESULTING total:',
|
'use. `Value` is BOTH balances the purchase moved, each a RESULTING total:',
|
||||||
@@ -4547,15 +4549,24 @@ const app = new Hono<App>({ strict: false })
|
|||||||
if (int(body.RequestedAmount) !== offer.CurrencyAmount) {
|
if (int(body.RequestedAmount) !== offer.CurrencyAmount) {
|
||||||
return refuse('Requested amount does not match')
|
return refuse('Requested amount does not match')
|
||||||
}
|
}
|
||||||
if (int(body.RequestedPrice) !== offer.Price) return refuse('Requested price does not match')
|
|
||||||
|
|
||||||
const ownerId = await getRoomOwnerId(c.env.DB, roomId)
|
const ownerId = await getRoomOwnerId(c.env.DB, roomId)
|
||||||
if (ownerId === null) return refuse('Purchase offer is not available')
|
if (ownerId === null) return refuse('Purchase offer is not available')
|
||||||
|
|
||||||
// The owner paying themselves would be a debit and a credit of the same tokens.
|
// The room's owner and co-owners take from their own shop for free: the client
|
||||||
|
// shows THEM the offer at no cost and posts `RequestedPrice=0`, so holding a
|
||||||
|
// contributor to the offer's price refused every purchase they made. Nobody is
|
||||||
|
// charged — the owner paying themselves would be a debit and a credit of the same
|
||||||
|
// tokens, and a co-owner is building the shop, not buying from it — so the posted
|
||||||
|
// price is not checked for them at all; everyone else must have seen the real one.
|
||||||
|
const canManage = (await canManageRoomById(c.env.DB, roomId, id)) === true
|
||||||
|
if (!canManage && int(body.RequestedPrice) !== offer.Price) {
|
||||||
|
return refuse('Requested price does not match')
|
||||||
|
}
|
||||||
|
|
||||||
const startingTokens = intVar(c.env.STARTING_TOKENS, DEFAULT_STARTING_TOKENS)
|
const startingTokens = intVar(c.env.STARTING_TOKENS, DEFAULT_STARTING_TOKENS)
|
||||||
const tokens = CurrencyType.RecCenterTokens
|
const tokens = CurrencyType.RecCenterTokens
|
||||||
const charged = offer.Price > 0 && ownerId !== id
|
const charged = offer.Price > 0 && !canManage
|
||||||
// Charged FIRST, and atomically: a buyer who cannot pay is handed nothing.
|
// Charged FIRST, and atomically: a buyer who cannot pay is handed nothing.
|
||||||
if (charged && !(await spendCurrency(c.env.DB, id, tokens, offer.Price, startingTokens))) {
|
if (charged && !(await spendCurrency(c.env.DB, id, tokens, offer.Price, startingTokens))) {
|
||||||
return refuse('Not enough tokens')
|
return refuse('Not enough tokens')
|
||||||
|
|||||||
@@ -1142,7 +1142,11 @@ export const AwardRoomCurrencyResultList = z.array(AwardRoomCurrencyResult)
|
|||||||
export const PurchaseRoomCurrencyRequest = z.object({
|
export const PurchaseRoomCurrencyRequest = z.object({
|
||||||
PurchaseOfferId: z.string().describe('The offer — a `CurrencyPurchaseOfferId`'),
|
PurchaseOfferId: z.string().describe('The offer — a `CurrencyPurchaseOfferId`'),
|
||||||
RequestedAmount: z.string().describe('How much room currency; must equal the offer’s'),
|
RequestedAmount: z.string().describe('How much room currency; must equal the offer’s'),
|
||||||
RequestedPrice: z.string().describe('What it costs in tokens; must equal the offer’s'),
|
RequestedPrice: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
'What it costs in tokens; must equal the offer’s, except for the room’s owner and co-owners, who are shown 0 and pay nothing'
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2302,6 +2302,24 @@ describe('econ endpoints', () => {
|
|||||||
expect([await tokens(1), await tokens(7402)]).toEqual([1555, 100])
|
expect([await tokens(1), await tokens(7402)]).toEqual([1555, 100])
|
||||||
expect(await drainFrames()).toEqual([])
|
expect(await drainFrames()).toEqual([])
|
||||||
|
|
||||||
|
// A co-owner (account 2 on room 2511) takes from the shop for free too. The client
|
||||||
|
// shows them the offer at no cost and posts `RequestedPrice=0` — which a stranger
|
||||||
|
// is refused for above — so their price is not checked: 0 and the real one both go
|
||||||
|
// through, nobody is charged, the owner is not paid, and no frame is sent.
|
||||||
|
await setTokens(2, 50)
|
||||||
|
const coOwner = (await (await purchase('2', offerId, 444, 0)).json()) as typeof gratis
|
||||||
|
expect(coOwner.Value.CurrencyBalanceResponse.Balance).toBe(444)
|
||||||
|
expect(coOwner.Value.TokenBalanceResponse.Balance).toBe(50)
|
||||||
|
const coOwnerAgain = (await (await purchase('2', offerId, 444, 555)).json()) as typeof gratis
|
||||||
|
expect(coOwnerAgain.Value.CurrencyBalanceResponse.Balance).toBe(888)
|
||||||
|
expect(coOwnerAgain.Value.TokenBalanceResponse.Balance).toBe(50)
|
||||||
|
// The amount still has to be the offer's, manager or not.
|
||||||
|
expect(await (await purchase('2', offerId, 1, 0)).json()).toEqual(
|
||||||
|
refused('Requested amount does not match')
|
||||||
|
)
|
||||||
|
expect([await tokens(1), await tokens(2)]).toEqual([1555, 50])
|
||||||
|
expect(await drainFrames()).toEqual([])
|
||||||
|
|
||||||
await env.DB.prepare('DELETE FROM room_balance WHERE currency_id IN (?1, ?2)')
|
await env.DB.prepare('DELETE FROM room_balance WHERE currency_id IN (?1, ?2)')
|
||||||
.bind(sold.CurrencyId, other.CurrencyId)
|
.bind(sold.CurrencyId, other.CurrencyId)
|
||||||
.run()
|
.run()
|
||||||
|
|||||||
Reference in New Issue
Block a user