Transaction

92052B544ECF3D…DE38D9E2D192

Block 336,753 · index 0 · RPC

Summary

Hash
92052B544ECF3D932FDB29C6E85051247D5673F1D9607DF1CDF4DE38D9E2D192
Block
336,753
Size
75833 bytes
Gas used
98,665,579 / 250,000,000
Fee
250000 ugnot
Status
success

Messages

Attached funds
12000000 ugnot

Arguments · 9

  1. #1service_market
  2. #2gnomod.toml
  3. #3module = "gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/service_market" gno = "0.9"
  4. #4service_market.gno
  5. #5// Realm service_market is a custodial GNOT marketplace for SERVICES: // providers publish a standing offer (title, description, price, // delivery window), a customer purchases it by paying the exact price // into escrow, the provider marks the work delivered, and the escrow is // released to the provider only once the customer accepts — or once the // acceptance window lapses. // // COMPOSITION (per the recorded DISCOVERY / REUSE ANALYSIS in // DISCOVERY.md): balance accounting is feeledger, coin movement is // coinio, free-text render safety is p/nt/markdown/sanitize/v0. This // realm owns only the two-level offer/order state machine, which the // discovery gate found nowhere else: every escrow realm in the searched // sources escrows FUNDER-FIRST (the party who will later decide escrows // first, then solicits work), while a service marketplace must escrow // CUSTOMER-FIRST against a standing offer. // // LIFECYCLE. A service is a reusable offer; an order is one purchase of // it. Order terminal states are frozen and reached exactly once. // // RegisterService (anyone, no coins) : status Active. The fee bps is // SNAPSHOTTED under the // provider's own maxFeeBps // ceiling. // RetireService (provider only) : Active -> Retired. Blocks NEW // orders only; orders already in // flight are untouched. // PurchaseService (EOA + -send) : order Purchased, amount into // escrow. Sets the delivery // deadline. // MarkDelivered (provider only) : Purchased -> Delivered. Starts // the acceptance window. // AcceptDelivery (customer only) : Delivered -> Released. THE // RESOLUTION. Escrow becomes the // provider's claimable balance // minus the snapshotted fee. // ReleaseTimeout (anyone) : Delivered -> Released after // AcceptanceBlocks. Deemed // acceptance. // DeclineOrder (provider only) : Purchased -> Refunded, fee-free. // ClaimRefund (anyone) : Purchased -> Refunded after the // delivery deadline, fee-free. // Claim / ClaimAll (anyone) : pays out the caller's own // claimable balance. // // EVERY VALUE EXIT IS A PULL. Release and refund only move numbers // between the escrow pool and a claimable balance; the beneficiary // later calls Claim. Nothing in this realm ever pushes coins to a third // party, which is what lets both timeout valves stay permissionless: a // stranger triggering ReleaseTimeout or ClaimRefund performs no send, so // a beneficiary that cannot receive a send can never brick the valve. // // THE THREE REQUIRED PROTECTIONS. // // Unauthorized settlement: every identity derives from the crossing // entrypoint's cur.Previous().Address(); no function takes a caller // identity as a parameter (the designation-forgery shape that // disqualified most of the escrow realms found in discovery). The payee // is COPIED INTO THE ORDER at purchase, so neither retiring the service // nor any later edit can redirect an in-flight order's payment. There is // no admin path to any order's escrow: the admin sets the fee for FUTURE // registrations and nothing else. // // Double payment: an order's status is checked and driven terminal in // the same transaction that moves its value, and escrowTotal is // decremented in lockstep with the credit. Release and refund both // require a non-terminal status, so at most one of them can ever // succeed for a given order, and neither can succeed twice. // // Stuck funds: both counterparties have a permissionless valve against // the other's inaction. A provider who never delivers loses the escrow // back to the customer at the delivery deadline (ClaimRefund); a // customer who never accepts loses it to the provider at the acceptance // deadline (ReleaseTimeout). Neither valve trusts its caller. // // ACCEPTANCE IS A PROTOCOL CONSTANT, NOT A PROVIDER SETTING. The // provider chooses the delivery window (their own commitment, and their // own risk), but AcceptanceBlocks is fixed. Were it provider-chosen, a // provider would set it to zero, mark work delivered, and auto-release // in the same block — settlement without resolution, wearing the // costume of a timeout. // // MONETARY INVARIANT (conservation). With H = ugnot held at the realm // address, E = escrowTotal (Σ amount over Purchased and Delivered // orders), U = claimable balances, F = the fee pot, S >= 0 out-of-band // surplus: // // H == E + U + F + S // // PurchaseService raises H and E by exactly the price. Release moves // amount from E to U+F (feeledger guarantees credited + fee == amount); // refund moves amount from E to U at zero fee. Claim*/WithdrawFees debit // the ledger before coinio.Payout moves the identical amount out. Any // panic aborts the whole transaction; this realm never issues or removes // coins. Surplus is recoverable only via SweepDenom (fee recipient), // which reserves Liabilities() = E + U + F. // // LIMITATION — NO DISPUTE ARBITRATION, DELIBERATELY. This realm resolves // on acceptance or on the acceptance timeout. It does NOT adjudicate // whether delivered work was good. A customer who considers the work // inadequate has no lever here beyond declining to accept, and the // timeout will still pay the provider. That is a real limitation and it // is the deliberate price of the property above: an arbiter empowered to // redirect escrow is a party who can seize funds, and the closest realm // found in discovery (r/samcrew/escrow_v3) carries exactly that shape — // a single hardcoded admin key, with no rotation function, that is // simultaneously sole arbiter, a unilateral release path, and a // permanent pause switch over every fund path. Adding arbitration here // would change what this application IS and expand its security model, // so it is refused rather than smuggled in. A deployment that needs // adjudicated disputes needs that designed, audited and authorized as // its own application. // // REALM-CALLER CAVEAT: assertNoSend reads the ORIGIN transaction's send // envelope, not this realm's receipt, so a realm caller is refused by // every non-payable function whenever the SAME transaction attached a // -send anywhere — even though this realm received nothing. It fails // closed, it is not third-party triggerable, and the workaround is to // isolate the call in its own -send-free transaction. // // Named concretely, because the generic phrasing understates where it // lands. It applies to every exported function except PurchaseService, // the only payable one, but four of them carry the weight: // MarkDelivered, without which no order against a realm provider can // ever reach a release; ReleaseTimeout and ClaimRefund, the two valves // this design deliberately leaves permissionless; and Claim / ClaimAll, // the only paths that extract a credited balance. A realm-based keeper // bot that batches a valve call into a transaction carrying coins for // some other purpose is therefore silently unusable, and a realm // provider must budget a dedicated transaction both to deliver and to // collect. // Requirement 3 (no stuck funds) survives this: both valves are // permissionless and any EOA can pull them, so no order depends on a // realm caller to unstick. package service_market import ( "chain" "chain/runtime" "chain/runtime/unsafe" "strconv" "gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio" "gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger" "gno.land/p/nt/avl/v0" "gno.land/p/nt/markdown/sanitize/v0" ) // Denom is the only asset this realm accepts. const Denom = "ugnot" // MaxFeeBps is the hard protocol-fee cap: 1000 bps = 10%. const MaxFeeBps = int64(1000) // AcceptanceBlocks is how long a customer has to accept a delivery // before anyone may release it to the provider. It is a PROTOCOL // constant on purpose — see the header. ~7 days at pearl's observed // ~4.2s blocks. const AcceptanceBlocks = int64(140000) // Service status values. const ( ServiceActive = "active" ServiceRetired = "retired" ) // Order status values. Released and Refunded are terminal. const ( OrderPurchased = "purchased" OrderDelivered = "delivered" OrderReleased = "released" OrderRefunded = "refunded" ) // Input bounds. const ( MaxTitleLen = 80 MaxDescLen = 2000 MaxURILen = 400 MinPrice = int64(1) // MinDeliveryBlocks/MaxDeliveryBlocks bound the provider's own // turnaround commitment: ~1 hour to ~60 days. MinDeliveryBlocks = int64(850) MaxDeliveryBlocks = int64(1200000) // MaxServicesPerProvider bounds catalog monopolization by a single // address. The same finding was RED in both permission_registry and // service_registry; it is cheaper to carry the counter from the // start than to discover it in an audit. MaxServicesPerProvider = 20 // RenderLimit bounds the home page. An unbounded Render was YELLOW // in three prior audits. RenderLimit = 20 ) type service struct { id int64 provider address title string description string price int64 deliveryBlocks int64 feeBps int64 // snapshotted at registration, charged at release status string orders int64 // lifetime count, never decremented } type order struct { id int64 serviceID int64 customer address provider address // copied at purchase; never re-read from the service amount int64 feeBps int64 // copied at purchase from the service's snapshot status string deliveryDeadline int64 // absolute height; meaningful while Purchased acceptanceDeadline int64 // absolute height; set at MarkDelivered deliveryURI string } var ( admin address // may set fee, fee recipient, stage successor pendingAdmin address // staged by TransferAdmin, completes via AcceptAdmin feeRecipient address // may withdraw fees and sweep surplus feeBps int64 // fee snapshotted into NEW services self address // this realm's address, captured at deploy nextService int64 nextOrder int64 escrowTotal int64 // == Σ amount over Purchased and Delivered orders services = avl.NewTree() // padID(id) -> *service orders = avl.NewTree() // padID(id) -> *order providerNum = avl.NewTree() // address -> *int64, services per provider ledger = feeledger.MustNew(MaxFeeBps) ) func init() { admin = unsafe.OriginCaller() feeRecipient = admin self = unsafe.CurrentRealm().Address() } // --- provider side --- // RegisterService publishes a standing offer and returns its id. No // coins are accepted; the storage deposit the caller pays is the // anti-spam. The current protocol fee is snapshotted into the service // and must not exceed maxFeeBps, the ceiling the provider signed for — // pass MaxFeeBps to accept any legal fee. // // Providers may be EOAs or realms, but a realm provider takes on three // obligations that this realm cannot check for it. Realm bytes are // immutable after addpkg, so a realm deployed without them cannot // acquire them later: failing (1) means it never earns, failing (2) or // (3) means what it earned is permanently stranded. // // 1. It must already expose a crossing entrypoint that calls // MarkDelivered. That is the only transition to OrderDelivered, and // both release paths require it, so a provider realm that cannot // call it never earns a credit at all — every order against it runs // to ClaimRefund instead. This obligation binds before the next one. // 2. It must already expose a crossing entrypoint that calls Claim or // ClaimAll on this realm. A credited balance is a pull, never a // push; nothing here can reach into a provider realm to deliver it. // 3. It must make both calls in transactions whose ORIGIN carried no // -send, per the REALM-CALLER CAVEAT in the package header. // // The customer side is not symmetric: PurchaseService is payable and // routes through coinio.Receive, which requires an EOA calling directly // via MsgCall. A realm can therefore sell a service here but cannot buy // one — and "can sell" is exactly as strong as the three obligations // above, no stronger. A customer is structurally always an EOA, which // is why the permissionless valves are always pullable. func RegisterService(cur realm, title, description string, price, deliveryBlocks, maxFeeBps int64) int64 { assertNoSend() provider := cur.Previous().Address() assertValidTitle(title) if len(description) > MaxDescLen { panic("description must be at most " + strconv.Itoa(MaxDescLen) + " characters") } if price < MinPrice { panic("price must be at least " + itoa(MinPrice) + Denom) } if deliveryBlocks < MinDeliveryBlocks || deliveryBlocks > MaxDeliveryBlocks { panic("deliveryBlocks must be " + itoa(MinDeliveryBlocks) + "-" + itoa(MaxDeliveryBlocks)) } if maxFeeBps < 0 || maxFeeBps > MaxFeeBps { panic("maxFeeBps must be 0-" + itoa(MaxFeeBps)) } if feeBps > maxFeeBps { panic("current fee " + itoa(feeBps) + " bps exceeds your ceiling of " + itoa(maxFeeBps) + " bps") } if countFor(provider) >= MaxServicesPerProvider { panic("at most " + strconv.Itoa(MaxServicesPerProvider) + " services per provider") } id := nextService nextService++ services.Set(padID(id), &service{ id: id, provider: provider, title: title, description: description, price: price, deliveryBlocks: deliveryBlocks, feeBps: feeBps, status: ServiceActive, }) bumpFor(provider, 1) chain.Emit("ServiceRegistered", "id", itoa(id), "provider", provider.String(), "price", itoa(price), "feeBps", itoa(feeBps), ) return id } // RetireService stops a service accepting NEW orders. Orders already in // flight keep their own copies of provider, amount and fee, and run to // their own terminal states unaffected. Provider only, and terminal — // there is no un-retire, so a customer reading "active" cannot have it // flicker underneath them. func RetireService(cur realm, id int64) { assertNoSend() caller := cur.Previous().Address() s := mustGetService(id) if caller != s.provider { panic("only the provider may retire this service") } if s.status != ServiceActive { panic("service is not active") } s.status = ServiceRetired bumpFor(s.provider, -1) chain.Emit("ServiceRetired", "id", itoa(id), "provider", s.provider.String()) } // --- customer side --- // PurchaseService escrows EXACTLY the listed price and opens an order. // The caller must be an EOA (coinio.Receive is the receipt-guaranteed // shape) and must attach exactly the price in ugnot; any mismatch aborts // and the coins revert with the transaction. Self-purchase is rejected. func PurchaseService(cur realm, serviceID int64) int64 { customer, amount := coinio.Receive(0, cur, Denom) s := mustGetService(serviceID) if s.status != ServiceActive { panic("service is not active") } if customer == s.provider { panic("the provider cannot purchase their own service") } if amount != s.price { panic("send exactly the listed price: " + itoa(s.price) + Denom) } newEscrow, ok := checkedAdd(escrowTotal, amount) if !ok { panic("escrow total would overflow") } id := nextOrder nextOrder++ orders.Set(padID(id), &order{ id: id, serviceID: serviceID, customer: customer, provider: s.provider, amount: amount, feeBps: s.feeBps, status: OrderPurchased, deliveryDeadline: runtime.ChainHeight() + s.deliveryBlocks, }) escrowTotal = newEscrow s.orders++ chain.Emit("ServicePurchased", "orderId", itoa(id), "serviceId", itoa(serviceID), "customer", customer.String(), "provider", s.provider.String(), "amount", itoa(amount), ) return id } // MarkDelivered records that the work is done and starts the acceptance // window. Provider only. The uri is an opaque reference to the // deliverable; this realm neither fetches nor interprets it, and it is // sanitized on the way out to Render. // // Delivering AFTER the delivery deadline is allowed as long as nobody // has yet called ClaimRefund — late work the customer still wants should // not be destroyed by a deadline that exists to protect the customer. // The customer's remedy is unchanged: they simply do not accept, and the // refund valve stays open right up until this transition lands. func MarkDelivered(cur realm, orderID int64, uri string) { assertNoSend() caller := cur.Previous().Address() o := mustGetOrder(orderID) if caller != o.provider { panic("only the provider may mark this order delivered") } if o.status != OrderPurchased { panic("order is not awaiting delivery") } if len(uri) > MaxURILen { panic("uri must be at most " + strconv.Itoa(MaxURILen) + " characters") } o.status = OrderDelivered o.deliveryURI = uri o.acceptanceDeadline = runtime.ChainHeight() + AcceptanceBlocks chain.Emit("OrderDelivered", "orderId", itoa(orderID), "provider", o.provider.String(), "acceptanceDeadline", itoa(o.acceptanceDeadline), ) } // AcceptDelivery is THE RESOLUTION: the customer accepts the delivered // work and the escrow becomes the provider's claimable balance, minus // the fee snapshotted into the order at purchase. Customer only. func AcceptDelivery(cur realm, orderID int64) { assertNoSend() caller := cur.Previous().Address() o := mustGetOrder(orderID) if caller != o.customer { panic("only the customer may accept this delivery") } if o.status != OrderDelivered { panic("order is not awaiting acceptance") } release(o, "accepted") } // ReleaseTimeout releases a delivered order to the provider once the // acceptance window has lapsed. Permissionless: a customer who stops // responding must not be able to strand a provider's completed work. // This is deemed acceptance, and it is the only path by which escrow // reaches a provider without the customer's explicit act. func ReleaseTimeout(cur realm, orderID int64) { assertNoSend() o := mustGetOrder(orderID) if o.status != OrderDelivered { panic("order is not awaiting acceptance") } if runtime.ChainHeight() < o.acceptanceDeadline { panic("acceptance window has not lapsed: " + itoa(o.acceptanceDeadline-runtime.ChainHeight()) + " blocks remain") } release(o, "timeout") } // DeclineOrder returns the escrow to the customer before delivery. // Provider only, fee-free — a provider who cannot fulfil should be able // to hand the money straight back rather than wait out a deadline. Safe // to allow unilaterally because it only ever moves value AWAY from the // caller. func DeclineOrder(cur realm, orderID int64) { assertNoSend() caller := cur.Previous().Address() o := mustGetOrder(orderID) if caller != o.provider { panic("only the provider may decline this order") } if o.status != OrderPurchased { panic("order is not awaiting delivery") } refund(o, "declined") } // ClaimRefund returns the escrow to the customer once the provider has // missed the delivery deadline. Permissionless: a provider who abandons // an order must not be able to strand the customer's money. // // It is fee-free. Charging a protocol fee on a refund would mean the // marketplace profits from provider non-performance. func ClaimRefund(cur realm, orderID int64) { assertNoSend() o := mustGetOrder(orderID) if o.status != OrderPurchased { panic("order is not awaiting delivery") } if runtime.ChainHeight() < o.deliveryDeadline { panic("delivery window has not lapsed: " + itoa(o.deliveryDeadline-runtime.ChainHeight()) + " blocks remain") } refund(o, "expired") } // --- payouts --- // Claim sends amount ugnot of the caller's claimable balance back to the // caller. func Claim(cur realm, amount int64) { assertNoSend() caller := cur.Previous().Address() if err := ledger.Withdraw(caller.String(), amount); err != nil { panic(err) } coinio.Payout(0, cur, caller, Denom, amount) chain.Emit("Claim", "to", caller.String(), "amount", itoa(amount)) } // ClaimAll sends the caller's entire claimable balance back to the // caller. Fails if there is nothing to claim. func ClaimAll(cur realm) { assertNoSend() caller := cur.Previous().Address() amount, err := ledger.WithdrawAll(caller.String()) if err != nil { panic(err) } if amount == 0 { panic("nothing to claim") } coinio.Payout(0, cur, caller, Denom, amount) chain.Emit("Claim", "to", caller.String(), "amount", itoa(amount)) } // WithdrawFees sends the accrued fee pot to the fee recipient. Only the // fee recipient may call it. func WithdrawFees(cur realm) { assertNoSend() caller := cur.Previous().Address() if caller != feeRecipient { panic("only the fee recipient may withdraw fees") } if ledger.FeesAccrued() == 0 { panic("no fees accrued") } amount := ledger.WithdrawFees() coinio.Payout(0, cur, feeRecipient, Denom, amount) chain.Emit("FeesWithdrawn", "to", feeRecipient.String(), "amount", itoa(amount)) } // SweepDenom sends the surplus of a single denomination to the fee // recipient. For ugnot only the excess over Liabilities() moves — which // reserves live escrow as well as claimable balances and the fee pot — // and other denoms move wholly. Only the fee recipient may call it. func SweepDenom(cur realm, denom string) { assertNoSend() caller := cur.Previous().Address() if caller != feeRecipient { panic("only the fee recipient may sweep surplus") } reserve := int64(0) if denom == Denom { reserve = Liabilities() } swept := coinio.Sweep(0, cur, feeRecipient, denom, reserve) chain.Emit("SurplusSwept", "to", feeRecipient.String(), "coins", itoa(swept)+denom) } // --- administration --- // SetFeeBps sets the protocol fee snapshotted into FUTURE services. // Existing services, and every order already placed against them, keep // the fee they were created under. Admin only; bounded by [0, MaxFeeBps]. func SetFeeBps(cur realm, bps int64) { assertNoSend() assertAdmin(cur.Previous().Address()) if bps < 0 || bps > MaxFeeBps { panic("fee must be 0-" + itoa(MaxFeeBps) + " bps") } feeBps = bps chain.Emit("FeeChanged", "bps", itoa(bps)) } // SetFeeRecipient sets the address that may withdraw fees and sweep // surplus. Admin only. func SetFeeRecipient(cur realm, next address) { assertNoSend() assertAdmin(cur.Previous().Address()) if !next.IsValid() { panic("invalid fee recipient address") } feeRecipient = next chain.Emit("FeeRecipientChanged", "to", next.String()) } // TransferAdmin stages a successor. Two-step: the successor must call // AcceptAdmin, so a typo cannot orphan the realm. Admin only. Passing // the zero address cancels a pending nomination; any other value must be // a well-formed address, for parity with SetFeeRecipient. The two-step // handover already made a malformed nominee harmless — it could never // call AcceptAdmin — so this check rejects the typo at the point it is // made rather than leaving it staged and readable as a real nomination. func TransferAdmin(cur realm, next address) { assertNoSend() assertAdmin(cur.Previous().Address()) if next != "" && !next.IsValid() { panic("invalid admin address") } pendingAdmin = next chain.Emit("AdminNominated", "to", next.String()) } // AcceptAdmin completes a staged handover. Only the nominee may call it. func AcceptAdmin(cur realm) { assertNoSend() caller := cur.Previous().Address() if pendingAdmin == "" || caller != pendingAdmin { panic("only the nominated admin may accept") } admin = caller pendingAdmin = "" chain.Emit("AdminTransferred", "to", admin.String()) } // --- views --- func ServiceInfo(id int64) (provider address, title string, price, deliveryBlocks, feeBps int64, status string, orderCount int64) { s := mustGetService(id) return s.provider, s.title, s.price, s.deliveryBlocks, s.feeBps, s.status, s.orders } func ServiceDescription(id int64) string { return mustGetService(id).description } func OrderInfo(id int64) (serviceID int64, customer, provider address, amount, feeBps int64, status string) { o := mustGetOrder(id) return o.serviceID, o.customer, o.provider, o.amount, o.feeBps, o.status } // OrderDeadlines returns the two absolute heights that govern an order. // acceptanceDeadline is 0 until the order is delivered. func OrderDeadlines(id int64) (deliveryDeadline, acceptanceDeadline int64) { o := mustGetOrder(id) return o.deliveryDeadline, o.acceptanceDeadline } func OrderURI(id int64) string { return mustGetOrder(id).deliveryURI } // Quote reports what a purchase of this service would cost and what the // provider would receive on resolution, at the service's snapshotted fee. func Quote(id int64) (price, fee, toProvider int64) { s := mustGetService(id) f, err := feeledger.FeeFor(s.price, s.feeBps) if err != nil { panic(err) } return s.price, f, s.price - f } func Admin() address { return admin } func PendingAdmin() address { return pendingAdmin } func FeeRecipient() address { return feeRecipient } func FeeBps() int64 { return feeBps } func NumServices() int64 { return nextService } func NumOrders() int64 { return nextOrder } func ServicesOf(a address) int64 { return countFor(a) } func BalanceOf(addr address) int64 { return ledger.BalanceOf(addr.String()) } func UsersTotal() int64 { return ledger.UsersTotal() } func FeesAccrued() int64 { return ledger.FeesAccrued() } // EscrowTotal is the live escrow pool E: the sum of every order still in // Purchased or Delivered. func EscrowTotal() int64 { return escrowTotal } // Liabilities is everything this realm owes: live escrow, claimable // balances and the fee pot. SweepDenom reserves exactly this. func Liabilities() int64 { return escrowTotal + ledger.Liabilities() } func Held() int64 { return coinio.HeldAt(self, Denom) } func Surplus() int64 { return Held() - Liabilities() } func Address() address { return self } func Height() int64 { return runtime.ChainHeight() } // --- render --- func Render(path string) string { if len(path) > 2 && path[:2] == "s/" { return renderService(path[2:]) } if len(path) > 2 && path[:2] == "o/" { return renderOrder(path[2:]) } held := Held() liab := Liabilities() status := "OK" if held < liab { status = "VIOLATED" } out := "# Service marketplace\n\n" out += "Providers publish services; customers purchase into escrow; payment is\n" out += "released only on acceptance, or when the acceptance window lapses.\n\n" out += "## Configuration\n\n" out += "- fee for new services: " + itoa(feeBps) + " bps (cap " + itoa(MaxFeeBps) + " bps; each service keeps the fee snapshotted at registration)\n" out += "- acceptance window: " + itoa(AcceptanceBlocks) + " blocks\n" out += "- fee recipient: " + feeRecipient.String() + "\n" out += "- admin: " + admin.String() + "\n\n" out += "## Accounting (H == E + U + F + S)\n\n" out += "- escrow held for open orders (E): " + itoa(escrowTotal) + Denom + "\n" out += "- claimable balances (U): " + itoa(ledger.UsersTotal()) + Denom + "\n" out += "- fees accrued (F): " + itoa(ledger.FeesAccrued()) + Denom + "\n" out += "- held (H): " + itoa(held) + Denom + "\n" out += "- conservation: " + status + "\n\n" out += "## Latest services\n\n" if nextService == 0 { out += "No services yet.\n" return out } shown := 0 services.ReverseIterate("", "", func(_ string, v any) bool { s := v.(*service) out += "- [#" + itoa(s.id) + "](" + realmPath + ":s/" + itoa(s.id) + ") [" + s.status + "] " + s.title + " — " + itoa(s.price) + Denom + "\n" shown++ return shown >= RenderLimit }) return out } func renderService(arg string) string { id, err := strconv.ParseInt(arg, 10, 64) if err != nil { return "> [!WARNING]\n> invalid service id\n" } v := services.Get(padID(id)) if v == nil { return "> [!WARNING]\n> unknown service id\n" } s := v.(*service) out := "# Service #" + itoa(s.id) + ": " + s.title + "\n\n" out += "- status: " + s.status + "\n" out += "- provider: " + s.provider.String() + "\n" out += "- price: " + itoa(s.price) + Denom + "\n" out += "- delivery window: " + itoa(s.deliveryBlocks) + " blocks\n" out += "- fee (snapshot): " + itoa(s.feeBps) + " bps\n" out += "- orders placed: " + itoa(s.orders) + "\n" out += "\n## Description\n\n" + sanitize.InlineText(s.description) + "\n" return out } func renderOrder(arg string) string { id, err := strconv.ParseInt(arg, 10, 64) if err != nil { return "> [!WARNING]\n> invalid order id\n" } v := orders.Get(padID(id)) if v == nil { return "> [!WARNING]\n> unknown order id\n" } o := v.(*order) out := "# Order #" + itoa(o.id) + "\n\n" out += "- status: " + o.status + "\n" out += "- service: [#" + itoa(o.serviceID) + "](" + realmPath + ":s/" + itoa(o.serviceID) + ")\n" out += "- customer: " + o.customer.String() + "\n" out += "- provider: " + o.provider.String() + "\n" out += "- amount: " + itoa(o.amount) + Denom + "\n" out += "- fee (snapshot): " + itoa(o.feeBps) + " bps\n" out += "- delivery deadline: height " + itoa(o.deliveryDeadline) + "\n" if o.acceptanceDeadline != 0 { out += "- acceptance deadline: height " + itoa(o.acceptanceDeadline) + "\n" } if o.deliveryURI != "" { out += "\n## Delivery\n\n" + sanitize.InlineText(o.deliveryURI) + "\n" } return out } // --- internals --- const realmPath = "/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/service_market" // release moves an order's escrow into the provider's claimable balance // at the order's snapshotted fee, and drives the order terminal. The // status write and the escrow decrement happen together with the credit, // so no order can be released twice and E stays exactly Σ open amounts. func release(o *order, reason string) { credited, fee, err := ledger.Deposit(o.provider.String(), o.amount, o.feeBps) if err != nil { panic(err) } o.status = OrderReleased escrowTotal -= o.amount // >= 0: escrowTotal == Σ open amounts >= o.amount chain.Emit("OrderReleased", "orderId", itoa(o.id), "provider", o.provider.String(), "amount", itoa(o.amount), "credited", itoa(credited), "fee", itoa(fee), "reason", reason, ) } // refund moves an order's escrow into the customer's claimable balance // at ZERO fee and drives the order terminal. func refund(o *order, reason string) { credited, _, err := ledger.Deposit(o.customer.String(), o.amount, 0) if err != nil { panic(err) } o.status = OrderRefunded escrowTotal -= o.amount chain.Emit("OrderRefunded", "orderId", itoa(o.id), "customer", o.customer.String(), "amount", itoa(credited), "reason", reason, ) } // assertNoSend refuses coins on non-payable entrypoints: an accidental // -send would otherwise strand at the realm as sweep-only surplus. // PurchaseService is the only payable function. NOTE: this reads the // ORIGIN envelope — see the realm-provider caveat in the header. func assertNoSend() { if len(unsafe.OriginSend()) != 0 { panic("this function does not accept coins") } } func assertAdmin(caller address) { if caller != admin { panic("admin only") } } func mustGetService(id int64) *service { v := services.Get(padID(id)) if v == nil { panic("unknown service id") } return v.(*service) } func mustGetOrder(id int64) *order { v := orders.Get(padID(id)) if v == nil { panic("unknown order id") } return v.(*order) } // countFor reports how many ACTIVE services an address holds. func countFor(a address) int64 { v := providerNum.Get(a.String()) if v == nil { return 0 } return *(v.(*int64)) } // bumpFor adjusts the active-service counter, removing the entry at zero // so a provider who retires everything stops paying for the slot. func bumpFor(a address, delta int64) { key := a.String() v := providerNum.Get(key) if v == nil { if delta <= 0 { return } n := delta providerNum.Set(key, &n) return } p := v.(*int64) *p += delta if *p <= 0 { providerNum.Remove(key) } } // assertValidTitle bounds length and restricts the charset so titles are // list-safe in Render without escaping. func assertValidTitle(title string) { if len(title) == 0 || len(title) > MaxTitleLen { panic("title must be 1-" + strconv.Itoa(MaxTitleLen) + " characters") } for i := 0; i < len(title); i++ { c := title[i] switch { case c >= 'a' && c <= 'z': case c >= 'A' && c <= 'Z': case c >= '0' && c <= '9': case c == ' ' || c == '_' || c == '-': default: panic("title may only contain letters, digits, space, _ and -") } } } // padID renders an id as a fixed-width key so avl iteration order is // numeric order. func padID(id int64) string { s := strconv.FormatInt(id, 10) for len(s) < 12 { s = "0" + s } return s } func checkedAdd(a, b int64) (int64, bool) { c := a + b if (b > 0 && c < a) || (b < 0 && c > a) { return 0, false } return c, true } func itoa(n int64) string { return strconv.FormatInt(n, 10) }
  6. #6service_market_test.gno
  7. #7package service_market import ( "chain" "chain/banker" "strings" "testing" "gno.land/p/nt/testutils/v0" "gno.land/p/nt/uassert/v0" ) var ( testAdmin = testutils.TestAddress("smadmin") prov1 = testutils.TestAddress("smprov1") prov2 = testutils.TestAddress("smprov2") cust1 = testutils.TestAddress("smcust1") cust2 = testutils.TestAddress("smcust2") mallory = testutils.TestAddress("smmallory") ) // Established harness facts, carried from the sibling realms' suites: // init runs with an empty origin context (the real deploy captures the // origin caller); the banker resets between tests while realm globals // persist, so conservation is asserted in DELTA form after re-seeding // carried liabilities; testing.SetRealm affects only the frame that // calls it; and testing.SkipHeights WIPES the origin/realm context, so // every caller is re-armed after a skip. func init() { admin = testAdmin feeRecipient = testAdmin self = chain.PackageAddress("gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/service_market") } type baseline struct{ held, escrow, users, fees int64 } func begin() baseline { if l := Liabilities(); l > 0 { testing.IssueCoins(Address(), ugnot(l)) } return snap() } func snap() baseline { return baseline{held: Held(), escrow: EscrowTotal(), users: UsersTotal(), fees: FeesAccrued()} } // conserved asserts ΔH == ΔE + ΔU + ΔF + extraSurplus, the realm's // stated monetary invariant in delta form. func (b baseline) conserved(t *testing.T, extraSurplus int64) { t.Helper() dH := Held() - b.held dE := EscrowTotal() - b.escrow dU := UsersTotal() - b.users dF := FeesAccrued() - b.fees if dH != dE+dU+dF+extraSurplus { t.Fatalf("conservation violated: ΔH=%d ΔE=%d ΔU=%d ΔF=%d ΔS=%d", dH, dE, dU, dF, extraSurplus) } if Liabilities() != EscrowTotal()+UsersTotal()+FeesAccrued() { t.Fatalf("Liabilities() = %d, want E+U+F", Liabilities()) } } // appInvariant re-derives the escrow pool from the orders themselves. // EscrowTotal is a running counter; this proves it still equals Σ amount // over non-terminal orders, which is the claim SweepDenom's reserve // rests on. func appInvariant(t *testing.T) { t.Helper() var sum int64 for id := int64(0); id < NumOrders(); id++ { _, _, _, amount, _, status := OrderInfo(id) switch status { case OrderPurchased, OrderDelivered: sum += amount case OrderReleased, OrderRefunded: default: t.Fatalf("order %d has unknown status %q", id, status) } } if sum != EscrowTotal() { t.Fatalf("EscrowTotal() = %d, but Σ open orders = %d", EscrowTotal(), sum) } } func coinsOf(addr address) int64 { return banker.NewReadonlyBanker().GetCoin(addr, Denom) } func ugnot(n int64) chain.Coins { return chain.NewCoins(chain.NewCoin(Denom, n)) } // NOTE ON THE HARNESS. testing.SetRealm applies to the frame that CALLS // it, so the caller identity must be armed in the SAME frame as the // entrypoint invocation. A nested `as(from)` helper is silently // ineffective: the realm reverts when that helper returns, and the call // lands as the default test caller instead of `from` — which is a test // that quietly stops testing what it names. Hence the three lines // inline at every site, matching the sibling suites. // registerAs publishes a service (non-payable: envelope explicitly cleared). func registerAs(cur realm, from address, title, desc string, price, delivery int64) int64 { testing.SetRealm(testing.NewUserRealm(from)) testing.SetOriginCaller(from) testing.SetOriginSend(nil) return RegisterService(cross(cur), title, desc, price, delivery, MaxFeeBps) } // purchaseAs performs a chain-faithful purchase: envelope staged AND landed. func purchaseAs(cur realm, from address, serviceID, pay int64) int64 { testing.SetRealm(testing.NewUserRealm(from)) testing.SetOriginCaller(from) testing.SetOriginSend(ugnot(pay)) testing.IssueCoins(Address(), ugnot(pay)) return PurchaseService(cross(cur), serviceID) } func TestInitialConfig(t *testing.T) { if Admin() != testAdmin || FeeRecipient() != testAdmin { t.Fatal("seeded roles mismatch") } var zero address if PendingAdmin() != zero { t.Fatal("pending admin not zero") } if FeeBps() != 0 || NumServices() != 0 || NumOrders() != 0 { t.Fatal("initial counters not zero") } if Held() != 0 || Liabilities() != 0 || EscrowTotal() != 0 { t.Fatal("initial accounting not zero") } if Address() == zero { t.Fatal("realm address is zero") } } func TestRegisterService(cur realm, t *testing.T) { b := begin() id := registerAs(cur, prov1, "logo design", "two concepts and revisions", 500000, 20000) provider, title, price, delivery, snapBps, status, orderCount := ServiceInfo(id) if provider != prov1 || title != "logo design" || price != 500000 || delivery != 20000 || snapBps != 0 || status != ServiceActive || orderCount != 0 { t.Fatalf("fields wrong: %v %q %d %d %d %s %d", provider, title, price, delivery, snapBps, status, orderCount) } if ServiceDescription(id) != "two concepts and revisions" { t.Fatal("description mismatch") } if ServicesOf(prov1) != 1 { t.Fatalf("ServicesOf = %d, want 1", ServicesOf(prov1)) } p, fee, toProvider := Quote(id) if p != 500000 || fee != 0 || toProvider != 500000 { t.Fatalf("Quote = %d/%d/%d", p, fee, toProvider) } appInvariant(t) b.conserved(t, 0) } func TestRegisterRejects(cur realm, t *testing.T) { b := begin() // Non-payable: an attached envelope is refused, not stranded. testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(ugnot(100)) uassert.AbortsWithMessage(t, cur, "this function does not accept coins", func() { RegisterService(cross(cur), "x", "y", 1, MinDeliveryBlocks, MaxFeeBps) }) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) uassert.AbortsContains(t, cur, "title must be", func() { RegisterService(cross(cur), "", "d", 1, MinDeliveryBlocks, MaxFeeBps) }) uassert.AbortsContains(t, cur, "title may only contain", func() { RegisterService(cross(cur), "bad*title", "d", 1, MinDeliveryBlocks, MaxFeeBps) }) uassert.AbortsContains(t, cur, "description must be", func() { RegisterService(cross(cur), "ok", strings.Repeat("x", MaxDescLen+1), 1, MinDeliveryBlocks, MaxFeeBps) }) uassert.AbortsContains(t, cur, "price must be at least", func() { RegisterService(cross(cur), "ok", "d", 0, MinDeliveryBlocks, MaxFeeBps) }) uassert.AbortsContains(t, cur, "deliveryBlocks must be", func() { RegisterService(cross(cur), "ok", "d", 1, MinDeliveryBlocks-1, MaxFeeBps) }) uassert.AbortsContains(t, cur, "deliveryBlocks must be", func() { RegisterService(cross(cur), "ok", "d", 1, MaxDeliveryBlocks+1, MaxFeeBps) }) uassert.AbortsContains(t, cur, "maxFeeBps must be", func() { RegisterService(cross(cur), "ok", "d", 1, MinDeliveryBlocks, MaxFeeBps+1) }) appInvariant(t) b.conserved(t, 0) } // The provider's published terms cannot be devalued by an admin fee // change between registration and purchase: registration itself refuses // a fee above the ceiling the provider signed for. This is the grants Y1 // pattern, applied at the offer rather than the grant. func TestFeeCeilingAndSnapshot(cur realm, t *testing.T) { b := begin() testing.SetRealm(testing.NewUserRealm(testAdmin)) testing.SetOriginCaller(testAdmin) testing.SetOriginSend(nil) SetFeeBps(cross(cur), 500) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) uassert.AbortsContains(t, cur, "exceeds your ceiling", func() { RegisterService(cross(cur), "cheap only", "d", 1000, MinDeliveryBlocks, 100) }) // Registered at 500 bps. id := registerAs(cur, prov1, "copywriting", "800 words", 1000000, 20000) _, _, _, _, snapBps, _, _ := ServiceInfo(id) if snapBps != 500 { t.Fatalf("snapshot bps = %d, want 500", snapBps) } // Admin raises the fee afterwards; the existing service keeps 500. testing.SetRealm(testing.NewUserRealm(testAdmin)) testing.SetOriginCaller(testAdmin) testing.SetOriginSend(nil) SetFeeBps(cross(cur), MaxFeeBps) _, _, _, _, snapBps, _, _ = ServiceInfo(id) if snapBps != 500 { t.Fatalf("snapshot bps changed to %d", snapBps) } // And an order placed now carries the SERVICE's 500, not the live cap. oid := purchaseAs(cur, cust1, id, 1000000) _, _, _, _, orderBps, _ := OrderInfo(oid) if orderBps != 500 { t.Fatalf("order bps = %d, want 500", orderBps) } // Clean up: release so later tests start from a settled pool. testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) MarkDelivered(cross(cur), oid, "ipfs://copy") testing.SetRealm(testing.NewUserRealm(cust1)) testing.SetOriginCaller(cust1) testing.SetOriginSend(nil) AcceptDelivery(cross(cur), oid) testing.SetRealm(testing.NewUserRealm(testAdmin)) testing.SetOriginCaller(testAdmin) testing.SetOriginSend(nil) SetFeeBps(cross(cur), 0) appInvariant(t) b.conserved(t, 0) } func TestPurchaseEscrows(cur realm, t *testing.T) { b := begin() p0 := BalanceOf(prov1) sid := registerAs(cur, prov1, "translation", "en to fr", 300000, 20000) oid := purchaseAs(cur, cust1, sid, 300000) serviceID, customer, provider, amount, _, status := OrderInfo(oid) if serviceID != sid || customer != cust1 || provider != prov1 || amount != 300000 || status != OrderPurchased { t.Fatalf("order wrong: %d %v %v %d %s", serviceID, customer, provider, amount, status) } // The money is in escrow, NOT yet in anyone's claimable balance. if EscrowTotal()-b.escrow != 300000 { t.Fatalf("ΔE = %d, want 300000", EscrowTotal()-b.escrow) } if BalanceOf(prov1) != p0 { t.Fatalf("provider credited before resolution: %d -> %d", p0, BalanceOf(prov1)) } deliveryDeadline, acceptanceDeadline := OrderDeadlines(oid) if deliveryDeadline != Height()+20000 { t.Fatalf("delivery deadline = %d", deliveryDeadline) } if acceptanceDeadline != 0 { t.Fatalf("acceptance deadline set before delivery: %d", acceptanceDeadline) } _, _, _, _, _, _, orderCount := ServiceInfo(sid) if orderCount != 1 { t.Fatalf("service order count = %d", orderCount) } appInvariant(t) b.conserved(t, 0) } func TestPurchaseRejects(cur realm, t *testing.T) { b := begin() sid := registerAs(cur, prov1, "editing", "line edit", 250000, 20000) // Wrong amount, in both directions. testing.SetRealm(testing.NewUserRealm(cust1)) testing.SetOriginCaller(cust1) testing.SetOriginSend(ugnot(249999)) testing.IssueCoins(Address(), ugnot(249999)) uassert.AbortsContains(t, cur, "send exactly the listed price", func() { PurchaseService(cross(cur), sid) }) testing.SetOriginSend(ugnot(250001)) testing.IssueCoins(Address(), ugnot(250001)) uassert.AbortsContains(t, cur, "send exactly the listed price", func() { PurchaseService(cross(cur), sid) }) // Self-purchase. testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(ugnot(250000)) testing.IssueCoins(Address(), ugnot(250000)) uassert.AbortsWithMessage(t, cur, "the provider cannot purchase their own service", func() { PurchaseService(cross(cur), sid) }) // Unknown service. testing.SetRealm(testing.NewUserRealm(cust1)) testing.SetOriginCaller(cust1) testing.SetOriginSend(ugnot(250000)) testing.IssueCoins(Address(), ugnot(250000)) uassert.AbortsWithMessage(t, cur, "unknown service id", func() { PurchaseService(cross(cur), 9999) }) testing.SetRealm(testing.NewUserRealm(cust1)) testing.SetOriginCaller(cust1) testing.SetOriginSend(nil) // Those aborts each landed coins at the realm that the transaction // then reverted. In the harness the send is simulated rather than // rolled back, so the excess shows up as surplus; assert it is // EXACTLY surplus and has not leaked into escrow or a balance. appInvariant(t) if EscrowTotal() != b.escrow { t.Fatalf("a rejected purchase moved escrow: %d -> %d", b.escrow, EscrowTotal()) } if UsersTotal() != b.users { t.Fatalf("a rejected purchase credited a balance") } } // Retiring stops NEW orders and nothing else. An order already in // flight keeps its own copy of provider, amount and fee. func TestRetireService(cur realm, t *testing.T) { b := begin() n0 := ServicesOf(prov1) p0 := BalanceOf(prov1) sid := registerAs(cur, prov1, "consulting", "one hour", 400000, 20000) oid := purchaseAs(cur, cust1, sid, 400000) testing.SetRealm(testing.NewUserRealm(mallory)) testing.SetOriginCaller(mallory) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "only the provider may retire this service", func() { RetireService(cross(cur), sid) }) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) RetireService(cross(cur), sid) _, _, _, _, _, status, _ := ServiceInfo(sid) if status != ServiceRetired { t.Fatalf("status = %s", status) } if ServicesOf(prov1) != n0 { t.Fatalf("retire did not free the provider slot: %d -> %d", n0, ServicesOf(prov1)) } // Terminal: no un-retire, and no second retire. uassert.AbortsWithMessage(t, cur, "service is not active", func() { RetireService(cross(cur), sid) }) // New orders refused... testing.SetRealm(testing.NewUserRealm(cust2)) testing.SetOriginCaller(cust2) testing.SetOriginSend(ugnot(400000)) testing.IssueCoins(Address(), ugnot(400000)) uassert.AbortsWithMessage(t, cur, "service is not active", func() { PurchaseService(cross(cur), sid) }) // ...but the in-flight order still completes normally. testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) MarkDelivered(cross(cur), oid, "notes.pdf") testing.SetRealm(testing.NewUserRealm(cust1)) testing.SetOriginCaller(cust1) testing.SetOriginSend(nil) AcceptDelivery(cross(cur), oid) _, _, _, _, _, ostatus := OrderInfo(oid) if ostatus != OrderReleased { t.Fatalf("in-flight order broke on retire: %s", ostatus) } if BalanceOf(prov1) != p0+400000 { t.Fatalf("provider credited %d, want %d", BalanceOf(prov1)-p0, 400000) } appInvariant(t) // The refused cust2 purchase landed its 400000 at the realm; on chain // the abort reverts that send, but the harness simulates it rather // than rolling it back, so it surfaces as out-of-band surplus. It is // asserted as surplus precisely to prove it did NOT reach escrow or a // balance. b.conserved(t, 400000) } func TestServiceCapPerProvider(cur realm, t *testing.T) { b := begin() ids := make([]int64, 0, MaxServicesPerProvider) for i := 0; i < MaxServicesPerProvider; i++ { ids = append(ids, registerAs(cur, prov2, "svc", "d", 1000, MinDeliveryBlocks)) } if ServicesOf(prov2) != MaxServicesPerProvider { t.Fatalf("ServicesOf = %d", ServicesOf(prov2)) } testing.SetRealm(testing.NewUserRealm(prov2)) testing.SetOriginCaller(prov2) testing.SetOriginSend(nil) uassert.AbortsContains(t, cur, "services per provider", func() { RegisterService(cross(cur), "one too many", "d", 1000, MinDeliveryBlocks, MaxFeeBps) }) // Retiring frees exactly one slot. RetireService(cross(cur), ids[0]) ids = append(ids, registerAs(cur, prov2, "replacement", "d", 1000, MinDeliveryBlocks)) testing.SetRealm(testing.NewUserRealm(prov2)) testing.SetOriginCaller(prov2) testing.SetOriginSend(nil) uassert.AbortsContains(t, cur, "services per provider", func() { RegisterService(cross(cur), "still too many", "d", 1000, MinDeliveryBlocks, MaxFeeBps) }) // The cap is per provider, not global. registerAs(cur, prov1, "unaffected", "d", 1000, MinDeliveryBlocks) // Teardown: realm globals persist between tests in this harness, so // leaving prov2 pinned at the cap would silently break every later // test that registers as prov2. Retire everything this test opened. testing.SetRealm(testing.NewUserRealm(prov2)) testing.SetOriginCaller(prov2) testing.SetOriginSend(nil) for _, id := range ids[1:] { RetireService(cross(cur), id) } if ServicesOf(prov2) != 0 { t.Fatalf("teardown left %d services on prov2", ServicesOf(prov2)) } appInvariant(t) b.conserved(t, 0) } // ---- the three required protections ---- // UNAUTHORIZED SETTLEMENT. Nobody but the customer can resolve a // delivered order early — not the provider who did the work, not the // admin, not the fee recipient, not a stranger, not another realm. func TestUnauthorizedSettlement(cur realm, t *testing.T) { b := begin() p0 := BalanceOf(prov1) sid := registerAs(cur, prov1, "audit work", "review the thing", 1000000, 20000) oid := purchaseAs(cur, cust1, sid, 1000000) // Only the provider may mark delivered. for _, bad := range []address{cust1, testAdmin, mallory, prov2} { testing.SetRealm(testing.NewUserRealm(bad)) testing.SetOriginCaller(bad) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "only the provider may mark this order delivered", func() { MarkDelivered(cross(cur), oid, "x") }) } // Not even from another code realm. testing.SetRealm(testing.NewCodeRealm("gno.land/r/demo/attacker")) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "only the provider may mark this order delivered", func() { MarkDelivered(cross(cur), oid, "x") }) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) MarkDelivered(cross(cur), oid, "ipfs://report") // Only the customer may accept. THE key authorization. for _, bad := range []address{prov1, testAdmin, mallory, cust2} { testing.SetRealm(testing.NewUserRealm(bad)) testing.SetOriginCaller(bad) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "only the customer may accept this delivery", func() { AcceptDelivery(cross(cur), oid) }) } testing.SetRealm(testing.NewCodeRealm("gno.land/r/demo/attacker")) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "only the customer may accept this delivery", func() { AcceptDelivery(cross(cur), oid) }) // The timeout valve is permissionless but NOT early: even the // provider cannot pull it before the window lapses. testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) uassert.AbortsContains(t, cur, "acceptance window has not lapsed", func() { ReleaseTimeout(cross(cur), oid) }) // Nothing has moved. if BalanceOf(prov1) != p0 { t.Fatalf("provider credited without resolution: %d -> %d", p0, BalanceOf(prov1)) } if EscrowTotal()-b.escrow != 1000000 { t.Fatalf("escrow moved: ΔE=%d", EscrowTotal()-b.escrow) } // The customer, and only the customer, resolves it. testing.SetRealm(testing.NewUserRealm(cust1)) testing.SetOriginCaller(cust1) testing.SetOriginSend(nil) AcceptDelivery(cross(cur), oid) if BalanceOf(prov1)-p0 != 1000000 { t.Fatalf("provider credited %d, want 1000000 at 0 bps", BalanceOf(prov1)-p0) } appInvariant(t) b.conserved(t, 0) } // There is no admin path to escrow at all. The admin's only powers are // the fee for FUTURE services, the fee recipient, and succession. func TestAdminCannotTouchEscrow(cur realm, t *testing.T) { b := begin() sid := registerAs(cur, prov1, "held work", "d", 700000, 20000) oid := purchaseAs(cur, cust1, sid, 700000) testing.SetRealm(testing.NewUserRealm(testAdmin)) testing.SetOriginCaller(testAdmin) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "only the provider may mark this order delivered", func() { MarkDelivered(cross(cur), oid, "x") }) uassert.AbortsWithMessage(t, cur, "only the provider may decline this order", func() { DeclineOrder(cross(cur), oid) }) uassert.AbortsContains(t, cur, "delivery window has not lapsed", func() { ClaimRefund(cross(cur), oid) }) // And sweep cannot reach live escrow: the reserve covers E too. testing.IssueCoins(Address(), ugnot(12345)) // genuine out-of-band surplus before := coinsOf(testAdmin) SweepDenom(cross(cur), Denom) if got := coinsOf(testAdmin) - before; got != 12345 { t.Fatalf("swept %d, want exactly the 12345 surplus", got) } if EscrowTotal() != b.escrow+700000 { t.Fatalf("sweep reached escrow: %d", EscrowTotal()) } appInvariant(t) } // DOUBLE PAYMENT. Every terminal transition is reachable at most once, // and the two terminal outcomes are mutually exclusive. func TestNoDoublePayment(cur realm, t *testing.T) { b := begin() sid := registerAs(cur, prov1, "double check", "d", 600000, 20000) // Accept twice. oid := purchaseAs(cur, cust1, sid, 600000) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) MarkDelivered(cross(cur), oid, "v1") testing.SetRealm(testing.NewUserRealm(cust1)) testing.SetOriginCaller(cust1) testing.SetOriginSend(nil) AcceptDelivery(cross(cur), oid) credited := BalanceOf(prov1) uassert.AbortsWithMessage(t, cur, "order is not awaiting acceptance", func() { AcceptDelivery(cross(cur), oid) }) if BalanceOf(prov1) != credited { t.Fatalf("second accept paid again: %d -> %d", credited, BalanceOf(prov1)) } // Released orders cannot then be refunded, timed out, or re-delivered. uassert.AbortsWithMessage(t, cur, "order is not awaiting acceptance", func() { ReleaseTimeout(cross(cur), oid) }) uassert.AbortsWithMessage(t, cur, "order is not awaiting delivery", func() { ClaimRefund(cross(cur), oid) }) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "order is not awaiting delivery", func() { DeclineOrder(cross(cur), oid) }) uassert.AbortsWithMessage(t, cur, "order is not awaiting delivery", func() { MarkDelivered(cross(cur), oid, "v2") }) // Decline twice, and a declined order cannot then be released. oid2 := purchaseAs(cur, cust2, sid, 600000) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) DeclineOrder(cross(cur), oid2) refunded := BalanceOf(cust2) uassert.AbortsWithMessage(t, cur, "order is not awaiting delivery", func() { DeclineOrder(cross(cur), oid2) }) if BalanceOf(cust2) != refunded { t.Fatalf("second decline refunded again") } testing.SetRealm(testing.NewUserRealm(cust2)) testing.SetOriginCaller(cust2) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "order is not awaiting acceptance", func() { AcceptDelivery(cross(cur), oid2) }) appInvariant(t) b.conserved(t, 0) } // A claim pays out once; the ledger is debited before coins move. func TestNoDoubleClaim(cur realm, t *testing.T) { b := begin() p0 := BalanceOf(prov1) // carried in from earlier tests; ClaimAll drains it too sid := registerAs(cur, prov1, "claim once", "d", 800000, 20000) oid := purchaseAs(cur, cust1, sid, 800000) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) MarkDelivered(cross(cur), oid, "done") testing.SetRealm(testing.NewUserRealm(cust1)) testing.SetOriginCaller(cust1) testing.SetOriginSend(nil) AcceptDelivery(cross(cur), oid) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) before := coinsOf(prov1) ClaimAll(cross(cur)) if got := coinsOf(prov1) - before; got != p0+800000 { t.Fatalf("claimed %d, want %d", got, p0+800000) } if BalanceOf(prov1) != 0 { t.Fatal("balance not zeroed") } uassert.AbortsWithMessage(t, cur, "nothing to claim", func() { ClaimAll(cross(cur)) }) uassert.AbortsContains(t, cur, "insufficient", func() { Claim(cross(cur), 1) }) if got := coinsOf(prov1) - before; got != p0+800000 { t.Fatalf("a second claim paid out: total %d", got) } appInvariant(t) b.conserved(t, 0) } // STUCK FUNDS, arm 1: the provider abandons the order. Anyone may // return the escrow to the customer once the delivery window lapses. func TestStuckFundsProviderAbandons(cur realm, t *testing.T) { b := begin() sid := registerAs(cur, prov1, "never delivered", "d", 900000, MinDeliveryBlocks) oid := purchaseAs(cur, cust1, sid, 900000) // Too early, even for the customer. testing.SetRealm(testing.NewUserRealm(cust1)) testing.SetOriginCaller(cust1) testing.SetOriginSend(nil) uassert.AbortsContains(t, cur, "delivery window has not lapsed", func() { ClaimRefund(cross(cur), oid) }) testing.SkipHeights(MinDeliveryBlocks) // A total stranger pulls the valve; the money goes to the CUSTOMER, // not to the caller. testing.SetRealm(testing.NewUserRealm(mallory)) testing.SetOriginCaller(mallory) testing.SetOriginSend(nil) mBefore := BalanceOf(mallory) ClaimRefund(cross(cur), oid) if BalanceOf(mallory) != mBefore { t.Fatal("the valve paid its caller") } if BalanceOf(cust1) != 900000 { t.Fatalf("customer refund = %d, want 900000", BalanceOf(cust1)) } if FeesAccrued() != b.fees { t.Fatal("a refund accrued a protocol fee") } _, _, _, _, _, status := OrderInfo(oid) if status != OrderRefunded { t.Fatalf("status = %s", status) } // And the customer can actually get the coins out. testing.SetRealm(testing.NewUserRealm(cust1)) testing.SetOriginCaller(cust1) testing.SetOriginSend(nil) before := coinsOf(cust1) ClaimAll(cross(cur)) if got := coinsOf(cust1) - before; got != 900000 { t.Fatalf("customer withdrew %d", got) } appInvariant(t) b.conserved(t, 0) } // STUCK FUNDS, arm 2: the customer goes silent after delivery. Anyone // may release the escrow to the provider once acceptance lapses. This // is the valve whose polarity is INVERTED from every sibling realm's, // and getting it wrong would strand completed work. func TestStuckFundsCustomerGoesSilent(cur realm, t *testing.T) { b := begin() sid := registerAs(cur, prov1, "delivered ignored", "d", 1000000, MaxDeliveryBlocks) oid := purchaseAs(cur, cust1, sid, 1000000) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) MarkDelivered(cross(cur), oid, "ipfs://final") _, acceptanceDeadline := OrderDeadlines(oid) if acceptanceDeadline != Height()+AcceptanceBlocks { t.Fatalf("acceptance deadline = %d, want height+%d", acceptanceDeadline, AcceptanceBlocks) } testing.SkipHeights(AcceptanceBlocks) testing.SetRealm(testing.NewUserRealm(mallory)) testing.SetOriginCaller(mallory) testing.SetOriginSend(nil) mBefore := BalanceOf(mallory) ReleaseTimeout(cross(cur), oid) if BalanceOf(mallory) != mBefore { t.Fatal("the valve paid its caller") } if BalanceOf(prov1) != 1000000 { t.Fatalf("provider = %d, want 1000000", BalanceOf(prov1)) } _, _, _, _, _, status := OrderInfo(oid) if status != OrderReleased { t.Fatalf("status = %s", status) } appInvariant(t) b.conserved(t, 0) } // The delivery deadline protects the customer, so late delivery is // allowed while the refund valve is still un-pulled — but the refund // valve closes the instant delivery lands, and reopening it is not // possible. func TestLateDeliveryRace(cur realm, t *testing.T) { b := begin() p0 := BalanceOf(prov1) sid := registerAs(cur, prov1, "late but wanted", "d", 500000, MinDeliveryBlocks) oid := purchaseAs(cur, cust1, sid, 500000) testing.SkipHeights(MinDeliveryBlocks + 10) // Provider delivers late; permitted. testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) MarkDelivered(cross(cur), oid, "late.zip") // The refund valve is now closed: the order is no longer awaiting // delivery, so the lapsed delivery deadline is irrelevant. testing.SetRealm(testing.NewUserRealm(mallory)) testing.SetOriginCaller(mallory) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "order is not awaiting delivery", func() { ClaimRefund(cross(cur), oid) }) testing.SetRealm(testing.NewUserRealm(cust1)) testing.SetOriginCaller(cust1) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "order is not awaiting delivery", func() { ClaimRefund(cross(cur), oid) }) // The customer still holds the real lever: accept, or wait out the // acceptance window. AcceptDelivery(cross(cur), oid) if BalanceOf(prov1)-p0 != 500000 { t.Fatalf("provider credited %d, want 500000", BalanceOf(prov1)-p0) } appInvariant(t) b.conserved(t, 0) } // ---- fees, sweep, administration ---- func TestFeeSplitAndWithdraw(cur realm, t *testing.T) { b := begin() p0 := BalanceOf(prov1) testing.SetRealm(testing.NewUserRealm(testAdmin)) testing.SetOriginCaller(testAdmin) testing.SetOriginSend(nil) SetFeeBps(cross(cur), 250) // 2.5% sid := registerAs(cur, prov1, "fee bearing", "d", 1000000, 20000) p, fee, toProvider := Quote(sid) if p != 1000000 || fee != 25000 || toProvider != 975000 { t.Fatalf("Quote = %d/%d/%d", p, fee, toProvider) } oid := purchaseAs(cur, cust1, sid, 1000000) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) MarkDelivered(cross(cur), oid, "x") testing.SetRealm(testing.NewUserRealm(cust1)) testing.SetOriginCaller(cust1) testing.SetOriginSend(nil) AcceptDelivery(cross(cur), oid) if BalanceOf(prov1)-p0 != 975000 { t.Fatalf("provider credited %d, want 975000", BalanceOf(prov1)-p0) } if FeesAccrued()-b.fees != 25000 { t.Fatalf("fee pot ΔF = %d", FeesAccrued()-b.fees) } testing.SetRealm(testing.NewUserRealm(mallory)) testing.SetOriginCaller(mallory) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "only the fee recipient may withdraw fees", func() { WithdrawFees(cross(cur)) }) testing.SetRealm(testing.NewUserRealm(testAdmin)) testing.SetOriginCaller(testAdmin) testing.SetOriginSend(nil) before := coinsOf(testAdmin) WithdrawFees(cross(cur)) if got := coinsOf(testAdmin) - before; got != b.fees+25000 { t.Fatalf("withdrew %d", got) } uassert.AbortsWithMessage(t, cur, "no fees accrued", func() { WithdrawFees(cross(cur)) }) testing.SetRealm(testing.NewUserRealm(testAdmin)) testing.SetOriginCaller(testAdmin) testing.SetOriginSend(nil) SetFeeBps(cross(cur), 0) appInvariant(t) b.conserved(t, 0) } func TestSweepReservesEscrow(cur realm, t *testing.T) { b := begin() sid := registerAs(cur, prov1, "sweep guard", "d", 400000, 20000) purchaseAs(cur, cust1, sid, 400000) // No surplus yet. coinio.Sweep aborts rather than sending zero, so // the escrow is unreachable by REVERT, not merely by arithmetic — // which is the stronger of the two. testing.SetRealm(testing.NewUserRealm(testAdmin)) testing.SetOriginCaller(testAdmin) testing.SetOriginSend(nil) before := coinsOf(testAdmin) uassert.AbortsContains(t, cur, "no surplus to sweep", func() { SweepDenom(cross(cur), Denom) }) if coinsOf(testAdmin) != before { t.Fatal("swept out of a zero-surplus realm") } if EscrowTotal() != b.escrow+400000 { t.Fatal("sweep reached escrow") } // A foreign denom moves wholly. testing.IssueCoins(Address(), chain.NewCoins(chain.NewCoin("gjunk", 77))) SweepDenom(cross(cur), "gjunk") if banker.NewReadonlyBanker().GetCoin(testAdmin, "gjunk") != 77 { t.Fatal("foreign denom not swept") } testing.SetRealm(testing.NewUserRealm(mallory)) testing.SetOriginCaller(mallory) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "only the fee recipient may sweep surplus", func() { SweepDenom(cross(cur), Denom) }) appInvariant(t) } func TestAdminHandover(cur realm, t *testing.T) { b := begin() testing.SetRealm(testing.NewUserRealm(mallory)) testing.SetOriginCaller(mallory) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "admin only", func() { TransferAdmin(cross(cur), mallory) }) uassert.AbortsWithMessage(t, cur, "admin only", func() { SetFeeBps(cross(cur), 10) }) uassert.AbortsWithMessage(t, cur, "admin only", func() { SetFeeRecipient(cross(cur), mallory) }) testing.SetRealm(testing.NewUserRealm(testAdmin)) testing.SetOriginCaller(testAdmin) testing.SetOriginSend(nil) // A malformed nominee is rejected where the typo is made, for parity // with SetFeeRecipient. The zero address keeps its documented meaning // as the cancel sentinel and must NOT be caught by that check. uassert.AbortsWithMessage(t, cur, "invalid admin address", func() { TransferAdmin(cross(cur), address("g1notanaddress")) }) TransferAdmin(cross(cur), prov2) if Admin() != testAdmin { t.Fatal("nomination took effect immediately") } if PendingAdmin() != prov2 { t.Fatalf("pending = %v", PendingAdmin()) } TransferAdmin(cross(cur), "") if PendingAdmin() != "" { t.Fatalf("zero address did not cancel: %v", PendingAdmin()) } TransferAdmin(cross(cur), prov2) // Wrong acceptor. testing.SetRealm(testing.NewUserRealm(mallory)) testing.SetOriginCaller(mallory) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "only the nominated admin may accept", func() { AcceptAdmin(cross(cur)) }) testing.SetRealm(testing.NewUserRealm(prov2)) testing.SetOriginCaller(prov2) testing.SetOriginSend(nil) AcceptAdmin(cross(cur)) if Admin() != prov2 { t.Fatalf("admin = %v", Admin()) } var zero address if PendingAdmin() != zero { t.Fatal("pending not cleared") } // Authority MOVED: the former admin is now powerless. testing.SetRealm(testing.NewUserRealm(testAdmin)) testing.SetOriginCaller(testAdmin) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "admin only", func() { SetFeeBps(cross(cur), 10) }) // Hand it back so the rest of the suite keeps its seeded roles. testing.SetRealm(testing.NewUserRealm(prov2)) testing.SetOriginCaller(prov2) testing.SetOriginSend(nil) TransferAdmin(cross(cur), testAdmin) testing.SetRealm(testing.NewUserRealm(testAdmin)) testing.SetOriginCaller(testAdmin) testing.SetOriginSend(nil) AcceptAdmin(cross(cur)) if Admin() != testAdmin { t.Fatal("handback failed") } appInvariant(t) b.conserved(t, 0) } // ---- render safety ---- func TestRenderSanitizesUntrustedText(cur realm, t *testing.T) { begin() evil := "[click](https://evil.example) ![x](https://evil.example/x.png)" sid := registerAs(cur, prov1, "render probe", evil, 100000, 20000) oid := purchaseAs(cur, cust1, sid, 100000) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) MarkDelivered(cross(cur), oid, evil) sOut := Render("s/" + itoa(sid)) if strings.Contains(sOut, "](https://evil.example)") { t.Fatal("service description rendered an unescaped markdown link") } oOut := Render("o/" + itoa(oid)) if strings.Contains(oOut, "](https://evil.example/x.png)") { t.Fatal("delivery uri rendered an unescaped markdown image") } // Unknown and malformed ids degrade to a warning, never a panic. for _, p := range []string{"s/abc", "o/abc", "s/999999", "o/999999"} { if !strings.Contains(Render(p), "[!WARNING]") { t.Fatalf("Render(%q) did not warn", p) } } if !strings.Contains(Render(""), "Service marketplace") { t.Fatal("home render broken") } appInvariant(t) } // The home page is bounded regardless of catalog size. func TestRenderIsBounded(cur realm, t *testing.T) { begin() for i := 0; i < RenderLimit+5; i++ { registerAs(cur, prov2, "bulk svc", "d", 1000, MinDeliveryBlocks) testing.SetRealm(testing.NewUserRealm(prov2)) testing.SetOriginCaller(prov2) testing.SetOriginSend(nil) // Retire immediately so the per-provider cap is not the thing // under test here. RetireService(cross(cur), NumServices()-1) } out := Render("") if got := strings.Count(out, "\n- [#"); got > RenderLimit { t.Fatalf("home listed %d services, cap is %d", got, RenderLimit) } appInvariant(t) } // ---- guards on every non-payable entrypoint ---- func TestNonPayableEntrypointsRefuseCoins(cur realm, t *testing.T) { b := begin() sid := registerAs(cur, prov1, "send guard", "d", 100000, 20000) oid := purchaseAs(cur, cust1, sid, 100000) stage := func(from address) { testing.SetRealm(testing.NewUserRealm(from)) testing.SetOriginCaller(from) testing.SetOriginSend(ugnot(1)) } msg := "this function does not accept coins" stage(prov1) uassert.AbortsWithMessage(t, cur, msg, func() { MarkDelivered(cross(cur), oid, "x") }) uassert.AbortsWithMessage(t, cur, msg, func() { DeclineOrder(cross(cur), oid) }) uassert.AbortsWithMessage(t, cur, msg, func() { RetireService(cross(cur), sid) }) uassert.AbortsWithMessage(t, cur, msg, func() { ClaimAll(cross(cur)) }) uassert.AbortsWithMessage(t, cur, msg, func() { Claim(cross(cur), 1) }) stage(cust1) uassert.AbortsWithMessage(t, cur, msg, func() { AcceptDelivery(cross(cur), oid) }) uassert.AbortsWithMessage(t, cur, msg, func() { ClaimRefund(cross(cur), oid) }) uassert.AbortsWithMessage(t, cur, msg, func() { ReleaseTimeout(cross(cur), oid) }) stage(testAdmin) uassert.AbortsWithMessage(t, cur, msg, func() { SetFeeBps(cross(cur), 1) }) uassert.AbortsWithMessage(t, cur, msg, func() { SetFeeRecipient(cross(cur), prov1) }) uassert.AbortsWithMessage(t, cur, msg, func() { TransferAdmin(cross(cur), prov1) }) uassert.AbortsWithMessage(t, cur, msg, func() { AcceptAdmin(cross(cur)) }) uassert.AbortsWithMessage(t, cur, msg, func() { WithdrawFees(cross(cur)) }) uassert.AbortsWithMessage(t, cur, msg, func() { SweepDenom(cross(cur), Denom) }) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) appInvariant(t) if EscrowTotal() != b.escrow+100000 { t.Fatal("a refused call moved escrow") } } func TestUnknownIdsAbort(cur realm, t *testing.T) { begin() testing.SetRealm(testing.NewUserRealm(mallory)) testing.SetOriginCaller(mallory) testing.SetOriginSend(nil) uassert.AbortsWithMessage(t, cur, "unknown service id", func() { RetireService(cross(cur), 999999) }) uassert.AbortsWithMessage(t, cur, "unknown order id", func() { AcceptDelivery(cross(cur), 999999) }) uassert.AbortsWithMessage(t, cur, "unknown order id", func() { ClaimRefund(cross(cur), 999999) }) uassert.AbortsWithMessage(t, cur, "unknown order id", func() { ReleaseTimeout(cross(cur), 999999) }) // A view is not a crossing call, so its failure is a plain panic // rather than an abort — a different assertion, not a weaker one. uassert.PanicsWithMessage(t, cur, "unknown service id", func() { ServiceDescription(999999) }) uassert.PanicsWithMessage(t, cur, "unknown order id", func() { OrderInfo(999999) }) } func TestMarkDeliveredBoundsURI(cur realm, t *testing.T) { begin() sid := registerAs(cur, prov1, "uri bound", "d", 100000, 20000) oid := purchaseAs(cur, cust1, sid, 100000) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) uassert.AbortsContains(t, cur, "uri must be at most", func() { MarkDelivered(cross(cur), oid, strings.Repeat("u", MaxURILen+1)) }) MarkDelivered(cross(cur), oid, strings.Repeat("u", MaxURILen)) if len(OrderURI(oid)) != MaxURILen { t.Fatal("uri not stored at the boundary") } appInvariant(t) } func TestCheckedAddOverflow(t *testing.T) { const maxInt64 int64 = 1<<63 - 1 if _, ok := checkedAdd(maxInt64, 1); ok { t.Fatal("overflow not detected") } if v, ok := checkedAdd(maxInt64-1, 1); !ok || v != maxInt64 { t.Fatal("boundary add rejected") } if v, ok := checkedAdd(5, -3); !ok || v != 2 { t.Fatal("negative delta broken") } } // A full multi-party run, asserting conservation at every checkpoint — // the property that all three protections ultimately rest on. func TestFullLifecycleConservation(cur realm, t *testing.T) { b := begin() testing.SetRealm(testing.NewUserRealm(testAdmin)) testing.SetOriginCaller(testAdmin) testing.SetOriginSend(nil) SetFeeBps(cross(cur), 100) // 1% s1 := registerAs(cur, prov1, "alpha", "d", 1000000, MinDeliveryBlocks) s2 := registerAs(cur, prov2, "beta", "d", 2000000, MaxDeliveryBlocks) b.conserved(t, 0) // Accepted. o1 := purchaseAs(cur, cust1, s1, 1000000) b.conserved(t, 0) testing.SetRealm(testing.NewUserRealm(prov1)) testing.SetOriginCaller(prov1) testing.SetOriginSend(nil) MarkDelivered(cross(cur), o1, "a") testing.SetRealm(testing.NewUserRealm(cust1)) testing.SetOriginCaller(cust1) testing.SetOriginSend(nil) AcceptDelivery(cross(cur), o1) b.conserved(t, 0) // Declined. o2 := purchaseAs(cur, cust2, s2, 2000000) testing.SetRealm(testing.NewUserRealm(prov2)) testing.SetOriginCaller(prov2) testing.SetOriginSend(nil) DeclineOrder(cross(cur), o2) b.conserved(t, 0) // Expired to a refund. o3 := purchaseAs(cur, cust1, s1, 1000000) testing.SkipHeights(MinDeliveryBlocks) testing.SetRealm(testing.NewUserRealm(mallory)) testing.SetOriginCaller(mallory) testing.SetOriginSend(nil) ClaimRefund(cross(cur), o3) b.conserved(t, 0) // Timed out to a release. o4 := purchaseAs(cur, cust2, s2, 2000000) testing.SetRealm(testing.NewUserRealm(prov2)) testing.SetOriginCaller(prov2) testing.SetOriginSend(nil) MarkDelivered(cross(cur), o4, "b") testing.SkipHeights(AcceptanceBlocks) testing.SetRealm(testing.NewUserRealm(mallory)) testing.SetOriginCaller(mallory) testing.SetOriginSend(nil) ReleaseTimeout(cross(cur), o4) b.conserved(t, 0) if EscrowTotal() != b.escrow { t.Fatalf("escrow not drained: %d -> %d", b.escrow, EscrowTotal()) } // Everyone withdraws; the realm ends holding only what it started // with plus the fee pot it still owes. for _, who := range []address{prov1, prov2, cust1, cust2, testAdmin, mallory} { if BalanceOf(who) == 0 { continue } testing.SetRealm(testing.NewUserRealm(who)) testing.SetOriginCaller(who) testing.SetOriginSend(nil) ClaimAll(cross(cur)) } // Every counterparty this suite has ever used is in that list, so // draining them must take U to exactly zero — not back to the // baseline, which still carried earlier tests' unclaimed credits. if UsersTotal() != 0 { t.Fatalf("claimable residue: %d", UsersTotal()) } appInvariant(t) b.conserved(t, 0) testing.SetRealm(testing.NewUserRealm(testAdmin)) testing.SetOriginCaller(testAdmin) testing.SetOriginSend(nil) SetFeeBps(cross(cur), 0) }
  8. #8/gno.MemPackageType
  9. #9 MPUserAll

Result log

msg:0,success:true,log:,events:[]