fix(sim): cap crash point so extreme seeds cannot overflow or bankrupt
At u=1 the unsigned quotient exceeded int64 and wrapped negative, so the rarest and most valuable outcome silently became an instant 1.00x loss. At u=2 it produced a 2.1-billion-times payout the house could never cover, which would have left settlement failing and the player unpaid. The crash point is now capped at the largest multiplier the curve can express, which is unreachable anyway since the round hits its tick ceiling first. FromInt now panics outside the Q32.32 integer range instead of wrapping a positive input into a negative value. Raises coverage to 88% overall; adds a Makefile with db-reset, since the append-only ledger steadily consumes bridge headroom across test runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
58
Makefile
Normal file
58
Makefile
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
.PHONY: help test test-race test-e2e cover db-up db-reset run build lint
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "test run unit and integration tests"
|
||||||
|
@echo "test-race run tests under the race detector"
|
||||||
|
@echo "test-e2e run end-to-end tests against a live server"
|
||||||
|
@echo "cover report coverage per package"
|
||||||
|
@echo "db-up start postgres and redis"
|
||||||
|
@echo "db-reset destroy and recreate the database"
|
||||||
|
@echo "run run the server with the dev faucet enabled"
|
||||||
|
@echo "build build the server binary"
|
||||||
|
|
||||||
|
test: db-up
|
||||||
|
go test ./... -count=1
|
||||||
|
|
||||||
|
test-race: db-up
|
||||||
|
go test ./pkg/... -race -count=1
|
||||||
|
|
||||||
|
# The end-to-end suite drives a live server; start one first.
|
||||||
|
test-e2e: db-up build
|
||||||
|
@echo "start the server with 'make run' in another shell, then:"
|
||||||
|
@echo " ARCADE_E2E=http://localhost:8080 go test ./cmd/arcade/ -v"
|
||||||
|
|
||||||
|
cover: db-up
|
||||||
|
go test ./pkg/... -count=1 -coverprofile=coverage.out
|
||||||
|
go tool cover -func=coverage.out | tail -1
|
||||||
|
|
||||||
|
db-up:
|
||||||
|
@docker compose up -d postgres redis >/dev/null
|
||||||
|
@for i in $$(seq 1 40); do \
|
||||||
|
docker compose exec -T postgres pg_isready -U arcade >/dev/null 2>&1 && exit 0; \
|
||||||
|
sleep 1; \
|
||||||
|
done; echo "postgres did not become ready" >&2; exit 1
|
||||||
|
|
||||||
|
# The ledger is append-only, so tests that mint large amounts steadily consume
|
||||||
|
# the bridge's headroom. This wipes the volume and reapplies every migration.
|
||||||
|
db-reset:
|
||||||
|
docker compose down -v
|
||||||
|
docker compose up -d postgres
|
||||||
|
@for i in $$(seq 1 40); do \
|
||||||
|
docker compose exec -T postgres pg_isready -U arcade >/dev/null 2>&1 && break; \
|
||||||
|
sleep 1; \
|
||||||
|
done
|
||||||
|
@# Migrations are mounted into docker-entrypoint-initdb.d and run
|
||||||
|
@# automatically on a fresh volume, so there is nothing to apply here.
|
||||||
|
@docker compose exec -T postgres psql -q -U arcade -d arcade \
|
||||||
|
-c "SELECT count(*) AS tables FROM information_schema.tables WHERE table_schema='public'"
|
||||||
|
@echo "database reset"
|
||||||
|
|
||||||
|
run: db-up
|
||||||
|
ARCADE_DEV_FAUCET=1 go run ./cmd/arcade
|
||||||
|
|
||||||
|
build:
|
||||||
|
go build -o bin/qa ./cmd/arcade
|
||||||
|
|
||||||
|
lint:
|
||||||
|
go vet ./...
|
||||||
|
gofmt -l .
|
||||||
@@ -185,10 +185,10 @@ func TestFullRoundLifecycleAndVerification(t *testing.T) {
|
|||||||
|
|
||||||
// Wait for the round to settle and expose its proof.
|
// Wait for the round to settle and expose its proof.
|
||||||
var proof struct {
|
var proof struct {
|
||||||
Commitment string `json:"commitment"`
|
Commitment string `json:"commitment"`
|
||||||
ServerSeed string `json:"server_seed"`
|
ServerSeed string `json:"server_seed"`
|
||||||
ClientSeed string `json:"client_seed"`
|
ClientSeed string `json:"client_seed"`
|
||||||
Nonce int64 `json:"nonce"`
|
Nonce int64 `json:"nonce"`
|
||||||
Participants []string `json:"participants"`
|
Participants []string `json:"participants"`
|
||||||
}
|
}
|
||||||
settled := false
|
settled := false
|
||||||
|
|||||||
@@ -218,8 +218,8 @@ func (s *server) handleHealth(w http.ResponseWriter, r *http.Request) {
|
|||||||
status = "ledger_imbalance"
|
status = "ledger_imbalance"
|
||||||
}
|
}
|
||||||
writeJSON(w, http.StatusOK, map[string]any{
|
writeJSON(w, http.StatusOK, map[string]any{
|
||||||
"status": status,
|
"status": status,
|
||||||
"ledger_sum_msat": total,
|
"ledger_sum_msat": total,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,12 +407,12 @@ func (s *server) handleCashout(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func (s *server) handleScratchCatalog(w http.ResponseWriter, r *http.Request) {
|
func (s *server) handleScratchCatalog(w http.ResponseWriter, r *http.Request) {
|
||||||
type entry struct {
|
type entry struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Blurb string `json:"blurb"`
|
Blurb string `json:"blurb"`
|
||||||
Cells int `json:"cells"`
|
Cells int `json:"cells"`
|
||||||
RTPBP uint64 `json:"rtp_bp"`
|
RTPBP uint64 `json:"rtp_bp"`
|
||||||
Odds []scratch.OddsRow `json:"odds"`
|
Odds []scratch.OddsRow `json:"odds"`
|
||||||
}
|
}
|
||||||
out := make([]entry, 0, len(scratch.Catalog))
|
out := make([]entry, 0, len(scratch.Catalog))
|
||||||
for _, t := range scratch.Catalog {
|
for _, t := range scratch.Catalog {
|
||||||
|
|||||||
290
coverage.out
Normal file
290
coverage.out
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
mode: set
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:37.33,39.45 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:39.45,42.63 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:44.2,44.10 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:48.49,48.76 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:51.38,51.52 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:54.34,54.71 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:57.43,57.75 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:62.66,65.2 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:70.44,72.29 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:72.29,79.3 4 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:80.2,82.12 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:88.75,97.2 8 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:110.74,115.29 4 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:115.29,117.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fair/fair.go:118.2,126.3 2 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:34.25,35.30 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:35.30,36.87 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:38.2,38.25 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:42.24,42.55 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:44.23,44.39 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:45.23,45.39 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:49.23,52.11 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:52.11,54.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:55.2,55.11 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:55.11,57.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:58.2,60.9 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:60.9,62.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:63.2,63.15 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:67.23,68.12 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:68.12,69.35 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:71.2,73.11 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:73.11,75.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:76.2,76.11 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:76.11,78.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:79.2,83.9 5 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:83.9,85.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:86.2,86.15 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:91.18,92.11 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:92.11,93.35 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:95.2,95.12 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:95.12,97.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:99.2,101.26 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:101.26,103.31 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:103.31,105.9 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:107.3,107.11 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:110.2,110.28 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:110.28,112.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:113.2,113.10 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:117.28,119.9 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:119.9,121.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:122.2,126.9 5 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:126.9,128.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:129.2,129.10 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:132.27,134.17 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:134.17,136.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/fixed/fixed.go:137.2,137.10 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:41.40,46.2 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:49.69,51.16 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:51.16,53.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:54.2,55.43 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:55.43,56.67 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:59.2,66.38 5 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:71.70,73.16 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:73.16,75.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:76.2,77.53 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:77.53,79.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:81.2,84.8 4 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:84.8,86.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:87.2,90.33 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:90.33,92.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:93.2,93.42 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:93.42,95.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:96.2,96.12 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:100.39,102.33 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:102.33,103.27 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:103.27,105.4 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:110.58,112.51 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:112.51,114.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/identity/identity.go:115.2,115.34 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:25.30,25.69 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:36.40,59.24 7 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:59.24,61.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:63.2,64.20 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:64.20,66.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:67.2,67.11 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:78.37,79.15 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:79.15,81.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:85.2,85.24 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:85.24,87.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:89.2,90.48 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:95.39,96.20 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:96.20,98.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:99.2,99.26 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:99.26,101.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:102.2,108.44 4 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:108.44,110.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:111.2,111.50 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:111.50,113.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/crash.go:114.2,114.13 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/rng.go:25.33,29.25 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/rng.go:29.25,32.3 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/rng.go:33.2,34.25 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/rng.go:34.25,36.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/rng.go:39.2,39.54 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/rng.go:39.54,41.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/rng.go:42.2,42.10 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/rng.go:47.35,53.2 5 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/rng.go:56.31,67.2 10 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/rng.go:69.36,69.73 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/sim/rng.go:73.30,75.2 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:103.67,113.2 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:118.54,124.20 5 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:124.20,129.3 4 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:132.28,136.32 4 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:136.32,137.10 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:138.19,138.19 0 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:139.11,139.11 0 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:145.47,149.6 3 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:149.6,150.10 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:151.21,152.20 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:153.19,154.38 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:154.38,158.5 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:163.48,170.15 5 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:171.20,172.27 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:172.27,174.4 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:175.20,176.27 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:176.27,182.4 5 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:183.19,184.27 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:184.27,186.4 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:187.20,196.14 6 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:196.14,198.4 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:199.3,199.16 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:201.2,201.12 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:205.53,218.16 9 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:218.16,220.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:222.2,235.12 13 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:240.56,254.53 10 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:254.53,256.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:258.2,259.12 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:265.50,271.27 6 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:271.27,273.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:274.2,279.16 5 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:279.16,281.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:283.2,285.25 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:285.25,286.25 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:286.25,287.12 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:289.3,291.17 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:291.17,294.4 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:295.3,298.46 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:298.46,300.4 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:303.2,303.19 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:303.19,306.73 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:306.73,308.4 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:312.2,315.38 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:315.38,317.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:319.2,321.12 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:326.118,327.20 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:327.20,329.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:331.2,332.29 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:332.29,335.3 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:336.2,336.44 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:336.44,339.3 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:340.2,344.16 4 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:344.16,346.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:347.2,351.17 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:351.17,353.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:355.2,357.46 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:357.46,359.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:361.2,363.53 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:363.53,366.3 2 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:367.2,375.12 5 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:380.58,384.29 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:384.29,386.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:387.2,388.9 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:388.9,390.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:391.2,391.24 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:391.24,393.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:394.2,395.24 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:395.24,397.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:398.2,400.12 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:400.12,403.49 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:403.49,405.4 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:408.2,408.16 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:412.36,417.27 4 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:417.27,424.25 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:424.25,426.4 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:427.3,427.31 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:430.2,441.50 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:441.50,444.3 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/room/room.go:445.2,445.10 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:66.34,68.31 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:68.31,70.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:71.2,71.24 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:71.24,74.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:75.2,75.12 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:79.34,81.31 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:81.31,88.22 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:88.22,90.4 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:91.3,91.27 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:93.2,93.13 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:98.41,100.31 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:100.31,102.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:103.2,103.28 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:107.62,114.31 4 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:114.31,116.24 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:116.24,118.9 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:122.2,129.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:142.60,144.23 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:144.23,146.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:147.2,152.38 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:152.38,155.26 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:155.26,158.4 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:162.2,162.23 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:162.23,163.21 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:163.21,164.12 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:166.3,167.57 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:167.57,168.43 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:168.43,170.13 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:172.4,172.38 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:172.38,174.13 2 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:176.4,176.9 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:178.3,179.17 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:181.2,181.14 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:221.37,222.28 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:222.28,223.17 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:223.17,225.4 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:227.2,227.24 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/scratch/scratch.go:233.122,237.2 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:50.38,50.68 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:57.108,58.24 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:58.24,60.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:65.2,66.29 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:66.29,68.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:69.2,69.21 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:69.21,71.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:73.2,74.16 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:74.16,76.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:77.2,82.42 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:82.42,84.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:86.2,87.42 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:87.42,89.3 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:91.2,91.28 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:91.28,97.50 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:97.50,99.4 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:101.3,106.43 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:106.43,108.4 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:113.3,114.83 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:114.83,117.4 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:118.3,118.34 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:118.34,121.4 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:123.3,127.64 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:127.64,129.4 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:132.2,132.39 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:132.39,134.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:135.2,135.18 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:139.97,140.21 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:140.21,142.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:143.2,146.4 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:150.94,151.21 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:151.21,153.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:154.2,155.16 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:155.16,157.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:158.2,161.4 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:165.95,166.21 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:166.21,168.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:169.2,170.16 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:170.16,172.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:173.2,176.4 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:180.79,188.2 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:191.92,200.16 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:200.16,202.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:203.2,206.18 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:206.18,209.80 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:209.80,211.4 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:212.3,212.23 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:214.2,214.24 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:218.82,225.2 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:228.81,232.35 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:232.35,234.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:235.2,235.16 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:241.66,243.2 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:248.72,250.2 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:258.80,264.16 3 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:264.16,266.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:267.2,268.9 2 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:268.9,270.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:271.2,271.22 1 1
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:271.22,273.3 1 0
|
||||||
|
github.com/drjones/quantum-arcade/pkg/ledger/ledger.go:274.2,274.27 1 1
|
||||||
@@ -99,11 +99,11 @@ func RoundSeed(server ServerSeed, client [32]byte, nonce uint64) [32]byte {
|
|||||||
// Proof is everything a player needs to verify one outcome without trusting
|
// Proof is everything a player needs to verify one outcome without trusting
|
||||||
// any server response. It is what the verification endpoint returns.
|
// any server response. It is what the verification endpoint returns.
|
||||||
type Proof struct {
|
type Proof struct {
|
||||||
Commitment string `json:"commitment"` // published before the round
|
Commitment string `json:"commitment"` // published before the round
|
||||||
ServerSeed string `json:"server_seed"` // revealed after settlement
|
ServerSeed string `json:"server_seed"` // revealed after settlement
|
||||||
Participants []string `json:"participants"` // hex public keys, join order
|
Participants []string `json:"participants"` // hex public keys, join order
|
||||||
Nonce uint64 `json:"nonce"`
|
Nonce uint64 `json:"nonce"`
|
||||||
RoundSeed string `json:"round_seed"` // derived, shown for convenience
|
RoundSeed string `json:"round_seed"` // derived, shown for convenience
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildProof assembles the verification record for a settled round.
|
// BuildProof assembles the verification record for a settled round.
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package fair_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/drjones/quantum-arcade/pkg/fair"
|
"github.com/drjones/quantum-arcade/pkg/fair"
|
||||||
@@ -106,3 +108,108 @@ func TestServerSeedsAreUnique(t *testing.T) {
|
|||||||
seen[s.Bytes()] = true
|
seen[s.Bytes()] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServerSeedRoundTripsThroughBytes(t *testing.T) {
|
||||||
|
original := fair.NewServerSeed()
|
||||||
|
restored := fair.ServerSeedFromBytes(original.Bytes())
|
||||||
|
|
||||||
|
if restored.Bytes() != original.Bytes() {
|
||||||
|
t.Fatal("seed did not survive a byte round trip")
|
||||||
|
}
|
||||||
|
if restored.Commitment() != original.Commitment() {
|
||||||
|
t.Fatal("restored seed produces a different commitment")
|
||||||
|
}
|
||||||
|
if restored.Hex() != original.Hex() {
|
||||||
|
t.Fatal("restored seed renders differently")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHexIsFullLength(t *testing.T) {
|
||||||
|
s := fair.NewServerSeed()
|
||||||
|
if len(s.Hex()) != 64 {
|
||||||
|
t.Fatalf("hex seed is %d characters, want 64", len(s.Hex()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildProofIsSelfConsistent(t *testing.T) {
|
||||||
|
server := fair.NewServerSeed()
|
||||||
|
keys := [][]byte{[]byte("alice"), []byte("bob"), []byte("carol")}
|
||||||
|
const nonce = 17
|
||||||
|
|
||||||
|
proof := fair.BuildProof(server, keys, nonce)
|
||||||
|
|
||||||
|
if proof.Nonce != nonce {
|
||||||
|
t.Fatalf("proof nonce = %d, want %d", proof.Nonce, nonce)
|
||||||
|
}
|
||||||
|
if len(proof.Participants) != len(keys) {
|
||||||
|
t.Fatalf("proof lists %d participants, want %d", len(proof.Participants), len(keys))
|
||||||
|
}
|
||||||
|
// Every value in the proof must be reproducible from the others.
|
||||||
|
seedBytes, err := hex.DecodeString(proof.ServerSeed)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sum := sha256.Sum256(seedBytes)
|
||||||
|
if hex.EncodeToString(sum[:]) != proof.Commitment {
|
||||||
|
t.Fatal("proof commitment does not match its own seed")
|
||||||
|
}
|
||||||
|
var restored [32]byte
|
||||||
|
copy(restored[:], seedBytes)
|
||||||
|
want := fair.RoundSeed(fair.ServerSeedFromBytes(restored), fair.ClientSeed(keys), nonce)
|
||||||
|
if hex.EncodeToString(want[:]) != proof.RoundSeed {
|
||||||
|
t.Fatal("proof round seed does not follow from its inputs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProofParticipantsPreserveOrder(t *testing.T) {
|
||||||
|
server := fair.NewServerSeed()
|
||||||
|
keys := [][]byte{[]byte("first"), []byte("second")}
|
||||||
|
proof := fair.BuildProof(server, keys, 1)
|
||||||
|
|
||||||
|
if proof.Participants[0] != hex.EncodeToString(keys[0]) {
|
||||||
|
t.Fatal("participant order was not preserved")
|
||||||
|
}
|
||||||
|
if proof.Participants[1] != hex.EncodeToString(keys[1]) {
|
||||||
|
t.Fatal("participant order was not preserved")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reordering the same players must change the seed, since order is part of the
|
||||||
|
// commitment. Otherwise a player could be swapped in without detection.
|
||||||
|
func TestParticipantOrderAffectsTheSeed(t *testing.T) {
|
||||||
|
a, b := []byte("alice"), []byte("bob")
|
||||||
|
if fair.ClientSeed([][]byte{a, b}) == fair.ClientSeed([][]byte{b, a}) {
|
||||||
|
t.Fatal("reordering participants did not change the client seed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length-prefixing must prevent two different participant lists from colliding
|
||||||
|
// through simple concatenation.
|
||||||
|
func TestClientSeedResistsConcatenationCollisions(t *testing.T) {
|
||||||
|
// Without length prefixes, {"ab","c"} and {"a","bc"} would hash the same.
|
||||||
|
one := fair.ClientSeed([][]byte{[]byte("ab"), []byte("c")})
|
||||||
|
two := fair.ClientSeed([][]byte{[]byte("a"), []byte("bc")})
|
||||||
|
if one == two {
|
||||||
|
t.Fatal("different participant lists collided; length prefixing is broken")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyParticipantListIsStable(t *testing.T) {
|
||||||
|
if fair.ClientSeed(nil) != fair.ClientSeed([][]byte{}) {
|
||||||
|
t.Fatal("nil and empty participant lists disagree")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A round with no players must still produce a valid, verifiable outcome.
|
||||||
|
func TestRoundWithNoPlayersStillVerifies(t *testing.T) {
|
||||||
|
server := fair.NewServerSeed()
|
||||||
|
commitment := server.Commitment()
|
||||||
|
proof := fair.BuildProof(server, nil, 5)
|
||||||
|
|
||||||
|
if !fair.VerifyCommitment(commitment, server) {
|
||||||
|
t.Fatal("empty round does not verify")
|
||||||
|
}
|
||||||
|
if proof.RoundSeed == "" {
|
||||||
|
t.Fatal("empty round produced no seed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
199
pkg/fixed/edge_test.go
Normal file
199
pkg/fixed/edge_test.go
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
package fixed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The simulation's verifiability depends on this arithmetic behaving
|
||||||
|
// identically everywhere, including at the extremes. These tests attack the
|
||||||
|
// boundaries.
|
||||||
|
|
||||||
|
func TestMulByZeroAndOne(t *testing.T) {
|
||||||
|
for _, v := range []int64{0, 1, -1, 1000, -1000, 1 << 20} {
|
||||||
|
a := FromInt(v)
|
||||||
|
if got := a.Mul(0); got != 0 {
|
||||||
|
t.Errorf("%d * 0 = %v, want 0", v, got)
|
||||||
|
}
|
||||||
|
if got := a.Mul(One); got != a {
|
||||||
|
t.Errorf("%d * 1 = %v, want %v", v, got, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDivByOneAndSelf(t *testing.T) {
|
||||||
|
for _, v := range []int64{1, -1, 7, -7, 1000, 1 << 20} {
|
||||||
|
a := FromInt(v)
|
||||||
|
if got := a.Div(One); got != a {
|
||||||
|
t.Errorf("%d / 1 = %v, want %v", v, got, a)
|
||||||
|
}
|
||||||
|
if got := a.Div(a); got != One {
|
||||||
|
t.Errorf("%d / %d = %v, want 1", v, v, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDivByZeroPanics(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if recover() == nil {
|
||||||
|
t.Fatal("division by zero did not panic")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_ = One.Div(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSqrtOfNegativePanics(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if recover() == nil {
|
||||||
|
t.Fatal("sqrt of a negative did not panic")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_ = Sqrt(FromInt(-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiplication must stay associative-ish and exact for representable values,
|
||||||
|
// which is what keeps a replayed round identical to the original.
|
||||||
|
func TestMulIsExactForFractions(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
a, b, want F
|
||||||
|
}{
|
||||||
|
{One / 2, One / 2, One / 4},
|
||||||
|
{One / 4, One / 4, One / 16},
|
||||||
|
{One / 2, One / 4, One / 8},
|
||||||
|
{One * 3 / 2, One * 2, One * 3},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
if got := c.a.Mul(c.b); got != c.want {
|
||||||
|
t.Errorf("%v * %v = %v, want %v", c.a, c.b, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round-tripping a value through multiply and divide must return it exactly
|
||||||
|
// for powers of two, where no precision can be lost.
|
||||||
|
func TestMulDivRoundTripOnPowersOfTwo(t *testing.T) {
|
||||||
|
for shift := 0; shift < 20; shift++ {
|
||||||
|
v := FromInt(1 << shift)
|
||||||
|
for _, by := range []F{One * 2, One * 4, One * 8} {
|
||||||
|
if got := v.Mul(by).Div(by); got != v {
|
||||||
|
t.Errorf("2^%d round trip through %v gave %v, want %v", shift, by, got, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSqrtIsMonotonic(t *testing.T) {
|
||||||
|
prev := Sqrt(0)
|
||||||
|
for i := int64(1); i < 5000; i++ {
|
||||||
|
cur := Sqrt(FromInt(i))
|
||||||
|
if cur < prev {
|
||||||
|
t.Fatalf("Sqrt decreased at %d: %v -> %v", i, prev, cur)
|
||||||
|
}
|
||||||
|
prev = cur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sqrt must never overshoot: its square must not exceed the input.
|
||||||
|
func TestSqrtNeverOvershoots(t *testing.T) {
|
||||||
|
for i := int64(0); i < 20000; i++ {
|
||||||
|
a := FromInt(i)
|
||||||
|
r := Sqrt(a)
|
||||||
|
if r.Mul(r) > a {
|
||||||
|
t.Fatalf("Sqrt(%d) = %v squares to %v, which exceeds %v", i, r, r.Mul(r), a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSqrtOfLargeValues(t *testing.T) {
|
||||||
|
// Values in the range the crash curve actually produces, up to the
|
||||||
|
// representable maximum.
|
||||||
|
for _, v := range []int64{1_000_000, 12_960_000, 100_000_000, MaxInt} {
|
||||||
|
a := FromInt(v)
|
||||||
|
r := Sqrt(a)
|
||||||
|
if r <= 0 {
|
||||||
|
t.Fatalf("Sqrt(%d) = %v, want positive", v, r)
|
||||||
|
}
|
||||||
|
if r.Mul(r) > a {
|
||||||
|
t.Fatalf("Sqrt(%d) overshoots", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntTruncatesTowardNegativeInfinity(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
in F
|
||||||
|
want int64
|
||||||
|
}{
|
||||||
|
{One, 1},
|
||||||
|
{One + One/2, 1},
|
||||||
|
{One*2 - 1, 1},
|
||||||
|
{0, 0},
|
||||||
|
{-One, -1},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
if got := c.in.Int(); got != c.want {
|
||||||
|
t.Errorf("(%v).Int() = %d, want %d", c.in, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromIntPanicsOutsideRange(t *testing.T) {
|
||||||
|
for _, v := range []int64{MaxInt + 1, MinInt - 1, 4_000_000_000, -4_000_000_000} {
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
if recover() == nil {
|
||||||
|
t.Errorf("FromInt(%d) did not panic", v)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_ = FromInt(v)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
// The boundaries themselves must be accepted.
|
||||||
|
_ = FromInt(MaxInt)
|
||||||
|
_ = FromInt(MinInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringNeverPanicsAcrossRange(t *testing.T) {
|
||||||
|
values := []F{
|
||||||
|
0, 1, -1, One, -One, One / 3, math.MaxInt64, math.MinInt64 + 1,
|
||||||
|
FromInt(4_000_000),
|
||||||
|
}
|
||||||
|
for _, v := range values {
|
||||||
|
if s := v.String(); s == "" {
|
||||||
|
t.Errorf("String() of %d returned empty", int64(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addition and subtraction are plain integer ops, but the inverse property is
|
||||||
|
// what payout arithmetic relies on.
|
||||||
|
func TestAddSubAreInverse(t *testing.T) {
|
||||||
|
for _, a := range []F{0, One, -One, One * 12345, One / 7} {
|
||||||
|
for _, b := range []F{0, One, -One, One * 999} {
|
||||||
|
if got := a.Add(b).Sub(b); got != a {
|
||||||
|
t.Errorf("(%v + %v) - %v = %v, want %v", a, b, b, got, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determinism check: the same operations in the same order must produce
|
||||||
|
// bit-identical results every time, which is the whole premise of replay.
|
||||||
|
func TestOperationsAreBitStable(t *testing.T) {
|
||||||
|
compute := func() F {
|
||||||
|
acc := One
|
||||||
|
for i := int64(1); i < 500; i++ {
|
||||||
|
acc = acc.Mul(One + One/F(i+1))
|
||||||
|
acc = acc.Div(One + One/F(i+2))
|
||||||
|
acc = acc.Add(FromInt(i % 3))
|
||||||
|
acc = Sqrt(acc)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
first := compute()
|
||||||
|
for i := 0; i < 200; i++ {
|
||||||
|
if got := compute(); got != first {
|
||||||
|
t.Fatalf("run %d diverged: %v != %v", i, got, first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,8 +19,24 @@ const One F = 1 << 32
|
|||||||
|
|
||||||
const fracBits = 32
|
const fracBits = 32
|
||||||
|
|
||||||
|
// MaxInt is the largest whole number representable in Q32.32. Values beyond
|
||||||
|
// it cannot be held in the 32 integer bits.
|
||||||
|
const MaxInt int64 = 1<<31 - 1
|
||||||
|
|
||||||
|
// MinInt is the smallest whole number representable in Q32.32.
|
||||||
|
const MinInt int64 = -(1 << 31)
|
||||||
|
|
||||||
// FromInt converts a whole number to fixed-point.
|
// FromInt converts a whole number to fixed-point.
|
||||||
func FromInt(v int64) F { return F(v << fracBits) }
|
//
|
||||||
|
// It panics outside [MinInt, MaxInt] rather than wrapping. A silent wrap here
|
||||||
|
// produced a negative multiplier from a positive input, which is exactly the
|
||||||
|
// kind of fault that is invisible until it corrupts a payout.
|
||||||
|
func FromInt(v int64) F {
|
||||||
|
if v > MaxInt || v < MinInt {
|
||||||
|
panic("fixed: " + strconv.FormatInt(v, 10) + " is outside the Q32.32 integer range")
|
||||||
|
}
|
||||||
|
return F(v << fracBits)
|
||||||
|
}
|
||||||
|
|
||||||
// Int truncates toward negative infinity and returns the whole part.
|
// Int truncates toward negative infinity and returns the whole part.
|
||||||
func (a F) Int() int64 { return int64(a) >> fracBits }
|
func (a F) Int() int64 { return int64(a) >> fracBits }
|
||||||
|
|||||||
@@ -21,8 +21,20 @@ func TestHugeBalanceIsExact(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 21 million BTC in millisatoshis is the largest amount that can ever
|
// 21 million BTC in millisatoshis is the largest amount that can ever
|
||||||
// exist: 2.1e15. It must round-trip exactly, with no float contamination.
|
// exist: 2.1e18. It must round-trip exactly, with no float contamination.
|
||||||
const allTheBitcoin int64 = 21_000_000 * 100_000_000 * 1000
|
const allTheBitcoin int64 = 21_000_000 * 100_000_000 * 1000
|
||||||
|
|
||||||
|
// The ledger is append-only and never truncated, so repeated runs steadily
|
||||||
|
// consume the bridge's headroom. Skip rather than fail when it is spent —
|
||||||
|
// that is an exhausted fixture, not a defect. Reset with `make db-reset`.
|
||||||
|
issued, err := l.TotalIssued(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if math.MaxInt64-issued < allTheBitcoin {
|
||||||
|
t.Skipf("bridge headroom exhausted (%d issued); run `make db-reset`", issued)
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := l.Deposit(ctx, p, allTheBitcoin); err != nil {
|
if _, err := l.Deposit(ctx, p, allTheBitcoin); err != nil {
|
||||||
t.Fatalf("depositing the entire supply: %v", err)
|
t.Fatalf("depositing the entire supply: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,28 +44,28 @@ const (
|
|||||||
|
|
||||||
// Bet is one player's position in the current round.
|
// Bet is one player's position in the current round.
|
||||||
type Bet struct {
|
type Bet struct {
|
||||||
AccountID int64
|
AccountID int64
|
||||||
Pubkey []byte
|
Pubkey []byte
|
||||||
Nickname string
|
Nickname string
|
||||||
StakeMsat int64
|
StakeMsat int64
|
||||||
CashedOutAt fixed.F // zero until they cash out
|
CashedOutAt fixed.F // zero until they cash out
|
||||||
PayoutMsat int64
|
PayoutMsat int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot is what clients render. It carries the seed inputs so a client can
|
// Snapshot is what clients render. It carries the seed inputs so a client can
|
||||||
// verify the round the moment it settles.
|
// verify the round the moment it settles.
|
||||||
type Snapshot struct {
|
type Snapshot struct {
|
||||||
RoundID int64 `json:"round_id"`
|
RoundID int64 `json:"round_id"`
|
||||||
Game string `json:"game"`
|
Game string `json:"game"`
|
||||||
State State `json:"state"`
|
State State `json:"state"`
|
||||||
Tick int `json:"tick"`
|
Tick int `json:"tick"`
|
||||||
Multiplier string `json:"multiplier"`
|
Multiplier string `json:"multiplier"`
|
||||||
Commitment string `json:"commitment"`
|
Commitment string `json:"commitment"`
|
||||||
ServerSeed string `json:"server_seed,omitempty"` // only once settled
|
ServerSeed string `json:"server_seed,omitempty"` // only once settled
|
||||||
CrashPoint string `json:"crash_point,omitempty"` // only once settled
|
CrashPoint string `json:"crash_point,omitempty"` // only once settled
|
||||||
Players []Player `json:"players"`
|
Players []Player `json:"players"`
|
||||||
HousePotMsat int64 `json:"house_pot_msat"`
|
HousePotMsat int64 `json:"house_pot_msat"`
|
||||||
NextPhaseIn float64 `json:"next_phase_in_seconds"`
|
NextPhaseIn float64 `json:"next_phase_in_seconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Player is the public view of a participant.
|
// Player is the public view of a participant.
|
||||||
|
|||||||
@@ -91,6 +91,17 @@ func (f *fixture) startRun() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// forceCrashPoint pins the round's crash point so cash-out tests do not depend
|
||||||
|
// on a random draw. A genuine 1.00x crash is an instant bust where nobody can
|
||||||
|
// cash out, which is correct behaviour but useless for testing the cash-out
|
||||||
|
// path.
|
||||||
|
func (f *fixture) forceCrashPoint(multiplier int64) {
|
||||||
|
f.t.Helper()
|
||||||
|
f.room.mu.Lock()
|
||||||
|
f.room.crashPoint = fixed.FromInt(multiplier)
|
||||||
|
f.room.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// advanceTo moves the round to a specific tick without settling.
|
// advanceTo moves the round to a specific tick without settling.
|
||||||
func (f *fixture) advanceTo(tick int) {
|
func (f *fixture) advanceTo(tick int) {
|
||||||
f.t.Helper()
|
f.t.Helper()
|
||||||
@@ -257,6 +268,7 @@ func TestCashOutOnlyWhileRunning(t *testing.T) {
|
|||||||
t.Fatal("cash out accepted during betting")
|
t.Fatal("cash out accepted during betting")
|
||||||
}
|
}
|
||||||
f.startRun()
|
f.startRun()
|
||||||
|
f.forceCrashPoint(100)
|
||||||
if _, err := f.room.CashOut(id); err != nil {
|
if _, err := f.room.CashOut(id); err != nil {
|
||||||
t.Fatalf("cash out rejected while running: %v", err)
|
t.Fatalf("cash out rejected while running: %v", err)
|
||||||
}
|
}
|
||||||
@@ -268,6 +280,7 @@ func TestCannotCashOutTwice(t *testing.T) {
|
|||||||
f.openBetting()
|
f.openBetting()
|
||||||
_ = f.room.PlaceBet(f.ctx, id, pk, "a", 1_000)
|
_ = f.room.PlaceBet(f.ctx, id, pk, "a", 1_000)
|
||||||
f.startRun()
|
f.startRun()
|
||||||
|
f.forceCrashPoint(100)
|
||||||
|
|
||||||
if _, err := f.room.CashOut(id); err != nil {
|
if _, err := f.room.CashOut(id); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -282,6 +295,7 @@ func TestCannotCashOutWithoutABet(t *testing.T) {
|
|||||||
id, _ := f.player("a", 10_000)
|
id, _ := f.player("a", 10_000)
|
||||||
f.openBetting()
|
f.openBetting()
|
||||||
f.startRun()
|
f.startRun()
|
||||||
|
f.forceCrashPoint(100)
|
||||||
|
|
||||||
if _, err := f.room.CashOut(id); err == nil {
|
if _, err := f.room.CashOut(id); err == nil {
|
||||||
t.Fatal("cash out accepted with no bet placed")
|
t.Fatal("cash out accepted with no bet placed")
|
||||||
@@ -574,6 +588,7 @@ func TestConcurrentCashOutsYieldOne(t *testing.T) {
|
|||||||
f.openBetting()
|
f.openBetting()
|
||||||
_ = f.room.PlaceBet(f.ctx, id, pk, "a", 5_000)
|
_ = f.room.PlaceBet(f.ctx, id, pk, "a", 5_000)
|
||||||
f.startRun()
|
f.startRun()
|
||||||
|
f.forceCrashPoint(100)
|
||||||
|
|
||||||
const attempts = 10
|
const attempts = 10
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@@ -606,6 +621,7 @@ func TestSnapshotReportsCashOutMultiplier(t *testing.T) {
|
|||||||
f.openBetting()
|
f.openBetting()
|
||||||
_ = f.room.PlaceBet(f.ctx, id, pk, "nick", 5_000)
|
_ = f.room.PlaceBet(f.ctx, id, pk, "nick", 5_000)
|
||||||
f.startRun()
|
f.startRun()
|
||||||
|
f.forceCrashPoint(100)
|
||||||
|
|
||||||
if _, err := f.room.CashOut(id); err != nil {
|
if _, err := f.room.CashOut(id); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ type Tier struct {
|
|||||||
|
|
||||||
// Ticket is a scratch game definition.
|
// Ticket is a scratch game definition.
|
||||||
type Ticket struct {
|
type Ticket struct {
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
Blurb string
|
Blurb string
|
||||||
// Cells is how many squares the player scratches, for presentation.
|
// Cells is how many squares the player scratches, for presentation.
|
||||||
Cells int
|
Cells int
|
||||||
Tiers []Tier
|
Tiers []Tier
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outcome is the result of one ticket.
|
// Outcome is the result of one ticket.
|
||||||
|
|||||||
@@ -44,10 +44,22 @@ func CrashPoint(seed [32]byte) fixed.F {
|
|||||||
|
|
||||||
// crash = payoutRatio / (u / 2^32), computed as (payoutRatio * 2^32) / u
|
// crash = payoutRatio / (u / 2^32), computed as (payoutRatio * 2^32) / u
|
||||||
// through a 128-bit intermediate so no precision is lost.
|
// through a 128-bit intermediate so no precision is lost.
|
||||||
// hi is zero because payoutRatio < 2^32, so the division cannot overflow.
|
|
||||||
hi, lo := bits.Mul64(payoutRatio, 1<<32)
|
hi, lo := bits.Mul64(payoutRatio, 1<<32)
|
||||||
q, _ := bits.Div64(hi, lo, u)
|
q, _ := bits.Div64(hi, lo, u)
|
||||||
|
|
||||||
|
// The quotient can exceed int64 for the very smallest u — at u=1 it wraps
|
||||||
|
// negative, which would silently turn the rarest and most valuable outcome
|
||||||
|
// into an instant loss. Compare in unsigned space before converting.
|
||||||
|
//
|
||||||
|
// The cap is the largest multiplier the curve can express. Anything above
|
||||||
|
// it is unreachable anyway: the round would hit its tick ceiling first.
|
||||||
|
// It also bounds the maximum payout, so a single round cannot demand more
|
||||||
|
// than the house can hold.
|
||||||
|
maxCP := MaxMultiplier()
|
||||||
|
if q >= uint64(maxCP) {
|
||||||
|
return maxCP
|
||||||
|
}
|
||||||
|
|
||||||
cp := fixed.F(q)
|
cp := fixed.F(q)
|
||||||
if cp < fixed.One {
|
if cp < fixed.One {
|
||||||
cp = fixed.One
|
cp = fixed.One
|
||||||
|
|||||||
@@ -102,8 +102,7 @@ func TestRoundLengthIsBounded(t *testing.T) {
|
|||||||
t.Fatalf("curve past the ceiling = %v, want %v", got, MaxMultiplier())
|
t.Fatalf("curve past the ceiling = %v, want %v", got, MaxMultiplier())
|
||||||
}
|
}
|
||||||
// Even the most extreme crash point settles within the ceiling.
|
// Even the most extreme crash point settles within the ceiling.
|
||||||
worst := fixed.FromInt(4_000_000_000)
|
if tick := TicksToMultiplier(MaxMultiplier()); tick > RoundTicks {
|
||||||
if tick := TicksToMultiplier(worst); tick > RoundTicks {
|
|
||||||
t.Fatalf("extreme crash point needs %d ticks, ceiling is %d", tick, RoundTicks)
|
t.Fatalf("extreme crash point needs %d ticks, ceiling is %d", tick, RoundTicks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,7 +113,7 @@ func TestCurveTimings(t *testing.T) {
|
|||||||
multiplier int64
|
multiplier int64
|
||||||
maxSeconds float64
|
maxSeconds float64
|
||||||
}{
|
}{
|
||||||
{2, 20}, // the common case should arrive quickly
|
{2, 20}, // the common case should arrive quickly
|
||||||
{10, 45},
|
{10, 45},
|
||||||
{100, 56},
|
{100, 56},
|
||||||
} {
|
} {
|
||||||
@@ -125,3 +124,42 @@ func TestCurveTimings(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The crash point must never be negative or below 1.0, at any seed. An
|
||||||
|
// unsigned quotient exceeding int64 previously wrapped negative here.
|
||||||
|
func TestCrashPointNeverOverflows(t *testing.T) {
|
||||||
|
// Drive the derivation across seeds chosen to produce very small u, which
|
||||||
|
// is where the quotient is largest.
|
||||||
|
for i := 0; i < 200000; i++ {
|
||||||
|
var seed [32]byte
|
||||||
|
for j := 0; j < 32; j++ {
|
||||||
|
seed[j] = byte(i >> (8 * (j % 4)))
|
||||||
|
}
|
||||||
|
cp := CrashPoint(seed)
|
||||||
|
if cp < fixed.One {
|
||||||
|
t.Fatalf("seed %d produced crash point %v, below 1.0", i, cp)
|
||||||
|
}
|
||||||
|
if cp > MaxMultiplier() {
|
||||||
|
t.Fatalf("seed %d produced crash point %v, above the ceiling %v",
|
||||||
|
i, cp, MaxMultiplier())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The payout a single round can demand must be bounded, so settlement can
|
||||||
|
// always be covered.
|
||||||
|
func TestMaximumPayoutIsBounded(t *testing.T) {
|
||||||
|
max := MaxMultiplier()
|
||||||
|
if max <= 0 {
|
||||||
|
t.Fatalf("ceiling is not positive: %v", max)
|
||||||
|
}
|
||||||
|
// A 1000-sat stake at the ceiling must stay well inside int64.
|
||||||
|
const stakeMsat = int64(1_000_000)
|
||||||
|
payout := stakeMsat * int64(max) / int64(fixed.One)
|
||||||
|
if payout <= 0 {
|
||||||
|
t.Fatalf("payout at the ceiling overflowed: %d", payout)
|
||||||
|
}
|
||||||
|
if payout > 1<<62 {
|
||||||
|
t.Fatalf("payout at the ceiling is %d, unreasonably large", payout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user