nucleus_substrate_core/mechanism/
vcg.rs1use serde::{Deserialize, Serialize};
22use std::collections::{BTreeMap, BTreeSet};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31#[non_exhaustive]
32pub enum ResourceDim {
33 GpuSeconds,
35 GridCarbonGramsCo2,
37 PeerVerifierMillis,
40 CorpusBitsAdded,
42 KnowledgeSpillover,
45 FxVolatilityDelta,
47 AuctionDelay,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
62pub struct ExternalityProfile {
63 pub dimensions: BTreeMap<ResourceDim, OpaqueSignedClaim>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
72pub struct OpaqueSignedClaim {
73 pub signed_bytes: Vec<u8>,
75 pub signature: Vec<u8>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
82pub struct PostedAuction {
83 pub auction_id: String,
84 pub required_capabilities: BTreeSet<String>,
85 pub reward_micro_usd: u64,
86 pub pigouvian_rates: Vec<(ResourceDim, u64)>,
87 pub scale: u64,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
91pub struct AgentBid {
92 pub agent_spiffe_id: String,
93 pub auction_id: String,
94 pub effective_value_micro_usd: u64,
95 #[serde(default, skip_serializing_if = "Option::is_none")]
96 pub externality_profile: Option<ExternalityProfile>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
100pub struct MatchResult {
101 pub auction_id: String,
102 pub winner_spiffe_id: Option<String>,
103 pub clearing_price_micro_usd: u64,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
107pub struct VcgMatchResult {
108 pub winners: Vec<(String, u64)>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
118pub struct VickreyPayload {
119 pub auction: PostedAuction,
120 pub bids: Vec<AgentBid>,
121 pub match_result: MatchResult,
122}
123
124pub fn vickrey_projection_body(
130 auction: PostedAuction,
131 bids: Vec<AgentBid>,
132 match_result: MatchResult,
133) -> serde_json::Value {
134 let payload = VickreyPayload {
135 auction,
136 bids,
137 match_result,
138 };
139 serde_json::to_value(payload).expect("VickreyPayload serializes deterministically")
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use crate::{Projection, Receipt, Session};
146
147 fn dummy_auction() -> PostedAuction {
148 PostedAuction {
149 auction_id: "a1".into(),
150 required_capabilities: BTreeSet::new(),
151 reward_micro_usd: 1_000_000,
152 pigouvian_rates: vec![(ResourceDim::GpuSeconds, 100)],
153 scale: 1_000_000,
154 }
155 }
156
157 fn dummy_match() -> MatchResult {
158 MatchResult {
159 auction_id: "a1".into(),
160 winner_spiffe_id: Some("spiffe://test/agent".into()),
161 clearing_price_micro_usd: 250_000,
162 }
163 }
164
165 #[test]
166 fn vickrey_payload_round_trips_serde() {
167 let payload = VickreyPayload {
168 auction: dummy_auction(),
169 bids: vec![],
170 match_result: dummy_match(),
171 };
172 let json = serde_json::to_string(&payload).unwrap();
173 let back: VickreyPayload = serde_json::from_str(&json).unwrap();
174 assert_eq!(back.auction.auction_id, "a1");
175 assert_eq!(back.match_result.clearing_price_micro_usd, 250_000);
176 }
177
178 #[test]
179 fn projection_body_helper_packs_into_economic_variant() {
180 let body = vickrey_projection_body(dummy_auction(), vec![], dummy_match());
181 let projection = Projection::Economic(body);
182 assert_eq!(projection.kind(), "economic");
183 }
184
185 #[test]
189 fn cleared_auction_round_trips_through_receipt() {
190 let sk = ed25519_dalek::SigningKey::from_bytes(&[7u8; 32]);
191 let session = Session {
192 session_id: "spiffe://test/auction-hub".into(),
193 issuer_kid: "test-kid".into(),
194 issued_at_micros: 1_717_000_000_000_000,
195 parent_chain: vec![],
196 };
197 let body = vickrey_projection_body(dummy_auction(), vec![], dummy_match());
198 let receipt = Receipt::sign(session, vec![Projection::Economic(body)], &sk);
199 let vk: [u8; 32] = sk.verifying_key().to_bytes();
200 receipt.verify(&vk).expect("cleared-auction Receipt verifies");
201 }
202
203 #[test]
207 fn projection_economic_wire_format_includes_auction_id() {
208 let body = vickrey_projection_body(dummy_auction(), vec![], dummy_match());
209 let projection = Projection::Economic(body);
210 let v = serde_json::to_value(&projection).unwrap();
211 assert_eq!(v["kind"], "economic");
212 assert_eq!(v["body"]["auction"]["auction_id"], "a1");
213 assert_eq!(v["body"]["match_result"]["clearing_price_micro_usd"], 250_000);
214 }
215}